Log inGet started

engine

Updated 5 September 2026

The engine namespace — 36 functions.

globals/engine/discardPlayChanges

engine.discardPlayChanges() -> ()

Arm the leave-play safeguard's deliberate discard for the play session this is called from, so that session's play to edit flip proceeds and discards its unaccepted changes.

Returns ()

globals/engine/gameplayReady

engine.gameplayReady -> boolean

Whether gameplay simulation is running: not paused, and the play scene materialized. Read-only.

Returns boolean

globals/engine/gpuCompute

engine.gpuCompute -> boolean

Whether this process holds a live GPU device, so compute dispatch is available. Read-only.

Returns boolean

globals/engine/headless

engine.headless -> boolean

Whether this boot renders offscreen with no window a person can see. Content that only serves someone at a display stands down when it reads true. Read-only.

Returns boolean

globals/engine/markScriptingBaseline

engine.markScriptingBaseline() -> number

Record the scripting registries — world-event subscriptions, the four lifecycle-watcher lists, and the require cache — as they stand right now, and make that the point engine.resetScriptingState() restores to. Replaces any previous mark. Returns the new mark's generation, counting from 1. Mark once the engine is serving rather than while it boots: the registries keep growing as the prelude subscribes, the world entrypoint runs and the startup scene loads, so a mark taken partway through sits below the rest of that work and the first reset would remove it.

Returns number

engine.markScriptingBaseline()
world.on("player_join", function() end)
engine.resetScriptingState() -- the subscription above is gone

globals/engine/mode

engine.mode -> "edit" | "play"

The engine mode this process is in, edit or play. Assigning it takes the flip, side effects and all.

Returns "edit" | "play"

globals/engine/offDeviceRebuilt

engine.offDeviceRebuilt(id: number) -> boolean

Remove an engine.onDeviceRebuilt subscriber by its watcher id. Returns true when a live watcher carried that id, false when it named none — already removed, or never registered.

Parameters

  • id number — Watcher id returned by engine.onDeviceRebuilt.

Returns boolean

local id = engine.onDeviceRebuilt(function() end)
engine.offDeviceRebuilt(id)

globals/engine/offModeChange

engine.offModeChange(id: number) -> boolean

Remove an engine.onModeChange subscriber by its watcher id. Returns true when a live watcher carried that id, false when it named none — already removed, or never registered.

Parameters

  • id number — Watcher id returned by engine.onModeChange.

Returns boolean

local id = engine.onModeChange(function() end)
engine.offModeChange(id)

globals/engine/offPauseChange

engine.offPauseChange(id: number) -> boolean

Remove an engine.onPauseChange subscriber by its watcher id. Returns true when a live watcher carried that id, false when it named none.

Parameters

  • id number — Watcher id returned by engine.onPauseChange.

Returns boolean

globals/engine/offWorldLoaded

engine.offWorldLoaded(id: number) -> boolean

Remove an onWorldLoaded subscriber by its watcher id.

Parameters

  • id number

Returns boolean

globals/engine/offWorldReady

engine.offWorldReady(id: number) -> boolean

Remove an engine.onWorldReady subscriber by its watcher id. Returns true when a live watcher carried that id, false when it named none.

Parameters

  • id number — Watcher id returned by engine.onWorldReady.

Returns boolean

globals/engine/offWorldUnloading

engine.offWorldUnloading(id: number) -> boolean

Remove an engine.onWorldUnloading subscriber by its watcher id. Returns true when a live watcher carried that id, false when it named none.

Parameters

  • id number — Watcher id returned by engine.onWorldUnloading.

Returns boolean

globals/engine/onDeviceRebuilt

engine.onDeviceRebuilt(callback: (number) -> ()) -> number

Register a callback that fires after the engine has answered a lost render device by building another one. The callback receives the new device generation — a number that counts the devices this session has run on, and moves exactly once per rebuild. Returns a watcher id.

A device is lost when the driver resets, when the GPU is taken away, or when a browser reclaims a WebGPU context. Everything the engine can re-derive by itself it does: meshes, materials, shaders, render passes and the UI are all back on the new device before this fires. What it cannot re-derive is what YOUR content made and only the GPU held — a texture uploaded from pixels a script computed, a compute buffer it filled, a render target it created. Make those again here.

Content that owns no GPU resource of its own needs no subscriber: asset handles re-materialise on their next use.

Parameters

  • callback (number) -> () — Function invoked as (generation: number).

Returns number

engine.onDeviceRebuilt(function(generation)
-- the noise field lived only on the GPU, so it is computed again
regenerateNoiseTexture()
end)

globals/engine/onModeChange

engine.onModeChange(callback: (string, string) -> ()) -> number

Register a callback that fires synchronously whenever engine.mode changes. Callback receives (newMode, oldMode) as strings. Returns a watcher id for future removal. Consumers (player_spawner, camera_spawner, editor-UI bootstrap, world entrypoint top-level onModeChange, etc.) all subscribe through this single API — there is no other fire path. Mode is engine state, so the watcher hangs off the engine module.

Parameters

  • callback (string, string) -> () — Function invoked as (newMode: string, oldMode: string).

Returns number

local id = engine.onModeChange(function(new, old)
print("flipped " .. old .. " -> " .. new)
end)

globals/engine/onPauseChange

engine.onPauseChange(callback: (boolean, boolean) -> ()) -> number

Register a callback that fires synchronously whenever the gameplay pause flag flips via an explicit engine.paused write. Callback receives (newPaused, oldPaused) as booleans. Returns a watcher id. Pause is independent of engine.mode: pausing play mode returns the editor authoring surface (free camera + EditorOnly entities) over the frozen play world, and resuming hides it again. Mode-driven pause resets (the edit=paused / play=running defaults applied on a mode flip) are delivered through onModeChange, not this hook.

Parameters

  • callback (boolean, boolean) -> () — Function invoked as (newPaused: boolean, oldPaused: boolean).

Returns number

local id = engine.onPauseChange(function(paused)
print(paused and "frozen" or "running")
end)

globals/engine/onWorldLoaded

engine.onWorldLoaded(callback: () -> ()) -> number

Register a callback fired (no args) when the world is fully LOADED — its .world_entrypoint.luau ran AND its onWorldLoad returned (the startup scene loaded, defaults seeded, editor UI mounted). This is strictly AFTER onWorldReady (content synced): ready = "bytes are in the VFS"; loaded = "the entrypoint has run". LATCHED — a callback registered after the world is already loaded fires immediately, so a late consumer never misses it and never has to poll. Read the same state synchronously via engine.worldLoaded.

Parameters

  • callback () -> () — Function invoked with no arguments.

Returns number

globals/engine/onWorldReady

engine.onWorldReady(callback: () -> ()) -> number

Register a callback fired (no args) when the bound world's content has been synced into the VFS and the world is ready to load. This is the race-free, user-space hook that drives the whole world-VM lifecycle: the builtin world-entrypoint loader subscribes to it and, when it fires, loadstring(vfs.read(...))s /source/.world_entrypoint.luau and runs its onWorldLoad — exactly the way a scene entrypoint loads. The trusted VM fires this (via world.markReady()) ONLY once the bytes are in the VFS, so a subscriber never sees a half-synced world. Returns a watcher id.

Parameters

  • callback () -> () — Function invoked with no arguments.

Returns number

globals/engine/onWorldUnloading

engine.onWorldUnloading(callback: () -> ()) -> number

Symmetric teardown of engine.onWorldReady: register a callback fired (no args) when the bound world is unbinding/swapping out. The builtin loader runs the world entrypoint's onWorldUnload here, so the world entrypoint has the same load/unload parity a scene entrypoint has. Returns a watcher id.

Parameters

  • callback () -> () — Function invoked with no arguments.

Returns number

globals/engine/paused

engine.paused -> boolean

Whether gameplay is paused: update(dt) component callbacks are gated off while editorUpdate(dt) keeps firing in edit mode.

Returns boolean

globals/engine/profile

engine.profile -> "editor" | "runtime"

The boot profile this process started under, editor or runtime. Read-only.

Returns "editor" | "runtime"

globals/engine/resetScriptingState

engine.resetScriptingState() -> { [string]: number }

Drop every world-event subscription, lifecycle watcher and cached module registered since the last engine.markScriptingBaseline(), leaving everything registered before it in place — including the builtin world-entrypoint loader, which subscribes at VM boot and so always sits below any mark. Raises when no mark has been taken. Returns per-registry counts of what was removed: worldEvents, modeWatchers, worldReadyWatchers, worldUnloadingWatchers, pauseWatchers, modules, and total.

Returns { [string]: number }

globals/engine/scriptingRegistryCounts

engine.scriptingRegistryCounts() -> { [string]: number }

How many subscriptions each scripting registry holds right now, plus the size of the require cache and the generation of the mark in force. Keys: worldEvents, modeWatchers, worldReadyWatchers, worldUnloadingWatchers, pauseWatchers, modules, and baselineGeneration (nil when no mark has been taken).

Returns { [string]: number }

globals/engine/setMode

engine.setMode(mode: string, options: { strict: boolean? }?) -> { mode: string, bypassed: { any } }

Change the engine mode with per-call control over the play gate, and read back what the change went past. engine.mode = value is the same flip with the defaults.

options.strict = false lets THIS call enter play while your own content carries error-severity diagnostics. It settles with the call: the world's lsp.strict_mode is untouched, so no other session and no later session of the world sees a different gate. The returned bypassed array holds the diagnostics the call went past — each { path, line, col, code, message, severity } — and the engine log carries the same list. An error in content another session wrote never gates the flip, so it never appears here; a push still refuses to publish while any of them stands.

Parameters

  • mode string"edit" or "play".
  • options { strict: boolean? } (optional){ strict: boolean? }. strict = false waives the play gate for this call; true or omitted honours the world's lsp.strict_mode.

Returns { mode: string, bypassed: { any } }{ mode, bypassed } — the mode now in force and the diagnostics this call entered play past (empty when it went past none).

local report = engine.setMode("play", { strict = false })
for _, d in ipairs(report.bypassed) do
print(("entered play past %s:%d — %s"):format(d.path, d.line, d.message))
end

globals/engine/timeScale

engine.timeScale -> number

The global time scale applied to the fixed-timestep accumulator and to update(dt): 1.0 is real time, 0.0 frozen, 2.0 double speed.

Returns number

globals/engine/vertexStride

engine.vertexStride -> number

Byte stride of the engine's standard GPU Vertex layout, which a mesh built from a compute buffer sizes and strides its writes to. Read-only.

Returns number

globals/engine/worldLoaded

engine.worldLoaded -> boolean

Whether the world entrypoint's onWorldLoad has run to completion. Read-only.

Returns boolean

typed/builtin//modules/api/engine/engine/engine/discardPlayChanges

engine.discardPlayChanges() -> ()

Arm the leave-play safeguard's deliberate discard for the play session this is called from, so that session's play to edit flip proceeds and discards its unaccepted changes.

Returns ()

typed/builtin//modules/api/engine/engine/engine/gameplayReady

engine.gameplayReady -> boolean

Whether gameplay simulation is running: not paused, and the play scene materialized. Read-only.

Returns boolean

typed/builtin//modules/api/engine/engine/engine/gpuCompute

engine.gpuCompute -> boolean

Whether this process holds a live GPU device, so compute dispatch is available. Read-only.

Returns boolean

typed/builtin//modules/api/engine/engine/engine/headless

engine.headless -> boolean

Whether this boot renders offscreen with no window a person can see. Content that only serves someone at a display stands down when it reads true. Read-only.

Returns boolean

typed/builtin//modules/api/engine/engine/engine/mode

engine.mode -> "edit" | "play"

The engine mode this process is in, edit or play. Assigning it takes the flip, side effects and all.

Returns "edit" | "play"

typed/builtin//modules/api/engine/engine/engine/paused

engine.paused -> boolean

Whether gameplay is paused: update(dt) component callbacks are gated off while editorUpdate(dt) keeps firing in edit mode.

Returns boolean

typed/builtin//modules/api/engine/engine/engine/profile

engine.profile -> "editor" | "runtime"

The boot profile this process started under, editor or runtime. Read-only.

Returns "editor" | "runtime"

typed/builtin//modules/api/engine/engine/engine/timeScale

engine.timeScale -> number

The global time scale applied to the fixed-timestep accumulator and to update(dt): 1.0 is real time, 0.0 frozen, 2.0 double speed.

Returns number

typed/builtin//modules/api/engine/engine/engine/vertexStride

engine.vertexStride -> number

Byte stride of the engine's standard GPU Vertex layout, which a mesh built from a compute buffer sizes and strides its writes to. Read-only.

Returns number

typed/builtin//modules/api/engine/engine/engine/worldLoaded

engine.worldLoaded -> boolean

Whether the world entrypoint's onWorldLoad has run to completion. Read-only.

Returns boolean

  • api
  • reference