Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

README.md

layers

Top-level scene-management global + Scene proxy. § 17 step 9 of docs/plans/2026-05-01-player-camera-unification.md. Renamed from the legacy scene.* global to match /runtime/layers/ and to give scene-management state (active root, additives, callback registries) a single anchor distinct from per-scene authoring.

Global surface

SymbolTypeNotes
layers.activeScene | nilCurrent root non-additive scene as a proxy. Live read; updates on every load/unload.
layers.list(){ Scene }Every loaded scene (root + additives) as proxies.
layers.find(ref)Scene | nilLookup by AssetRef<scene> or layer-id string.
layers.is_loaded(ref)boolean
layers.load(ref, opts?)—Loads ref. opts.additive = true layers on top; default = non-additive (replaces root).
layers.unload(ref?)—Unloads ref; nil = active root.
layers.reload(ref?)—Equivalent to unload + load with same ref.
layers.onLoad(cb) -> handlefunctionSubscribe to any scene-load event. Returns handle for offLoad.
layers.offLoad(handle)function
layers.onUnload(cb) -> handle / offUnload(h)functionSame shape for unload events.
layers.observe()SceneObservationWhat every load did and what each loaded scene costs, in one read.
layers.lastLoad()SceneLoadReport | nilThe last load's report; nil when nothing has loaded.
layers.lastUnload()SceneUnloadReport | nilThe last unload's, under the name the layer was loaded with.
layers.loadHistory(){ SceneLoadReport }The completed load reports still held, oldest first.
layers.problems(ref?){ SceneLoadFailure }What a layer failed to produce, and why.
layers.whyPartial(ref?)(reason?, detail?)The nearest cause a layer is not whole, from a closed set.
layers.inventory(){ SceneLayerInventory }What the engine attributes to each loaded layer.
layers.cost(){ SceneLayerCost }Each layer's per-frame entrypoint tick, summed across the window.
layers.resetCostWindow()—Open a new cost window.

Scene proxy surface

A Scene proxy is a sealed table with fields and :method callables.

Field / methodNotes
s.assetAssetRef<scene>
s.name, s.pathinformational (logging, debug)
s.guidthe scene asset's guid — the layer's identity
s.sceneIdentitythe identity of the scene asset the layer was loaded from (e.g. @builtin::scenes.test_arena), whichever VM loaded it
s.additiveboolean — additive overlay vs root scene
s.parent, s.childrenadditive ownership chain
s:root()walks .parent to the non-additive root; returns self for roots
s.players, s.camerapopulated by §17 step 10 (player + camera spawners)
s.entrypointAssetRef<script> | nil
s.visibleboolean — read via field, toggle via s:set_visible(b)
s.state"loading" / "loaded" / "ready" / "partial" / "unloading" — the layer's lifecycle state. Both "ready" and "partial" are terminal: the load has settled.
s.ready, s.loadedbooleans in lock-step with state. ready is true once the layer has settled, in either terminal state — it is the flag to wait on.
s.okboolean — whether everything the scene declared was produced and the layer has run clean since. False whenever state is "partial", including when the tick starts raising long after the load settled.
s.failures{ SceneLoadFailure } — every distinct failure the load and the layer's per-frame tick produced, each with the count of how often it repeated.
s:unload()unload this scene; additives use layers.unload(layerId), roots use layers.active:reload()
s:reload()unload + reload same ref
s:set_visible(b)show/hide without unloading
s:load_additive(ref)load a sibling additive overlay; returns the new proxy

Example

-- Switch root scene
layers.load(asset.ref("@builtin::scenes.baseline_visual"))

-- Subscribe to all loads
local h = layers.onLoad(function(scene)
    print("loaded:", scene.name)
end)

-- Layer an HUD overlay on top
layers.load(asset.ref("@builtin::scenes.hud"), { additive = true })

-- Iterate every loaded scene
for _, scn in ipairs(layers.list()) do
    print(scn.name, scn.additive and "(additive)" or "(root)")
end

layers.unload()       -- unload the active root
layers.offLoad(h)

What a load did, and what a scene costs

Every load writes one record at its own completion point; the readings below compose that record, so a caller that did not make the load can still ask what it did. A load that produced failures settles into "partial" rather than "ready", and names the nearest cause from a closed set: loaderRaised, entrypointCompileFailed, entrypointBodyRaised, entrypointRaised, buildRaised, entityFailed, parentMissing, parentRefused, parentAbandoned, componentUnresolved, componentRefused, subscriberRaised, updateRaised.

layers.load(asset.ref("scenes.arena", "scene"))

-- Did it come up whole?
if not layers.active.ok then
    local reason, detail = layers.whyPartial()
    print(reason, detail)
    for _, f in layers.problems() do
        -- `count` rises when the same failure happens again, so a per-frame
        -- tick that keeps raising stays one line.
        print(f.phase, f.reason, f.entity, f.component, f.hook, f.count)
    end
end

-- What did the swap do?
local r = layers.lastLoad()
print(r.name, r.outcome, r.durationMs)
print("replaced", r.replaced and r.replaced.name)      -- the root that went
print("cascaded", #r.cascaded)                         -- the overlays with it
print(r.entities.added, "arrived,", r.entities.removed, "left")
print(r.phases.teardown, r.phases.instantiate, r.phases.settle)

-- Which loaded scene is expensive? `totalMs` is a SUM across the window, so
-- reset it first and read `avgMs` for the per-tick figure.
layers.resetCostWindow()
task.wait(1)
for _, c in layers.cost() do
    print(c.name, c.avgMs, "ms/tick over", c.calls, "ticks")
end

-- What does each layer hold, and what does no layer claim?
for _, l in layers.inventory() do print(l.name, l.entities, l.ok) end
print(layers.observe().totals.unattributed)

The scene toolbox carries the same two readings as scene.observe and scene.whyPartial.

Authoring surface

layers.* is the sole public scene-management surface. It accepts AssetRefs (not paths), drives a callback registry, and constructs Scene proxies that carry players / camera / hooks. The previous scene.* global has been removed — its low-level FFI bindings now live behind layers.* and the Scene proxy methods.