Log inGet started

modules

The modules namespace — the engine's Luau API reference for modules.

The modules namespace — 1033 functions.

modules/AnimationAssetTypeBehavior/README

AnimationAssetTypeBehavior

Behaviour for the animation asset type — a clip's disk shape. The payload is data.zanim (the engine-native binary AnimAsset, parallel to .mesh's data.zmsh). onCreate writes that payload, and — when the clip's source rig guid is supplied — records it as source_rig.json (a .rig asset reference) so the clip knows the skeleton it was authored on and retargets onto any humanoid. Converters call asset.create("animation", name, { bytes, rig }) to mint a clip.

modules/AnimationAssetTypeBehavior/onChange

onChange(ref: any, change: { [string]: any })

React to a write inside a .animation/ instance: when the clip's source rig link (source_rig.json) changes, record the rig's humanoid classification into .metadata for search — the clip inherits it from the rig it targets. Originator-only; .metadata syncs as ordinary content.

modules/AnimationAssetTypeBehavior/reindexSearchMeta

reindexSearchMeta(self): ()

Re-derive this clip's search .metadata (its humanoid classification, resolved from the source rig). onChange calls it when source_rig.json changes; the search-metadata backfill calls it per clip to populate clips minted before the deriver existed.

modules/AssetChangeDispatch/README

require("@builtin/modules/asset_change_dispatch") -- AssetChangeDispatch

Routes a VFS source write to the enclosing typed-asset's onChange(ref, change) hook. Installs _G.__zero_dispatch_asset_change, which the engine calls (via ffi_callbacks::fire_asset_change_dispatch) once per source write.

This is the type-level analogue of the component-centric onAssetReload(field) fan-out. Components react to assets they reference; an asset type reacts to writes INSIDE its own instances. When any file under a <name>.<type>/ folder changes on the VFS, the engine hands the written path here; this module finds the enclosing typed-asset folder, loads its <type>.assetType/behavior.luau, and — if that module exports an onChange function — invokes onChange(ref, change). The engine half is deliberately thin (a generic "this path was written" signal); all the resolution + behaviour lives here in Luau, mirroring the __build_asset_ref_proxy split.

Usage: local AssetChangeDispatch = require("@builtin/modules/asset_change_dispatch")

modules/AssetChangeDispatch/dispatch

dispatch(path: string, kind: string?, origin: string?)

Dispatch the asset-type change hook for a VFS path. Called by the engine for two orthogonal axes:

  • kind: a per-file EDIT ("edited", path is the written file) vs an asset COMPLETION ("seeded", path is the typed-asset folder, now fully present from a world seed). The type inspects kind to decide whether to filter by which file changed or act on the whole asset.
  • origin: "local" for a write made on this client, "remote" for a peer-synced write — forwarded so hooks can gate on it (importers run on the originator only). Resolves the typed asset, loads its behavior.luau, and invokes onChange(ref, change) (change = { path, asset, type, kind, origin }). When the path has no enclosing typed asset, forwards to the loose-file seam (dispatch_loose) so the importer system can claim orphan writes.

modules/AssetChangeDispatch/dispatchDelete

dispatchDelete(path: string)

Dispatch the asset-type DELETE hook for a removed typed-asset FOLDER — the teardown counterpart of dispatch. path is the folder that was removed; this resolves its <name>.<type> identity, loads the type's behavior.luau, and invokes onDelete(ref) if defined (a .component unregisters its type). No-op when the path isn't a registered typed asset or the type defines no onDelete. The engine calls this via ffi_callbacks::fire_asset_delete_dispatch once per typed-asset folder removal.

modules/AssetRef/README

require("@builtin/modules/asset_ref") -- AssetRef

AssetRef proxy builder. Attaches the default method metatable to every { __ref, type, name, guid, identity, path } envelope produced by asset.resolve / asset.ref, and dispatches type-specific methods from <typename>.assetType/behavior.luau so a .material ref carries material-only methods (and so on).

The Rust side (crates/zero_scripting/src/ffi/bindings/asset.rs) used to ship a C metatable with a fixed __index that knew about getSource / getBytes / getText / exists / inspect / meta. That shape couldn't grow without an FFI change, which meant the per-asset-type behaviour required to make refs useful (read a material's properties, instantiate a bundle, run a tool) had no hook. This module owns that responsibility now. It exposes one entry point — M.build(envelope) — that the Rust factory invokes via the _G.__build_asset_ref_proxy global the prelude installs. The build call attaches a shared metatable whose __index first serves the default method table, then falls through to the type's own ref table (loaded lazily from @builtin::assetTypes.<type>.behavior). The default method surface is fixed and engine-required — per-type ref tables CANNOT shadow getSource / getBytes / getText / exists or the lazy meta property, matching the contract in gh#1889. inspect is NOT a fixed default — it falls through to the type's own ref.inspect(self) (returning the type-specific detail for asset.inspect's dispatch), and is nil when the type defines none.

Usage: local AssetRef = require("@builtin/modules/asset_ref")

modules/AssetRef/build

build(envelope: any): any

Attach the AssetRef method metatable to an envelope table. Invoked by the Rust factory (push_asset_ref_handle_G.__build_asset_ref_proxy) immediately after the six envelope fields (__ref, type, name, guid, identity, path) have been set, so the metatable's __index only ever fires for method / property lookups, never for the literal envelope fields.

modules/AssetRef/canInstantiate

canInstantiate(self): boolean

Whether this asset can be instantiated into a scene — true iff its asset type defines a sceneInstantiate hook. Generic capability query (no type allowlist); consumers gate on it before offering a scene path (an Asset.source field, a viewport drop, a tool argument).

modules/AssetRef/deps

deps(self): { deps: { any } }

Return this asset's outbound dependency list, aggregated across every file inside it (for composite asset folders) — same data asset.deps(ref) returns. { deps = { { asset_guid, origin, literal, via, line?, checksum?, ... }, ... } }. Returns { deps = {} } if the asset has no recorded refs (no .refs sidecar anywhere in its tree, or every dep resolved to engine-origin).

modules/AssetRef/instantiateInto

instantiateInto(self, context: { [string]: any }?): (any, { [string]: string })

Instantiate this asset into a scene via its type's sceneInstantiate hook. The single uniform entry every consumer calls — it dispatches to the per-type hook so Asset.component, UI drops, and tools share one path across bundle / avatar / mesh / dynamicAsset and every future scene-instantiable type. Errors when the asset's type is not scene-instantiable (canInstantiate is false).

modules/AssetRef/loadTypeModule

loadTypeModule(asset_type: string): { [string]: any }?

Load the full behavior.luau module table for an asset type ({ ref?, global?, onChange? }), or nil when the type ships no behavior.luau. Registry-driven resolution — same path the per-type ref dispatch uses. Exposed so the asset-change dispatcher (modules/asset_change_dispatch) can reach a type's onChange hook without duplicating the resolution logic.

modules/AssetRef/persistInEditMode

persistInEditMode(self: any)

Generic edit-mode persistence hook. In EDIT mode, flush a ref's transient runtime overlay (ref.runtime) to its backing asset file by invoking the type's own saveDefinition(self). This enforces the edit-mode contract uniformly across EVERY assetType that defines a saveDefinition: in edit mode a runtime modification is written through to the asset immediately, so it syncs to peers and is saved (edit mode is not "playing" — there is no per-frame mutation and no perf concern). In PLAY mode this is a deliberate no-op: runtime overlays stay transient (frame-fast) and are persisted back to the source asset on demand. An assetType opts in simply by exposing ref.saveDefinition; no per-type branching lives here.

modules/AssetTypeAssetTypeRef/README

AssetTypeAssetTypeRef

Per-instance methods exposed on every AssetRef<assetType>. Loaded lazily by asset_ref.module.

An AssetRef<assetType> points at a <typename>.assetType/ definition folder. SHARED CODE that a type exposes to every instance is declared in that type's own behavior.luau as a modules map of tracked requires, e.g.: -- inside <typename>.assetType/behavior.luau M.modules = { shared = require(".shared") } and reached from any instance through the pinned instance->type link: -- inside any foo.<thatType>/init.luau local api = asset.containing(FILE).modules.shared Resolution follows the instance's pinned typeRef guid (its .refs via = "asset_type" dep), so identically-named modules in different types never collide and an imported instance reaches the exact type version it was authored against. This is the mechanism that makes a type fully self-contained: the behaviour every instance relies on lives in the type, not duplicated as a require("modules.x") in 100 instance files. See modules/asset_ref.module for the dispatch.

modules/AssetTypeAssetTypeRef/getReadme

getReadme(self): string?

Read the asset type's own README.md body (type-level docs — what THE TYPE is, surfaced by asset.inspect).

modules/AssetTypeAssetTypeRef/onCreate

onCreate(name: string, _opts: { [string]: any }?): { [string]: string }

Generic-creation hook for asset.create("assetType", name). Scaffolds a new asset type by cloning this type definition's own template/ skeleton with the [name] placeholder rewritten to your type's name, so the new <name>.assetType/ is ready to edit (type.yaml, behavior.luau, and its own template/ instance body). Pure: returns the substituted file map.

modules/AssetValidator/README

require("@builtin/systems/worldValidation.package/assetValidator") -- AssetValidator

Structure validator for asset folders. Delegates each asset to asset.validate — the engine primitive that checks the folder against its type's type.yaml (required files, one_of_groups, unexpected entries, content constraints), waives the describe-this-asset requirements for private subassets (assets nested inside a non-broadcasting container like a .bundle), and runs the type's own semantic validate hook — and maps the result into the report's Problem records.

asset.validate is the same primitive the world.push gate runs, so a world that validates clean here also passes the structural half of the publish gate. Each problem is attributed to the asset path (missing files anchor at the asset root; existing files — unexpected entries, content violations — anchor at the file). An asset whose type has no registered type.yaml gets a schema.unknown_type warning; an asset the registry cannot resolve (e.g. written this frame, registration still draining) gets a schema.unresolved_asset warning.

Usage: local AssetValidator = require("@builtin/systems/worldValidation.package/assetValidator")

modules/AssetValidator/validate

validate(entry)

Validate one asset folder via asset.validate and map the result into Problem records.

modules/AssetValidator/validateBatch

validateBatch(assets)

Validate a batch of asset entries and flatten the per-asset problem lists into one array. Convenience wrapper over M.validate.

modules/AvatarAssetTypeBehavior/README

AvatarAssetTypeBehavior

Behaviour for the avatar asset type — a playable character composed from three INDEPENDENT parts so each swaps on its own axis: - body — a .bundle: skinned mesh + bone entities (the look). - controller — the movement system (default: CharacterController + MovementState + Humanoid, the standard kinematic humanoid mover). - animation — the system that animates the body (default: the shared Locomotion blend space, which reads the controller's velocity and plays retargeted clips; or ClipPlayer; or your own component). Movement and animation are NOT tied together: attaching the controller never pulls in Locomotion, and choosing your own animation keeps the standard controller — so authoring your own locomotion is a one-field swap. The clip set retargets onto any humanoid rig, so the default locomotion drives any humanoid body with no per-body setup. :instantiate() composes the parts; :setCharacter(root, body) swaps the body. Point a scene's PlayerPrototype.body at a body carrying this avatar to make it the body every connecting player spawns with.

modules/AvatarAssetTypeBehavior/instantiate

instantiate(self, target: EntityRef?, opts: any): (EntityRef, { [string]: string })

Instantiate this avatar: spawn an avatar root, spawn the body bundle under it, then attach the movement controller and the animation system — independently. With the defaults the root gets the standard humanoid controller and the shared Locomotion that drives the body with retargeted clips. Pass a target entity REF to make that entity the avatar root (the player-avatar path passes the freshly-spawned avatar entity here).

modules/AvatarAssetTypeBehavior/sceneInstantiate

sceneInstantiate(self, context: any): (EntityRef, { [string]: string })

Scene-instantiation hook — the uniform contract the base AssetRef dispatches instantiateInto(context) to. An avatar conforms by adapting the context onto its own instantiate: context.target becomes the avatar root (the Asset component's owner), threading the stable-id idMap, sparse diff, and sourceTag; with no target it spawns a fresh avatar root and places it at context.position.

modules/AvatarAssetTypeBehavior/setCharacter

setCharacter(self, root: any, body: any): EntityRef

Swap the body on a live avatar. Despawns the current body and spawns body (a .bundle ref) in its place. The controller + animation systems live on the avatar root and re-resolve the new body's rig, so the look swaps while movement and animation are kept. A single-clip ClipPlayer rides the skinned body, so it is re-attached to the new body.

modules/BlankAppLayout/README

require("@builtin/_templates.blank_app.blank_app_layout") -- BlankAppLayout

Minimum-viable zui app — one screen, one widget. Clone-and-edit starting point. Replace the hud builder with your tree.

Usage: local BlankAppLayout = require("@builtin/_templates.blank_app.blank_app_layout")

modules/BundleAssetTypeRef/README

BundleAssetTypeRef

Per-instance methods exposed on every AssetRef<bundle>. Loaded lazily by asset_ref.module.

modules/BundleAssetTypeRef/getTemplate

getTemplate(self): any

Parse the bundle's entity_template into a Lua value. The engine format is a flat array of entity records ({ name, original_id, parent_id, components, ... }), so the result is an array-table; the return type is any because the payload is decoded JSON. Returns nil when parsing fails or the bundle has no template. Parsed results are cached per-guid — repeat :instantiate on the same bundle reuses the parsed table instead of re-decoding the JSON. Cleared by :setTemplate so authoring-time edits land on the next instantiate.

modules/BundleAssetTypeRef/getTemplateRaw

getTemplateRaw(self): string?

Read the raw entity_template file (JSON) as a string. Use :getTemplate() for a parsed table.

modules/BundleAssetTypeRef/inspectDetail

inspectDetail(self): any

asset.inspect type-specific detail: { nodeCount, attachedComponents, rootName }, parsed from the bundle's own entity_template JSON — never instantiates the hierarchy. A bundle with no readable template returns an empty detail rather than erroring.

modules/BundleAssetTypeRef/instantiate

instantiate(self, target: EntityRef?, opts: { [string]: any }?): (EntityRef, { [string]: string })

modules/BundleAssetTypeRef/listChildren

listChildren(self, rootsOnly: boolean?): { string }

List the names of every entity captured in THIS bundle instance's entity_template. Pass rootsOnly = true to restrict to entities with no parent_id. Best-effort: empty list when the template can't parse.

modules/BundleAssetTypeRef/listContents

listContents(self): { { name: string, isDirectory: boolean } }

List the entries directly under the bundle root folder (meshes, textures, materials, sub-bundles) for tooling (inspectors, dependency graphs).

modules/BundleAssetTypeRef/meshBindings

meshBindings(self): { any }

List each renderable node's mesh→material binding from THIS bundle's entity_template — the static data a spawned Model / SkinnedModel resolves — WITHOUT instantiating anything. Answers "which material does this mesh bind" as a direct read, with no spawn / settle / tree-walk / despawn cycle. Each entry is { node, nodeId, component, mesh, material }, where mesh and material are ref descriptors { guid, identity, name, path } (nil when the component omits that field). One entry per renderable component, in template order. Best-effort: empty list when the template can't parse.

modules/BundleAssetTypeRef/onChange

onChange(ref: any, change: { [string]: any })

React to a write inside a .bundle/ instance: an entity_template change means the assembled model changed — regenerate the bundle's preview.png, its persisted visual description (syncs, publishes, feeds search and browser thumbnails), and record the component set + humanoid classification into .metadata for search facets. Originator-only; both artifacts sync to peers as ordinary content.

modules/BundleAssetTypeRef/preview

preview(self, opts: { [string]: any }?)

Render a preview of this bundle — instantiate its entity hierarchy, auto-frame an offscreen camera over it, render one still, tear down.

modules/BundleAssetTypeRef/reindexSearchMeta

reindexSearchMeta(self): ()

Re-derive this bundle's search .metadata (component set + humanoid classification) from its current entity_template, without touching the preview. onChange calls it on every template edit; the search-metadata backfill calls it per bundle to populate assets minted before the deriver existed. Reads the template fresh — the parsed cache is only invalidated by setTemplate.

modules/BundleAssetTypeRef/sceneInstantiate

sceneInstantiate(self, context: { [string]: any }?): (EntityRef, { [string]: string })

Scene-instantiation hook — the uniform contract the base AssetRef dispatches instantiateInto(context) to. A bundle conforms by adapting the context onto its own instantiate: with context.target it explodes ONTO that entity (the Asset component's owner), threading the stable-id idMap, sparse diff, and sourceTag; with no target it spawns the scene-clean single-entity instance and places it at context.position.

modules/BundleAssetTypeRef/setTemplate

setTemplate(self, template: { any }): boolean

Overwrite the bundle's entity_template with a Lua table, JSON-encoded. The VFS write triggers the engine's asset hot-reload pipeline, so every component subscribed to this bundle's guid (via a declared asset field) reconciles automatically. Returns true on success.

modules/BundleAssetTypeRef/update

update(self, entityId: string): boolean

Re-compose THIS bundle from entityId's current hierarchy and write the new template back to the bundle's on-disk path. Walks the whole hierarchy under entityId (recursive), capturing live component state + transforms of every non-temporary descendant. The VFS write fires the engine's asset hot-reload pipeline, so every component subscribed to this bundle's guid reconciles automatically.

modules/BundleUpdate/README

require("@builtin/modules/bundle_update") -- BundleUpdate (also available as global 'bundle')

Adds bundle.update to the existing bundle namespace. Pure Luau — thin ergonomic wrapper around bundle.compose. The prelude grafts this onto the engine's bundle table at boot.

bundle.update(entityId, bundleRef?) re-composes the bundle from the entity's current hierarchy state, writing the new template into the bundle's existing on-disk path. The reconcile is fully engine-driven: the VFS write fires the generic asset hot-reload pipeline, which dispatches onAssetReload(field) on every component instance whose declared asset field references the bundle's guid. Components decide what reload means for them (Asset.component → despawn + re-instantiate template; user components → whatever they implement in onAssetReload). With one arg, the bundle ref is inferred from entity(entityId).component.get("Asset").source. With two args the explicit ref wins. There is no opt-in subscription surface — declaring an asset field IS the subscription.

Usage: local BundleUpdate = require("@builtin/modules/bundle_update") Also available as global: bundle

modules/BundleUpdate/installInto

installInto(bundleNamespace: BundleNamespace)

Install update onto the supplied bundle-shaped namespace. The prelude calls this once at boot with the engine's bundle global; users shouldn't call it directly.

modules/BundleUpdate/update

update(entityId: string, bundleRef: BundleRef?): any

Re-compose a bundle from an entity's current hierarchy and write it back to the bundle's on-disk path. The VFS write triggers the engine's generic asset hot-reload pipeline, which fires onAssetReload(field) on every component subscribed to this bundle's guid via a declared asset field — those components reconcile per their own policy.

modules/CanvasAppLayout/README

require("@builtin/_templates.canvas_app.canvas_app_layout") -- CanvasAppLayout

Z.shell.canvas template — two nodes + one bezier connection. First-cut shell renders nodes as rounded-rects with text; onConnect is deferred (manual wiring required for now).

Usage: local CanvasAppLayout = require("@builtin/_templates.canvas_app.canvas_app_layout")

modules/ComponentAssetTypeRef/README

ComponentAssetTypeRef

Per-instance methods exposed on every AssetRef<component>. Loaded lazily by asset_ref.module.

modules/ComponentAssetTypeRef/attachTo

attachTo(self, entityId: string, fields: { [string]: any }?): any

Attach this component to an entity, equivalent to entity(entityId).component.add(compRef.name, fields). The component's type name comes from the ref's name (the leaf folder stem with .component stripped) — never guessed.

modules/ComponentAssetTypeRef/getAssetFields

getAssetFields(self): any

The component type's asset-field declarations — a map of field name to asset category (e.g. { material = "material" }), or nil when the component declares no Field.assetRef fields.

modules/ComponentAssetTypeRef/getInfo

getInfo(self): any

Metadata for the component type: { name, builtin, executionOrder, hooks }, where hooks is a { <hookName> = true } map scanned from the source.

modules/ComponentAssetTypeRef/getInitScript

getInitScript(self): string?

Read the component's entry script (init.luau / init.lua) as raw text.

modules/ComponentAssetTypeRef/getReadme

getReadme(self): string?

Read the component's README.md body.

modules/ComponentAssetTypeRef/getSource

getSource(self): string?

The component type's registered source text — the live definition in the runtime registry. getInitScript reads the on-disk entry file; this reads what the engine actually registered (and resolves builtin components by identity).

modules/ComponentAssetTypeRef/inspect

inspect(self): any

asset.inspect type-specific detail: { fields, methods, events, hooks, assetFields, executionOrder }, parsed from the component's own entry script (getInitScript) via luau_introspect. Cached on the asset's content checksum, so re-inspecting unchanged source is free. A component with no readable entry script returns an empty detail rather than erroring.

modules/ComponentAssetTypeRef/listInstances

listInstances(self): { string }

List every entity in the live scene that currently has a component of this type. Useful for inspector tooling.

modules/ComponentAssetTypeRef/listPublicFields

listPublicFields(self): { string }

Best-effort list of the component's public field names by parsing the entry script. Recognises public.<name> = ... assignments at module scope; doesn't expand metatable declarations. Use as a discovery hint, not a strict schema.

modules/ComponentAssetTypeRef/onChange

onChange(ref, change)

Asset-type change callback: (re)register + hot-reload this component type whenever its .component is seeded (a world seed) or its entry script (init.luau / init.lua) is edited. This is what registers and live-reloads USER components — library components register through the VFS write hook (author-immutable content does not dispatch onChange). Mirrors the .material / .shader assetTypes owning their own registration. Convergent

  • idempotent: registration never writes back into the asset folder, and the underlying registry upsert is a no-op when the source is unchanged.

modules/ComponentAssetTypeRef/onDelete

onDelete(ref)

Asset-type delete callback: unregister this component type when its .component folder is removed. The teardown counterpart of onChange's registration — the type owns both halves of its runtime lifecycle, the way the .material / .shader types do. __components.unregister drops the type from the runtime registry, the FFI type-info / schema mirrors, and the /registered/components/ projection; instances already attached to live entities keep running (their closures are already bound).

modules/ComponentAssetTypeRef/onRegister

onRegister(self)

Initial-registration callback: register this component's type from its entry script the moment the instance first registers. Fired by the world-ready onRegister sweep BEFORE the world entrypoint (and its scene load) runs, so component.add-by-name resolves for entities the same load materialized. Idempotent: the registry upsert is a no-op when the source is unchanged, and a later onChange re-registration converges on the same definition.

modules/ComputeShaderAssetTypeRef/README

ComputeShaderAssetTypeRef

Per-instance methods exposed on every AssetRef<computeShader>. Loaded lazily by asset_ref.module.

modules/ComputeShaderAssetTypeRef/compileByName

compileByName(ref: string)

Compile a compute shader by reference from its .computeShader VFS source — the lazy compile-on-first-use entry. A system that must guarantee the shader is registered before its first dispatch calls this (via compute.compileByName) to bring it online through the same generic compileCompute path an edit runs. Resolution goes through the universal asset system — a reference that doesn't resolve is a bad reference in the content that owns it, not something to special-case here.

modules/ComputeShaderAssetTypeRef/getBindings

getBindings(self): { [string]: any }

List this compute shader's declared bindings + params (parsed from bindings.yaml). Returns { bindings = { {name, kind, access?, element?, format?}, ... }, params = { {name, type, default}, ... } }. This is the editor-discovery surface — the SAME parse the compile uses.

modules/ComputeShaderAssetTypeRef/getSource

getSource(self): string?

Read the compute WGSL body (shader.wgsl) as raw text.

modules/ComputeShaderAssetTypeRef/onChange

onChange(ref, change)

Asset-type change callback: (re)compile the compute shader whenever its WGSL body or bindings.yaml is written. This is the ONLY thing that compiles a .computeShader — so it fires on the initial create (the template write) AND on every later edit, with no world reload. Convergent: see compileCompute.

modules/ComputeShaderAssetTypeRef/setSource

setSource(self, src: string): boolean

Overwrite the compute WGSL body on disk. Hot-reload recompiles the shader on the next frame. Returns true on success.

modules/ContentVersion/README

require("@builtin/modules/content_version") -- ContentVersion

Per-path content-version counters — a cheap, synchronous "has this file changed?" token that lets an assetType behavior memoize a parse in its ref's runtime and serve repeated reads as a pure table lookup instead of re-reading + re-parsing the file every call.

A source write bumps the written path's counter. A memoized reader keyed by the value get(path) returned when it parsed can then check, on every later call, whether the counter still matches — an integer compare, no vfs.read, no parse. It rebuilds only when the counter moved. Two write surfaces feed it, so the token reflects a change no matter where it came from:

  • vfs.write / vfs.move / vfs.remove bump SYNCHRONOUSLY, in the same call — so a script that writes a file and reads it back in the SAME tick sees the new content immediately (the asset-change onChange dispatch fires a frame LATER, too late for a same-tick read).
  • the generic asset-change dispatcher bumps on every source write it routes, including peer-synced and engine-originated writes that never pass through the Luau vfs.* surface — with the engine's normalized path, which is the canonical form a reader keys on. Counters are per PATH (not one global epoch): the per-frame dirty-entity writer churns scene-dirty paths every frame during play, and a global epoch would let that churn invalidate every unrelated cache. Per-path isolation means only a change to THE file a reader depends on rebuilds it. Per-VM. The map holds one small integer per distinct written source path.

Usage: local ContentVersion = require("@builtin/modules/content_version")

modules/ContentVersion/bump

bump(path: string)

Bump path's version counter, invalidating every reader memoized against its previous value. Called by the vfs.* write surface and the asset-change dispatcher; content code rarely calls it directly.

modules/ContentVersion/get

get(path: string): number

The current version counter for path (0 if never written this VM). A memoized reader stores the value it saw when it parsed, and treats a later call as a cache hit exactly while get(path) still returns it.

modules/DataAssetTypeRef/README

DataAssetTypeRef

Per-instance methods on every AssetRef<data> — a configured value instance of a dataType contract. Instances carry values only; structure and behavior live on the contract.

modules/DataAssetTypeRef/contract

contract(self)

The dataType contract this instance is bound to.

modules/DataAssetTypeRef/data

data(self, wanted: string?)

Resolve this instance into a read-only typed value object: this instance's values.yaml with the contract's schema defaults applied, ref fields materialized (assetRef -> AssetRef, dataRef -> nested value object), and the bound contract chain's behavior methods reachable on it — the bound contract's own methods win over an ancestor's when both define the same name, and any explicit instance value wins over both. Errors loudly when the instance violates its contract, when wanted is given and the bound contract's chain doesn't include it, and when dataRef fields form a circular chain.

modules/DataAssetTypeRef/inspect

inspect(self): any

asset.inspect type-specific detail: { contract, valueKeys }, parsed from this instance's own values.yaml — the bound contract identity plus the sorted top-level keys of its values map. Never resolves the contract or materializes ref fields (that's :data()).

modules/DataAssetTypeRef/onCreate

onCreate(name: string, opts: { contract: string })

Create a data instance bound to a contract. Seeds values.yaml with the contract's declared defaults so a fresh instance is immediately valid wherever every required field has a default.

modules/DataAssetTypeRef/satisfies

satisfies(self, wanted: string): (boolean, string?)

Whether this instance's contract chain includes wanted — true for the bound contract itself and for any contract it extends.

modules/DataAssetTypeRef/satisfiesDetail

satisfiesDetail(self, wanted: string): { ok: boolean, code: string, reason: string? }

Chain-membership check that also reports WHY, as a stable code a caller can branch on without matching reason text.

modules/DataAssetTypeRef/validateInstance

validateInstance(self): (boolean, { DS.Violation })

Validate this instance's values against its contract's merged schema (missing required fields, constraint breaches, unknown fields, ref-field existence + contract compatibility).

modules/DataSchema/README

DataSchema

Pure schema engine for the typed-data system. A contract's schema.yaml declares fields + constraints; this module parses the decoded declaration, merges extends chains, applies defaults, and validates value tables. Pure Luau — no FFI, no VFS reads — callers decode the YAML themselves and inject asset/contract resolvers.

modules/DataSchema/applyDefaults

applyDefaults(merged: { [string]: FieldSpec }, values: { [string]: any }): { [string]: any }

Produce a NEW value table with every schema default filled in where values has no explicit entry — recursively: struct values gain their subfield defaults and array elements gain their item defaults, at every depth. Neither input is mutated.

modules/DataSchema/mergeChain

mergeChain(chain: { Schema? }): ({ [string]: FieldSpec }?, { string })

Merge an extends chain of parsed schemas into one field map. The chain is ordered ROOT PARENT FIRST, derived contract LAST. A child redeclaring a parent field is a problem — shared shape comes from the parent, per-child shape from new fields.

modules/DataSchema/parseSchema

parseSchema(raw: any): (Schema?, { string })

Parse a decoded schema.yaml table into a Schema. Returns (schema, problems) — schema is nil when any problem was found.

modules/DataSchema/validateValues

validateValues(

Validate a raw value table against a merged field map. Checks missing required fields (a field with a default is never missing), per-field constraints, unknown top-level fields, and ref fields via the injected resolvers.

modules/DataTypeAssetTypeRef/README

DataTypeAssetTypeRef

Per-instance methods exposed on every AssetRef<dataType> — the contract surface of the typed-data system: schema (extends chain merged), validation, defaults, chain navigation, instance listing.

modules/DataTypeAssetTypeRef/chain

chain(self)

The full extends chain as refs, root parent first, this contract last.

modules/DataTypeAssetTypeRef/defaults

defaults(self)

The default value table the merged schema declares.

modules/DataTypeAssetTypeRef/inspect

inspect(self): any

asset.inspect type-specific detail: { fields, extends }, the contract's OWN declared field schema (not the merged extends chain) — fields is { {name, type, required}, ... } sorted by name, parsed from this contract's own schema.yaml. extends is the parent contract identity, or nil for a root contract.

modules/DataTypeAssetTypeRef/instances

instances(self)

Every data instance bound to this contract (or to a contract that extends it). Scans the whole data-asset registry — a discovery surface for authoring and tooling, not a per-frame call. A broken instance (unreadable values.yaml, dangling contract binding) warns and is skipped, so one bad instance never aborts enumeration of the rest.

modules/DataTypeAssetTypeRef/parent

parent(self)

The parent contract ref (extends), or nil for a root contract.

modules/DataTypeAssetTypeRef/schema

schema(self)

The contract's merged field schema — its own fields plus every field inherited through the extends chain.

modules/DataTypeAssetTypeRef/validateValues

validateValues(self, values: { [string]: any })

Validate a value table against this contract's merged schema.

modules/Debris/README

Debris

Timed entity despawn — Roblox Debris:AddItem(instance, lifetime) analog. Wraps the internal __debris.* FFI with a typed Luau surface. Installed as the global debris via the prelude.

Collapses the common task.delay + entity.despawn pattern into one call. Use for transient entities — bullet tracers, hit FX, dropped pickups, tween-completion cleanups, ragdoll cleanup. Semantics (match Roblox):

  • debris.add(id, lifetime) queues a despawn after lifetime seconds. Default lifetime is 10 seconds.
  • Calling add twice on the same entity REPLACES the prior deadline.
  • If the entity is despawned through any other path the pending record is dropped silently — no error.
  • Pending entries survive world.save / world.load (saved lifetime is seconds-remaining, so timers resume from where they paused).

modules/Debris/add

add(id: any, lifetime: number?): DebrisHandle

Schedule the entity for despawn after lifetime seconds (default 10). Calling again on the same entity replaces the prior deadline. Negative or zero lifetime despawns immediately. Returns a handle for cancel(), or 0 if the entity id couldn't be resolved.

modules/Debris/cancel

cancel(handleOrId: any): boolean

Cancel a pending despawn. Accepts either a handle from debris.add or an entity id / proxy. Returns true if a pending record was actually removed.

modules/Debris/clear

clear(): boolean

Drop every pending entry. Used by the test suite to isolate cases — not part of the user-facing surface.

modules/Debris/count

count(): number

Number of currently pending debris entries — handy for diagnostics overlays.

modules/Debris/list

list(): { DebrisEntry }

Snapshot every pending entry as a flat array of {id, remainingSecs, handle} records. Order is not stable — don't rely on it.

modules/Debris/pending

pending(id: any): number?

Return the number of seconds remaining before the entity is despawned, or nil if it isn't scheduled.

modules/DockedAppLayout/README

require("@builtin/_templates.docked_app.docked_app_layout") -- DockedAppLayout

Z.shell.docked template — toolbar + body + status. Clone-and-edit starting point for editor-style demos. Worked references: spreadsheet.

Usage: local DockedAppLayout = require("@builtin/_templates.docked_app.docked_app_layout")

modules/DynamicAssetTypeRef/README

DynamicAssetTypeRef

Behaviour for the dynamicAsset asset type — a prompt-driven, self-regenerating 3D asset. Reference example for the asset-type onChange change-callback. Loaded lazily by modules/asset_ref.

modules/EcsAudioListenerSpec/README

EcsAudioListenerSpec

modules/EcsAudioSourceSpec/README

EcsAudioSourceSpec

modules/EcsCameraSpec/README

EcsCameraSpec

modules/EcsColliderSpec/README

EcsColliderSpec

modules/EcsCollisionGroupsSpec/README

EcsCollisionGroupsSpec

modules/EcsComponentSpecs/README

EcsComponentSpecs

modules/EcsGaussianSplatRefSpec/README

EcsGaussianSplatRefSpec

modules/EcsHandle/README

EcsHandle

modules/EcsLightSpec/README

EcsLightSpec

modules/EcsMaterialSpec/README

EcsMaterialSpec

modules/EcsMeshSpec/README

EcsMeshSpec

modules/EcsPhysicsJointSpec/README

EcsPhysicsJointSpec

modules/EcsPhysicsSpec/README

EcsPhysicsSpec

modules/EcsPlan/README

EcsPlan

modules/EcsPlayerOwnedSpec/README

EcsPlayerOwnedSpec

modules/EcsRetargetProfileSpec/README

EcsRetargetProfileSpec

modules/EcsSkeletonSpec/README

EcsSkeletonSpec

modules/EcsSkySpec/README

EcsSkySpec

modules/EcsTessellationSpec/README

EcsTessellationSpec

modules/EcsTransformConstraintsSpec/README

EcsTransformConstraintsSpec

modules/EcsTransformSpec/README

EcsTransformSpec

modules/EcsWheelColliderSpec/README

EcsWheelColliderSpec

modules/EditorPanelAssetTypeRef/README

EditorPanelAssetTypeRef

Per-instance methods + the onRegister lifecycle hook for every AssetRef<editorPanel>. Loaded lazily by asset_ref.module.

modules/EditorPanelAssetTypeRef/getInitScript

getInitScript(self): string?

Read the instance's init.luau source as raw text (inspectors).

modules/EditorPanelAssetTypeRef/loadSpecMethod

loadSpecMethod(self): any

Read this instance's panel spec (the table its init.luau returns).

modules/Engine/README

require("@builtin/modules/engine") -- Engine (also available as global 'engine')

Process-level engine state surface. Read-only boot profile and read-write engine mode. Both exposed as computed properties via metatable __index / __newindex — never call them as methods. Usage: if engine.profile == "editor" then ... end engine.mode = "play" Implemented as a thin Luau wrapper over internal FFI: engine.profile → __engineSettings.get("profile") engine.mode → __mode.get() / __mode.set(value) engine.paused → __pause.get() / __pause.set(value) engine.timeScale → __timescale.get() / __timescale.set(value) engine.gameplayReady → __gameplay.ready() engine.mode = X is load-bearing — the setter owns the mode-flip side effects so any path that changes mode behaves identically: edit → play: drain pending edit-mode marks into the dirty overlay (so wld.edit() can reassemble the user's authored state) BEFORE flipping __mode. Never writes canonical scene.json. play → edit: flip __mode first, then reload the active non-additive layer so the dirty overlay reapplies + play-mode runtime mutations are discarded. Toolbox wrappers (wld.play() / wld.edit()) are thin pass-throughs — they MUST NOT carry side-effect logic. If a future caller writes engine.mode = "play" directly (or via a different toolbox tool), the same side effects fire. Putting drain/reload on the toolbox alone would let a direct assignment leave the scene in a half-applied state. See man engine for the full surface and side effects.

Usage: local Engine = require("@builtin/modules/engine") Also available as global: engine

modules/Engine/_fireWorldLoaded

_fireWorldLoaded()

INTERNAL. Mark the world fully loaded and fan out to every onWorldLoaded subscriber. Called by the builtin world-entrypoint loader once onWorldLoad has returned. Idempotent per load — re-fires on a genuine reload (mirrors a scene's onReady), so the flag is set true and subscribers run on each call.

modules/Engine/_resetWorldLoaded

_resetWorldLoaded()

INTERNAL. Clear the world-loaded latch on unbind/unload so a subsequent bind re-fires onWorldLoaded for the new world.

modules/Engine/offWorldLoaded

offWorldLoaded(id: number): boolean

Remove an onWorldLoaded subscriber by its watcher id.

modules/Engine/onModeChange

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.

modules/Engine/onPauseChange

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.

modules/Engine/onWorldLoaded

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.

modules/Engine/onWorldReady

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.

modules/Engine/onWorldUnloading

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.

modules/FontAssetTypeRef/README

FontAssetTypeRef

Hooks for .font assets. onCreate is the type's contribution to the generic asset.create("font", name, opts) flow (mirroring texture.assetType): it parses the font file ONCE into the baked, vectorized glyph format (data.zfnt) — the asset payload. onRegister loads that baked format via the engine font.register(name, zfnt) primitive — a font is a general CPU resource, so one registration makes fontFamily = "<name>" resolve on the egui UI text surface AND on text.* 2D/3D rendering, and feeds font.glyph / font.textMesh for true 3D text.

modules/FontAssetTypeRef/onCreate

onCreate(name: string, opts: CreateOpts): { [string]: string }

The .font type's contribution to asset.create("font", name, opts). Parses the font file ONCE into the baked vectorized glyph format and stores it as data.zfnt. The original font bytes are embedded inside the baked payload, so no separate font file is kept. The heavy parse happens here, at author time — onRegister then loads the result cheaply.

modules/FontAssetTypeRef/onRegister

onRegister(self)

Install this .font instance into the engine's text systems. Reads the instance's baked data.zfnt and calls font.register(name, zfnt), which loads the vectorized glyph data into the runtime store (for font.glyph / font.textMesh) and feeds the embedded font bytes to the 2D/3D text and egui UI systems. Falls back to a raw font file for instances authored before the baked layout (font.register reparses, with a slow-path warn). Fired once per instance by the asset system (live on asset.create, and in the world-load sweep). Guarded so a single bad font never errors the sweep.

modules/GuideAssetTypeRef/README

GuideAssetTypeRef

Per-instance methods exposed on every AssetRef<guide>. Loaded lazily by asset_ref.module.

modules/GuideAssetTypeRef/getGuide

getGuide(self): string?

Read the guide's guide.md body.

modules/GuideAssetTypeRef/getReadme

getReadme(self): string?

Read the guide's README.md body.

modules/GuideAssetTypeRef/inspect

inspect(self): any

asset.inspect type-specific detail: { headings, wordCount }, parsed from this guide's own guide.md. A guide with no readable guide.md returns an empty headings list and zero wordCount rather than erroring.

modules/ImporterAssetTypeRef/README

ImporterAssetTypeRef

Per-instance methods exposed on every AssetRef<importer>. Loaded lazily by asset_ref.module.

modules/ImporterAssetTypeRef/getInitScript

getInitScript(self): string?

Read the importer's entry script as raw text.

modules/ImporterAssetTypeRef/getReadme

getReadme(self): string?

Read the importer's README.

modules/ImporterAssetTypeRef/run

run(self, payload: any): any

Invoke the importer with a payload (e.g. a path to the file to import). Loads the importer module on demand and calls its primary exported function. Raises a Luau error tagged with the importer's identity on load failure / missing entry.

modules/ImporterShared/README

require("@builtin/@builtin::assetTypes.importer.shared") -- ImporterShared

The importer SYSTEM — shared by every .importer/ instance. Owns dispatch, origin-gating, content-gating, the asset.containing bundle skip, re-entrancy, output marking, and bundle relocation. The engine emits one generic "a source path was written" signal per write (onLooseWrite); this module decides whether any registered importer should claim it and runs the winner. Individual importers implement ONLY canImport(path, bytes) + import(ctx) — never trigger logic.

Usage: local ImporterShared = require("@builtin/@builtin::assetTypes.importer.shared")

modules/ImporterShared/awaitJob

awaitJob(path: string, timeoutSecs: number?): ImportJob?

Wait until the latest job for path reaches a terminal state (imported / failed / unclaimed / skipped — NOT the in-flight "queued" or "running"), or the timeout elapses. Returns the job (nil when no job exists for the path at all).

modules/ImporterShared/cancelAllQueued

cancelAllQueued(): number

Cancel EVERY queued source that hasn't started importing yet (in-flight imports are left to finish). Use it to abandon a large accidental drop.

modules/ImporterShared/cancelQueued

cancelQueued(path: string): boolean

Cancel a QUEUED source before it runs: drop it from the pump's queue and settle its job as "cancelled". A source already importing is mid-parse and can't be unwound cleanly, so only queued items cancel — returns false for a path that is already running, terminal, or was never queued.

modules/ImporterShared/explain

explain(path: string): { [string]: any }

Dry-run the dispatch gates for a path WITHOUT importing: does the file exist, which importers claim it, and which gate (if any) would stop an import right now. The answer to "I wrote this file and nothing happened".

modules/ImporterShared/importedAssets

importedAssets(): { { [string]: any } }

Every asset in the world carrying import provenance, newest at first. Each row: { asset (path), importer (guid), source ({guid,path}), iteration, at }. Backed by the asset.list presence filter — one cross-type query.

modules/ImporterShared/job

job(path: string): ImportJob?

The latest import job recorded for an exact source path, or nil when no dispatch has reached a claimant for it.

modules/ImporterShared/jobs

jobs(filter: { state: string?, path: string?, limit: number? }?): { ImportJob }

Recent import jobs, newest first. Pass a filter to narrow: state keeps one state, path substring-matches the source path, limit caps the count (default 25).

modules/ImporterShared/listImporters

listImporters(): { { name: string, identity: string } }

Every registered importer: { name, identity } per .importer asset.

modules/ImporterShared/onLooseWrite

onLooseWrite(path: string, origin: string?)

Engine-driven: a source path with no enclosing typed-asset folder was written. Gate on origin + re-entrancy, then ENQUEUE it on the bounded import queue (default concurrency 1) — the pump reads the bytes, decides whether a registered importer claims it, applies the content + containment gates, and runs the winner, one import at a time.

modules/ImporterShared/queue

queue(): { ImportJob }

The live import backlog: queued sources (still waiting behind the concurrency cap, in FIFO order) followed by the ones importing right now. The focused "what is the importer doing this instant" view, distinct from jobs (the full recent-dispatch log including terminal states).

modules/ImporterShared/reimportAsset

reimportAsset(assetPath: string): string?

Reimport a produced asset in place: resolve its source and re-run the importers on it (the containment gate regenerates the container's derived assets from the retained source). Returns the produced path, or nil when no source could be resolved.

modules/ImporterShared/resolveImportSource

resolveImportSource(assetPath: string): string?

Resolve the source file a produced asset was imported from. Prefers the provenance source guid (survives rename/move), falls back to the recorded path, then to scanning the container for an importer-claimed file.

modules/ImporterShared/runImporters

runImporters(path: string, forcedContainer: string?): string?

Run the importers on path NOW and return the produced asset path (a .bundle / .texture / .audio / …), or nil if no importer claims it. The manual, deterministic counterpart to the engine's loose-write dispatch: forced (no origin, content, or containment gate) and synchronous — it runs in the calling task and returns only when the import is complete, so a caller can write a raw source quietly (vfs.write(path, bytes, { quiet = true })) and then import it deterministically instead of racing the watcher. A source that already lives inside its own output container re-imports in place, regenerating the container's derived assets from the retained source.

modules/ImporterShared/runTargets

runTargets(target: any, opts: { recursive: boolean?, mode: string? }?): { [string]: any }

Import/reimport one target, an array of targets, or a folder. A produced asset (with provenance) reimports in place; a loose source imports; a folder is scanned (recursive by default) and its imported assets reimported + loose sources imported, filtered by opts.mode ("all" | "new" | "existing").

modules/ImporterShared/stats

stats(): { queued: number, running: number, states: { [string]: number } }

Live queue counters: queued (sources waiting on the pump — the authoritative backlog depth), running (imports in flight), and states (every recorded job tallied by state). queued and running are exact; states tallies the job registry, which keeps every in-flight job plus the most recent settled records.

modules/InputMacroAssetTypeRef/README

InputMacroAssetTypeRef

Per-instance methods exposed on every AssetRef<inputMacro>. Loaded lazily by asset_ref.module.

modules/InputMacroAssetTypeRef/getEvents

getEvents(self): { any }?

Parse events.json into a Lua table.

modules/InputMacroAssetTypeRef/getEventsRaw

getEventsRaw(self): string?

Read the macro's events.json body as raw JSON text.

modules/InputMacroAssetTypeRef/length

length(self): number

Number of recorded events.

modules/InputMacroAssetTypeRef/replay

replay(self): number

Replay the macro by handing its recorded events to the sim toolbox's macro tool, which dispatches each timed input event through the engine input surface. Returns the number of events queued for replay.

modules/InputMapAssetTypeRef/README

InputMapAssetTypeRef

modules/InputMapAssetTypeRef/onCreate

onCreate(name: string, opts: CreateOpts): { [string]: string }

Generic-creation hook for asset.create("inputMap", name, opts). With opts.record, serializes it to a standalone init.luau that returns the same record — the "play it, bake it, tweak it" on-ramp Zin.map.bake drives. A binding that fails to serialize (an unknown kind, or an axis/vector composite whose nested arm fails — the whole composite drops) is dropped with an -- UNSERIALIZED kind: <kind> (<name>) comment line; a function-valued axis gate / curve is dropped with a -- <name>.gate omitted / -- <name>.curve omitted comment line. With no opts.record, contributes nothing on top of the type's template/ skeleton.

modules/InspectorAppLayout/README

require("@builtin/_templates.inspector_app.inspector_app_layout") -- InspectorAppLayout

Z.shell.inspector template — sample fields covering each auto-detected editor type.

Usage: local InspectorAppLayout = require("@builtin/_templates.inspector_app.inspector_app_layout")

modules/LightmapDataAssetTypeRef/README

LightmapDataAssetTypeRef

Per-instance methods on every AssetRef<lightmapData> — the baked-lighting container the bake flow writes and component awakes read. Entries are keyed (lightmaps by entity id, probe fields by field id); each entry pairs a manifest record with a raw f32 payload file inside the container.

modules/LightmapDataAssetTypeRef/beginBatch

beginBatch(self)

Hold the manifest open across a run of writes. Each setLightmap / setProbeField still writes its payload file as it goes, but the manifest is read once here and written once by commitBatch, instead of being decoded and re-encoded per entry. A bake storing many entries into one container is the case this exists for. Re-entrant calls are ignored, and a batch left open by an error is closed by the next commitBatch.

modules/LightmapDataAssetTypeRef/clearEntries

clearEntries(self)

Remove every entry and payload, leaving an empty manifest — the container-wide teardown baking.clear uses for a full-scene clear.

modules/LightmapDataAssetTypeRef/commitBatch

commitBatch(self)

Write the manifest a beginBatch has been holding and close the batch. No-op when no batch is open.

modules/LightmapDataAssetTypeRef/lightmap

lightmap(self, key: string): (LightmapEntry?, { number }?)

Read one entity's baked lightmap.

modules/LightmapDataAssetTypeRef/manifest

manifest(self)

The decoded manifest: { version, lightmaps = { [entityId] = entry }, probeFields = { [fieldId] = entry } }.

modules/LightmapDataAssetTypeRef/onCreate

onCreate(name: string, opts: { scene: string? }?)

Create an empty baked-lighting container. The bake flow (baking.all / Lightmap.bake / VolumeProbe.bake) fills it.

modules/LightmapDataAssetTypeRef/probeField

probeField(self, key: string): (ProbeFieldEntry?, { number }?)

Read one probe volume's baked field.

modules/LightmapDataAssetTypeRef/removeLightmap

removeLightmap(self, key: string)

Remove one entity's lightmap entry and its payload file. No-op when the entry is absent.

modules/LightmapDataAssetTypeRef/removeProbeField

removeProbeField(self, key: string)

Remove one probe volume's field entry and its payload file. No-op when the entry is absent.

modules/LightmapDataAssetTypeRef/setLightmap

setLightmap(self, key: string, meta: { [string]: any }, texels: { number })

Store one entity's baked lightmap: writes the texel payload file and its manifest entry, replacing any prior entry under the same key.

modules/LightmapDataAssetTypeRef/setProbeField

setProbeField(self, key: string, meta: { [string]: any }, sh: { number })

Store one probe volume's baked field: writes the SH payload file and its manifest entry, replacing any prior entry under the same key.

modules/MaterialAssetTypeRef/README

MaterialAssetTypeRef

Per-instance methods exposed on every AssetRef<material>. Loaded lazily by asset_ref.module via require("@builtin::assetTypes.material.behavior") the first time a material ref is touched in a VM.

modules/MaterialAssetTypeRef/applyToEntity

applyToEntity(self, entityId: string): boolean

Apply this material to an entity by setting the material field on its Model / SkinnedModel component (where the renderer reads it). A material only renders where there is a mesh.

modules/MaterialAssetTypeRef/getDefinition

getDefinition(self): string?

Read the on-disk mat.yaml body as raw text. Use vfs.write(self.path .. "/mat.yaml", ...) to persist edits, or go through :setProperty / :setShader for in-memory updates that survive engine restart via their side-effects.

modules/MaterialAssetTypeRef/getShader

getShader(self): string?

Read the shader identity currently bound to this material — parses mat.yaml and returns the shader: field as a string. The result is whatever the YAML names (a built-in like "pbr", a guid, or a full identity like "@builtin::shaders.pbr"); pass it to asset.resolve(..., "shader") to get a ref.

modules/MaterialAssetTypeRef/handle

handle(self)

Materialise this material's GPU resource (Disk→CPU→GPU) and return its MaterialHandle. Resolves the shader, packs properties, uploads every referenced .texture to the GPU (via each slot's texRef:handle()), then files the material's GPU record (renderer.material.create). The handle is cached on the interned ref's shared runtime table — its presence IS "materialised on the GPU" — so every later call (and every Model/SkinnedModel binding this material) gets the SAME handle → ONE registration, with the material's textures GPU-resident BEFORE the mesh renders (no white→colour pop). Mirrors meshRef:handle() / texRef:handle(): a Model binds this handle's guid into its native MaterialRef; it never registers the material itself. The mode-flip runtime wipe clears the cache so a mode change re-materialises.

modules/MaterialAssetTypeRef/isRegistered

isRegistered(self): boolean

Whether this material exists as an asset — its mat.yaml is present. Writing the asset is what registers the material, so file presence IS the registration check.

modules/MaterialAssetTypeRef/onChange

onChange(ref, change)

Asset-type change callback: (re)register this material into the renderer's MaterialRegistry whenever its .material is seeded (a baked builtin at boot, or a world seed) or its mat.yaml is edited. This is the ONLY thing that populates the registry for a material — the legacy boot-time library loader (which registered every material by name) is gone; materials register through their own assetType exactly as shaders do (__shader.compile). Keyed by identity, guid as alias, so a MaterialRef.id (name / identity / guid) resolves to the right entry. Convergent + idempotent: see registerToGpu.

modules/MaterialAssetTypeRef/onCreate

onCreate(name: string, opts: CreateOpts): { [string]: string }

modules/MaterialAssetTypeRef/persistEdit

persistEdit(self)

Set one property on the material — updates the live cache + pushes the value to the GPU material (registry update + dirty). Frame-fast: no mat.yaml rewrite (persistence is lazy via saveDefinition). Shared across every entity using this material (interned ref).

modules/MaterialAssetTypeRef/preview

preview(self, opts: { [string]: any }?)

Render a preview of this material on a unit sphere.

modules/MaterialAssetTypeRef/saveDefinition

saveDefinition(self): boolean

Persist the material's live runtime overrides into its mat.yaml (the lazy/explicit flush). Property/texture sets are frame-fast and transient by default (they live on the ref's runtime, wiped on mode change); call this to bake the current values into the asset so they survive a restart. Routes through the mat.yaml write → RegisterMaterial path.

modules/MaterialAssetTypeRef/setProperties

setProperties(self, patch: { [string]: any }): number

Set many properties at once. Entries the material's shader doesn't expose are skipped (a generic patch table won't error on shader differences).

modules/MaterialAssetTypeRef/setShader

setShader(self, shaderRef: any): (boolean, any)

Switch this material to a different shader by rewriting the material asset itself (mat.yaml), then letting the asset-reload pipeline mark every instance dirty so the renderer repacks. The full mat.yaml is regenerated for the new shader's property vocabulary: current property values and texture links are carried across by canonical ROLE (via modules.material_remap), so MAIN_TEXalbedobase_color_texture and friends survive the swap to our best ability. Properties the new shader does not expose are dropped; the name, builtin, parent, description, and render: block are preserved verbatim. This modifies the on-disk asset (or, in play mode, its runtime copy) — not a throwaway registry entry — so the change persists and propagates to all instances.

modules/MaterialRemap/README

MaterialRemap

Canonical property/texture-role dictionary for cross-shader value preservation. When a material swaps shaders the new shader exposes a DIFFERENT vocabulary (one shader's MAIN_TEX is another's albedo is a third's base_color_texture). This module maps any known alias onto a canonical role so values carry across the swap "to our best ability".

modules/MaterialRemap/canonicalizeTextures

canonicalizeTextures(

Canonicalize texture-slot names so links survive a shader swap. Each known slot is rewritten to its canonical on-disk slot name; unknown slots pass through unchanged. (The engine does not yet expose a target shader's reflected texture-slot list, so canonicalizing to the builtin convention is the best-effort path — see material_remap module header.)

modules/MaterialRemap/propertyRole

propertyRole(name: string): string?

Canonical role for a scalar/color property name, or nil when unknown.

modules/MaterialRemap/remapProperties

remapProperties(

Remap a table of old property values onto a target shader's accepted property names. Direct name matches win; otherwise the old key's canonical role is matched against the role of each target name. Values whose role the target shader does not expose are dropped (best-effort preservation).

modules/MaterialRemap/textureRole

textureRole(slot: string): string?

Canonical texture-slot role (and on-disk slot name) for a texture slot name, or nil when unknown.

modules/MeshAssetTypeBehavior/README

MeshAssetTypeBehavior

Behaviour for the mesh asset type — the disk + CPU side of the disk-asset ↔ GPU-mesh split. onCreate writes the engine-native ZMSH geometry payload as the container's data.zmsh primary; :load() decodes it into the CPU store and returns a CPU handle. Both go through the public renderer.mesh.* API — this behaviour calls no __ FFI directly.

modules/MeshAssetTypeBehavior/getVertices

getVertices(self)

Read this mesh's vertices — one entry per vertex, { pos = {x,y,z}, normal = {x,y,z}, uv = {u,v} }. Requires the CPU copy to be resident: turn on keepCpu (meshRef:setSettings({ keepCpu = true })) for an in-use mesh. Errors loudly — naming the exact reason — rather than silently loading a transient copy.

modules/MeshAssetTypeBehavior/handle

handle(self)

modules/MeshAssetTypeBehavior/load

load(self)

Load this .mesh asset's geometry into the guid-keyed CPU store and return a CPU handle (the Disk→CPU step). The handle carries the guid, vertex / index counts, and per-handle geometry ops (getTriangles / getVertices / getBounds / encode / unload) that read the Rust-side store — it holds no geometry itself. Upload to the GPU with renderer.mesh.create(handle); the DEFAULT is to handle:unload() right after. Delegates to renderer.mesh.loadCpu (the sole __ caller).

modules/MeshAssetTypeBehavior/onChange

onChange(ref: any, change: { [string]: any })

Reconcile the mesh's second (lightmap) UV set to its lightmapUvs setting when the asset's .metadata changes. "generate" (re)creates a non-overlapping unwrap into the second UV set, "none" strips it, "keep" leaves the stored geometry untouched. The re-cook round-trips the geometry through the codec, so tangents, skinning, and the skeleton are preserved. In play mode it returns immediately: a setting flip never rewrites the persisted geometry while the world is running (the play-lock); a resident mesh re-materialises on its next fetch.

modules/MeshAssetTypeBehavior/preview

preview(self, opts: { [string]: any }?)

Render a preview of this mesh, instantiated and framed. Drives the mesh's own model-instantiation path in an isolated preview scope.

modules/MeshAssetTypeBehavior/sceneInstantiate

sceneInstantiate(self, context: { [string]: any }?): (EntityRef, { [string]: string })

Scene-instantiation hook — the uniform contract the base AssetRef dispatches instantiateInto(context) to. A mesh conforms by spawning an entity carrying a Model that renders this mesh. With context.target the entity spawns as a child of that owner (so an owning Asset component tears it down with its other children); with no target it is a fresh root placed at context.position.

modules/MeshAssetTypeBehavior/setSettings

setSettings(self, patch: { [string]: any })

Write a partial settings patch to this mesh asset's .metadata. Pass any subset of the settings schema; only those keys change, the rest keep their stored value (asset.set_field deep-merges). Unknown keys error loudly. Settings are serializable and persist across reloads.

modules/MeshAssetTypeBehavior/setVertices

setVertices(self, positions)

Replace this mesh's vertex positions IN PLACE — indices, normals/uvs, and skinning are preserved, the AABB recomputes, and the edit shows on screen (the GPU re-fetches the changed CPU copy). Requires the CPU copy resident: turn on keepCpu (meshRef:setSettings({ keepCpu = true })) for an in-use mesh. Errors loudly — naming the reason — when the CPU copy isn't resident or the vertex count doesn't match.

modules/MeshAssetTypeBehavior/settings

settings(self): { [string]: any }

Read this mesh asset's settings, with every schema default filled in. The returned table always carries the full settings schema.

modules/ModuleAssetTypeRef/README

ModuleAssetTypeRef

Per-instance methods exposed on every AssetRef<module>. Loaded lazily by asset_ref.module.

modules/ModuleAssetTypeRef/getExports

getExports(self): { { name: string, type: string } }?

The module's exported names and their value types — requires the module and reflects over the table it returns. Available for every module (world-authored or library), since a module is just an asset.

modules/ModuleAssetTypeRef/getInitScript

getInitScript(self): string?

Read the module's entry script as raw text.

modules/ModuleAssetTypeRef/getReadme

getReadme(self): string?

Read the module's README body.

modules/ModuleAssetTypeRef/inspect

inspect(self): any

asset.inspect type-specific detail: { exports }, where exports is parsed from the module's own entry script (getInitScript) via luau_introspect.moduleExports — name/kind/signature/desc of every top-level export, read from the source text rather than a require() reflection. Cached on the asset's content checksum, so re-inspecting unchanged source is free. A module with no readable entry script returns an empty exports list rather than erroring.

modules/ModuleAssetTypeRef/loadModule

loadModule(self): any

Require the module by its canonical identity — same as require(self.identity), but pcall-wrapped so a load failure raises a Luau error tagged with the module identity rather than propagating the raw error.

modules/ModuleAssetTypeRef/onChange

onChange(ref, change)

Asset-type change callback: hot-reload this module whenever a .luau / .lua file inside it is edited (or the module is seeded). This is what live-reloads USER modules — library modules reload through the VFS write hook (author-immutable content does not dispatch onChange). Mirrors the .material / .shader assetTypes owning their own reload. Convergent: the reload only invalidates the require() cache + fires watchers and never writes back into the asset folder.

modules/ModuleAssetTypeRef/onRegister

onRegister(self)

Initial-registration callback: register every .luau / .lua file this module owns — its entry AND its plain-file submodules — into the require() layer, so require("<mod>.<sub>") resolves the instant it first registers (fired before the world entrypoint runs). A .luau inside a NESTED typed-asset folder (a nested .module / .component / …) belongs to that asset and registers through its own onRegister, so it is skipped here.

modules/PackageAssetTypeRef/README

PackageAssetTypeRef

Per-instance methods exposed on every AssetRef<package>. Loaded lazily by asset_ref.module.

modules/PackageAssetTypeRef/getDefinition

getDefinition(self): string?

Read the package's package.yaml body as raw text.

modules/PackageAssetTypeRef/getReadme

getReadme(self): string?

Read the package's README body.

modules/PackageAssetTypeRef/inspect

inspect(self): any

asset.inspect type-specific detail: { contents }, where contents is { {name, type}, ... } sorted by name — the package's own direct children (components, modules, tools, docs), enumerated via vfs.list against the package's own path. Never reads/executes any child's content.

modules/PackageAssetTypeRef/listContents

listContents(self): { { name: string, isDirectory: boolean } }

List the package's child entries — every VFS entry one level below the package root. Use this to enumerate components, modules, tools, etc. shipped by the package.

modules/Particles.Curves/ColorSequence.new

ColorSequence.new(a, b?) -> ColorSequence

Three shapes: constant ({r,g,b}), two-point lerp (startColor, endColor), or keypoint table ({ {time=, value={r,g,b,a?}, envelope={r,g,b}?}, ... }).

modules/Particles.Curves/ColorSequence:evaluate

ColorSequence:evaluate(t: number) -> (r, g, b, a)

Deterministic per-channel sample.

modules/Particles.Curves/ColorSequence:getKeypoints

ColorSequence:getKeypoints() -> {table}

Returns a fresh array of { time, value = {r,g,b,a}, envelope = {r,g,b} }.

modules/Particles.Curves/ColorSequence:pack

ColorSequence:pack(buffer: {number}, offset: number) -> number

Pack the color curve into a flat float array; returns the next-write offset.

modules/Particles.Curves/ColorSequence:sample

ColorSequence:sample(t: number, rng?: number) -> (r, g, b, a)

Sample with per-channel RGB envelope randomness (alpha is deterministic).

modules/Particles.Curves/NumberSequence.new

NumberSequence.new(a, b?) -> NumberSequence

Three shapes: constant (value), two-point lerp (start, end), or keypoint table ({ {time=, value=, envelope=}, ... }).

modules/Particles.Curves/NumberSequence:evaluate

NumberSequence:evaluate(t: number) -> number

Deterministic interpolation at life-fraction t in [0, 1].

modules/Particles.Curves/NumberSequence:getKeypoints

NumberSequence:getKeypoints() -> {table}

Returns a fresh array of { time, value, envelope } entries.

modules/Particles.Curves/NumberSequence:pack

NumberSequence:pack(buffer: {number}, offset: number) -> number

Pack the curve into a flat float array; returns the next-write offset.

modules/Particles.Curves/NumberSequence:sample

NumberSequence:sample(t: number, rng?: number) -> number

Envelope-randomized sample; rng (in [0, 1]) seeds the jitter.

modules/Particles.Curves/README

require("@builtin/systems/particles.package/curves") -- Particles.Curves

NumberSequence and ColorSequence — keyframe curves for VFX properties.

Animatable keyframe curves used by the particle system. Modelled on Roblox's NumberSequence / ColorSequence so the muscle memory carries over, with two differences:

  1. Up to 16 keypoints (Roblox caps at 20; we cap a bit lower so the GPU pack fits in a fixed-size param block).
  2. ColorSequence supports per-channel envelope (RGB) — Roblox does not. Curves are immutable plain tables with a metatable. Build once, sample many times. The shader-side pack format is a flat float array — see NumberSequence.pack / ColorSequence.pack for the contract. Usage: local size = NumberSequence.new({ { time = 0.0, value = 0.1 }, { time = 0.3, value = 1.0, envelope = 0.2 }, { time = 1.0, value = 0.0 }, }) print(size:evaluate(0.5)) -- deterministic interpolation print(size:sample(0.5)) -- envelope randomness applied local color = ColorSequence.new( { 1, 0.8, 0.2 }, -- yellow { 1, 0.1, 0.0 } -- red )

Usage: local Particles.Curves = require("@builtin/systems/particles.package/curves")

modules/Particles.Curves/handleKeypoints

handleKeypoints(v: any): any

Construct a NumberSequence. Three constructor shapes: NumberSequence.new(value) -- constant NumberSequence.new(startValue, endValue) -- two-point lerp NumberSequence.new({ {time=, value=, envelope=}, ... }) -- keypoints

modules/Particles.Curves/isColorSequence

isColorSequence(x: any) -> boolean

True if x was produced by ColorSequence.new(...).

modules/Particles.Curves/isNumberSequence

isNumberSequence(x: any) -> boolean

True if x was produced by NumberSequence.new(...).

modules/Particles.Curves/new

new(a: any, b: any?): any

modules/Particles.Meshes/README

require("@builtin/systems/particles.package/meshes") -- Particles.Meshes

Built-in source-mesh templates for mesh particles.

Source-mesh geometries for the mesh-particle path of the particle system. Each built-in returns a table with positions (flat xyz array), normals (flat xyz array), uvs (flat uv array), and indices (flat u32 array) — the shape particles.create expects under its mesh field. Built-in kinds: "cube" — 24 verts × 36 indices (face-normalled box) "octahedron" — 24 verts × 24 indices (face-normalled octahedron) "tetrahedron" — 12 verts × 12 indices (face-normalled tetrahedron) "plane" — 4 verts × 6 indices (axis-aligned XY quad) Custom geometry: Pass mesh = { positions = {...}, normals = {...}, uvs = {...}, indices = {...} } directly to particles.create. The module also accepts an asset ref (assetRef("identity", "mesh")) but resolution happens through the engine's asset system — the fast path is built-in kinds.

Usage: local Particles.Meshes = require("@builtin/systems/particles.package/meshes")

modules/Particles.Meshes/list

list() -> {string}

Returns the built-in mesh kind names.

modules/Particles.Meshes/resolve

resolve(spec: string | table) -> table

Resolve a mesh spec to { positions, normals, uvs, indices }. Spec is one of "cube" | "octahedron" | "tetrahedron" | "plane", or a custom { positions, normals, uvs?, indices } table.

modules/Particles.Shapes/README

require("@builtin/systems/particles.package/shapes") -- Particles.Shapes

Emission-shape samplers — CPU-side, called at spawn time.

Spawn positions and initial velocity directions for the standard emission shapes. CPU sampling is fine here because it runs at most N times per frame where N = emission rate, not N = active particle count — and the math is trivial. Shapes (matching Roblox naming): point — emit from origin box — Volume / Surface sphere — Volume / Surface, partial = hemisphere cap cylinder — Volume / Surface, axis = Y disc — Surface (XZ plane), partial = annulus inner radius cone — Surface, partial = half-angle Styles: "volume" — uniform inside the shape "surface" — uniform on the shape's boundary inOut (determines initial velocity direction): "outward" — surface-normal outward "inward" — surface-normal inward "inandout" — random sign per particle

Usage: local Particles.Shapes = require("@builtin/systems/particles.package/shapes")

modules/Particles.Shapes/frame

frame(ax: number, ay: number, az: number): (number, number, number, number, number, number, number, number, number)

Build an orthonormal frame whose middle axis is the given direction. Shape samplers emit around a local +Y axis; this frame maps those local samples into world space so emission can be aimed along any vector. Returns 9 numbers — right, axis, forward — for allocation-free use in per-particle loops: world = local.x * right + local.y * axis + local.z * forward. The roll about the axis is unspecified but stable for a given direction. A zero-length or +Y direction returns the exact identity frame.

modules/Particles.Shapes/sample

sample(shape: string, opts: table) -> (px, py, pz, dx, dy, dz)

Sample one spawn position + unit direction from the named shape. opts: { size = {x,y,z}, style = "volume"|"surface", inOut = "outward"|"inward"|"inandout", partial = number, spreadAngle = degrees }.

modules/Particles.Shapes/shapes

shapes() -> {string}

Returns the supported shape names — point, box, sphere, cylinder, disc, cone.

modules/Particles/ParticleSystem:clear

ParticleSystem:clear()

Kill every live particle by re-zeroing the state buffer.

modules/Particles/ParticleSystem:destroy

ParticleSystem:destroy()

Release every GPU buffer and the render entity. Idempotent.

modules/Particles/ParticleSystem:emit

ParticleSystem:emit(count: number)

One-shot burst of N particles, independent of rate / enabled.

modules/Particles/ParticleSystem:getActiveCount

ParticleSystem:getActiveCount() -> number

CPU-tracked estimate; slightly optimistic (no GPU readback).

modules/Particles/ParticleSystem:getMaxCount

ParticleSystem:getMaxCount() -> number

Buffer cap.

modules/Particles/ParticleSystem:getRenderEntity

ParticleSystem:getRenderEntity() -> string

Entity id of the renderable the system spawns at world origin.

modules/Particles/ParticleSystem:isPlaying

ParticleSystem:isPlaying() -> boolean

True while the timeline emits: enabled, past delay, and inside duration (or looping / untimed).

modules/Particles/ParticleSystem:play

ParticleSystem:play(restart?: boolean)

Start (or re-arm) the emission timeline: resets the clock to -delay, re-arms scheduled bursts, and enables emission. restart = true also clears live particles.

modules/Particles/ParticleSystem:setAcceleration

ParticleSystem:setAcceleration(x, y, z: number)

Set the constant acceleration vector (combined with gravity + wind).

modules/Particles/ParticleSystem:setBlendMode

ParticleSystem:setBlendMode(mode: string)

"alpha" (default, premultiplied) or "additive".

modules/Particles/ParticleSystem:setBursts

ParticleSystem:setBursts(bursts: table)

Replace the scheduled-burst list ({ {time, count}, ... }); nil/empty removes the schedule.

modules/Particles/ParticleSystem:setColor

ParticleSystem:setColor(value: ColorSequence | {number})

Per-particle color curve. A {number} array {r, g, b} is read as a constant color.

modules/Particles/ParticleSystem:setDirection

ParticleSystem:setDirection(x, y, z: number)

Aim emission: the shape's local +Y axis maps onto this world-space vector (positions and initial velocities both rotate). Pass nil to reset to the native +Y frame.

modules/Particles/ParticleSystem:setDrag

ParticleSystem:setDrag(d: number)

Set the velocity damping factor (per-second exponential decay).

modules/Particles/ParticleSystem:setEnabled

ParticleSystem:setEnabled(b: boolean)

Pause / resume continuous emission. Does not kill live particles.

modules/Particles/ParticleSystem:setGravity

ParticleSystem:setGravity(x, y, z: number)

Set the gravity vector (m/s^2). Default {0, -9.81, 0}.

modules/Particles/ParticleSystem:setLifetime

ParticleSystem:setLifetime(min: number, max?: number)

Per-particle lifetime range in seconds.

modules/Particles/ParticleSystem:setMaterial

ParticleSystem:setMaterial(materialName: string)

Replace the material applied to the render entity entirely.

modules/Particles/ParticleSystem:setOrientation

ParticleSystem:setOrientation(mode: string)

"FacingCamera" | "FacingCameraWorldUp" | "VelocityParallel" | "VelocityPerpendicular".

modules/Particles/ParticleSystem:setOrigin

ParticleSystem:setOrigin(x, y, z: number)

Move the emitter to a new world-space origin.

modules/Particles/ParticleSystem:setParam

ParticleSystem:setParam(name: string, value: number)

Generic setter — matches PARAM_LAYOUT keys or "user" for the user block.

modules/Particles/ParticleSystem:setRate

ParticleSystem:setRate(r: number)

Set continuous emission rate (particles/sec).

modules/Particles/ParticleSystem:setRotSpeed

ParticleSystem:setRotSpeed(min: number, max?: number)

Per-particle rotation-speed range (degrees/sec).

modules/Particles/ParticleSystem:setRotation

ParticleSystem:setRotation(min: number, max?: number)

Per-particle initial rotation range (degrees).

modules/Particles/ParticleSystem:setShape

ParticleSystem:setShape(opts: table)

Replace the emission shape — { kind, size, style, inOut, partial, spreadAngle } (any subset). Existing values are kept for omitted fields.

modules/Particles/ParticleSystem:setSize

ParticleSystem:setSize(value: NumberSequence | number | {number})

Size multiplier curve, sampled per particle every frame on the GPU. A {number} pair {start, end} is read as a two-stop ramp.

modules/Particles/ParticleSystem:setSpeed

ParticleSystem:setSpeed(min: number, max?: number)

Per-particle initial-speed range.

modules/Particles/ParticleSystem:setSquash

ParticleSystem:setSquash(value: NumberSequence | number)

X-dimension scale curve (>1 stretches, <1 squashes).

modules/Particles/ParticleSystem:setTexture

ParticleSystem:setTexture(texture: string)

Set the sprite texture (asset id or VFS path).

modules/Particles/ParticleSystem:setTransparency

ParticleSystem:setTransparency(value: NumberSequence | number | {number})

Transparency curve (0 = opaque, 1 = invisible). A {number} pair {start, end} is read as a two-stop ramp.

modules/Particles/ParticleSystem:setUserParam

ParticleSystem:setUserParam(index: number, value: number)

Write one slot (1..32) of the 32-float user param block. Custom WGSL update shaders read these at params[31 + index].

modules/Particles/ParticleSystem:setWind

ParticleSystem:setWind(x, y, z: number)

Set the wind vector (m/s).

modules/Particles/ParticleSystem:simulate

ParticleSystem:simulate(t: number, stepDt?: number)

Deterministically advance the system by t seconds in fixed steps (default 1/60). Drives the full update — timeline, bursts, compute passes — so a system can be prewarmed or scrubbed to a known state.

modules/Particles/ParticleSystem:stop

ParticleSystem:stop(clearParticles?: boolean)

Stop emitting. Live particles finish their lifetime unless clearParticles = true.

modules/Particles/ParticleSystem:update

ParticleSystem:update(dt: number)

Step the simulation. Drives continuous emission, dispatches the compute update pass, and re-packs per-frame uniforms. Call once per frame.

modules/Particles/README

require("@builtin/systems/particles.package/engine") -- Particles

Generic GPU-driven particle system — compute-shader simulation, zero-copy rendering.

A generic GPU particle substrate for VFX. Every active particle lives in a GPU storage buffer; the simulation runs entirely as compute dispatches; the per-frame vertex output is consumed directly by the renderer via renderer.mesh.create (no readback, no CPU re-upload). Spawning happens on the CPU (the bookkeeping cost is bounded by emission rate, not particle count) and writes new particle slots straight into the GPU state buffer. The module is one-stop: the high-level ParticleEmitter component declared elsewhere in src/lua/lib/components/ is a thin wrapper around this API. Usage: local particles = require("@builtin::systems.particles.engine") local fire = particles.create({ maxCount = 2000, rate = 100, -- particles/sec lifetime = { 0.8, 1.6 }, speed = { 2.5, 4.0 }, shape = { kind = "cone", size = {1, 1, 1}, partial = 0.3 }, gravity = { 0, 1.5, 0 }, -- buoyant drag = 0.6, size = NumberSequence.new({ { time = 0, value = 0.4 }, { time = 0.3, value = 1.0, envelope = 0.2 }, { time = 1, value = 0.0 }, }), color = ColorSequence.new({ 1, 0.9, 0.3 }, { 0.8, 0.1, 0 }), transparency = NumberSequence.new(0, 1), texture = "@builtin::textures.fire_sprite", blendMode = "additive", }) -- Each frame: fire:setOrigin(torch.position.x, torch.position.y, torch.position.z) fire:update(dt) See references/luau-cookbook.md#particles (zero-engine skill) for more recipes — explosion bursts, magic swirls, custom WGSL update injection.

Usage: local Particles = require("@builtin/systems/particles.package/engine")

modules/Particles/create

create(spec: table) -> ParticleSystem

Allocate a new particle system: GPU buffers, registered compute shader, renderer.mesh.create-wrapped renderable. See the about block for the full spec field list. Returns a handle whose methods are summarised below.

modules/Prelude/README

require("@builtin/modules/prelude") -- Prelude

Auto-require globals available in every script without require(). Injects Entity, Physics, Transform, and Material as global Luau wrappers.

This script runs once after FFI registration. It requires builtin library modules and exposes them as globals. Users never need to write: local Entity = require(".entity_reflect") Instead they just use Entity directly. Only high-frequency, universally-useful APIs belong here. Specialized modules (e.g. agent tools, editor UI) still use require(). Convention: __name = raw FFI binding (Rust native call, never used directly by scripts) Name = Luau wrapper (user-facing, auto-injected via this prelude)

Usage: local Prelude = require("@builtin/modules/prelude")

modules/PresetAssetTypeRef/README

PresetAssetTypeRef

Per-instance methods exposed on every AssetRef<preset>. Loaded lazily by asset_ref.module.

modules/PresetAssetTypeRef/applyTo

applyTo(self, entityId: string, componentType: string, overrides: { [string]: any }?): boolean

Apply this preset to a component on an entity. Looks up the component by componentType on entityId, then writes each preset property onto the component's public table. Errors cleanly when the entity has no component of that type. Returns true on success.

modules/PresetAssetTypeRef/getDefinition

getDefinition(self): string?

Read the preset's preset.yaml body as raw text.

modules/PresetAssetTypeRef/inspect

inspect(self): any

asset.inspect type-specific detail: { componentType, fieldValues }, parsed from the preset's own preset.yamlcomponentType is the component: field, fieldValues is the decoded properties table (preset.load with no overrides). Cached on the asset's content checksum, so re-inspecting unchanged source is free. A preset with no readable preset.yaml returns an empty detail rather than erroring.

modules/PresetAssetTypeRef/load_preset

load_preset(self, overrides: { [string]: any }?): { [string]: any }?

Load the preset's properties as a plain Lua table, optionally merging caller-supplied overrides on top. Equivalent to preset.load(self.identity, overrides).

modules/Preview/README

Preview

Compose an asset's own instantiate path into a rendered still preview. Each previewable assetType's M.ref.preview calls one of these primitives; they spawn temporary entities, auto-frame an offscreen camera over them, render one frame, read it back as a PNG, and tear down. Scheduled previews drain through one serial work queue whose drainer renders in shared batches — subjects settle together and capture concurrently — so a world-scale backfill completes in seconds per hundred assets while the engine stays responsive.

modules/Preview/fromEntities

fromEntities(makeFn: () -> any, opts: { [string]: any }?): PreviewResult

Instantiate entities via makeFn, auto-frame an offscreen camera to their bounds, render one frame, and return the still PNG (base64) + stats. Spawned entities are marked temporary and despawned after the render. Renders are serialised on the shared preview rig; renders requested while the preview queue is draining a batch join its shared pass, so each still shows only its own subject at single-render wall-clock.

modules/Preview/materialOnSphere

materialOnSphere(matRef: any, opts: { [string]: any }?): PreviewResult

Render a material on a unit sphere — the canonical material preview.

modules/Preview/queueStatus

queueStatus(): { [string]: any }

Report the preview work queue: how many renders are pending, the batch currently rendering, session totals, and the most recent failures.

modules/Preview/schedulePreview

schedulePreview(ref: any, opts: { [string]: any }?)

Queue a writePreview for ref on the preview work queue. The per-type generation hooks (material / texture / bundle onChange) call this on every content write; a burst of writes renders once, after the content settles, so the render sees the final bytes. Renders drain in shared batches in schedule order; poll queueStatus for progress.

modules/Preview/swatch

swatch(texRef: any, opts: { [string]: any }?): PreviewResult

Render a texture as a flat plane facing the camera — its swatch.

modules/Preview/writePreview

writePreview(ref: any, opts: { [string]: any }?): (boolean, string?)

Render an asset's preview via its type's ref:preview() and persist it as preview.png inside the asset folder. The preview is the asset's visual description — it syncs and publishes like any other file, feeds image-based search, and gives browsers a thumbnail. Skips scratch content (/source/tmp/). Writes only when the rendered bytes differ from the existing file, so re-rendering an unchanged asset produces no sync traffic.

modules/ProcGraphAssetTypeBehavior/README

ProcGraphAssetTypeBehavior

Per-instance methods exposed on every AssetRef<procGraph> — a .procGraph asset is a serialized procedural graph (proc.graph.v1). The methods below let callers use the graph THROUGH its ref (ref:eval("output:mesh"), ref:inputs(), ref:asOp()) instead of round-tripping through the proc module + a path string. asOp is also how the op registry resolves a .procGraph referenced as a graph node: the type owns "how a graph becomes an op", the evaluator's registry delegates to it. Loaded lazily by asset_ref.module via require("@builtin::systems.procgen.procGraph.behavior") the first time a procGraph ref is touched in a VM.

modules/ProcGraphAssetTypeBehavior/asOp

asOp(self): any

Wrap this graph as an op def so it can be used as a NODE in another graph: its params mirror this graph's declared inputs, its outputs mirror this graph's declared outputs, and its version folds this graph's structure with every content op it references (so an edit anywhere beneath re-keys the wrapping node). The op registry resolves a graph node whose op is a .procGraph ref through this method — the type owns the graph-as-op mapping; the evaluator's wrapGraphAsOp is the library that does the heavy folding.

modules/ProcGraphAssetTypeBehavior/eval

eval(self, target: string, opts: any?): any

Evaluate one output of this graph and return the produced value (Geometry / InstanceSet / Texture / Material / Bundle / …). target is an output name ("mesh"), or an explicit "output:<name>" / "node:<id>[:<out>]" selector. opts forwards to proc.eval ({ inputs = { … }, seed = N }) so the graph's exposed inputs can be overridden per call.

modules/ProcGraphAssetTypeBehavior/graph

graph(self): any

Load this asset's procedural graph (proc.graph.v1) into a live Graph. Reads graph.json fresh each call — the Generator that drives a graph owns its own cached copy, so the type stays a pure loader.

modules/ProcGraphAssetTypeBehavior/inputs

inputs(self): { [string]: any }

The graph's declared inputs — the parameters that drive it, each with its procgen type and default. This is the vocabulary a Generator exposes as editable overrides.

modules/ProcGraphAssetTypeBehavior/outputs

outputs(self): { string }

The graph's declared output names (sorted). Each is a value the graph produces and a valid eval target.

modules/ProcGraphAssetTypeBehavior/validate

validate(self): { any }

Type-check this graph's connections. Returns proc.validate's diagnostic list (empty when the graph is well-typed) — each entry is { level, code, node, input, expected, actual, suggestions }.

modules/ProcNodeAssetTypeBehavior/README

ProcNodeAssetTypeBehavior

Registration lifecycle for .procNode assets — custom procedural operations authored as content. Each instance's init.luau returns a def table that this behaviour push-registers into the proc op registry, keyed by the asset's stable GUID. Graphs reference these nodes by a typed GUID ref (never by name), so resolution is push-model with no dynamic name probing.

modules/ProcNodeAssetTypeBehavior/onChange

onChange(ref, _change)

Change callback: re-register + hot-reload this node whenever its .procNode is seeded or its entry script is edited. The node is re-registered from its current entry source; the version bump propagates the edit to every graph that reaches the node.

modules/ProcNodeAssetTypeBehavior/onDelete

onDelete(ref)

Delete callback: unregister this node's op when its .procNode folder is removed. The delete ref carries no guid (the folder is already gone), so the guid is resolved from the removed folder path via the registry's path->guid index. A graph referencing it afterward errors loudly at eval — resolution is push-model, never a silent fallback.

modules/ProcNodeAssetTypeBehavior/onRegister

onRegister(self)

Initial-registration callback: register this node's op the moment the instance first registers, so a graph loaded in the same world-ready sweep can resolve it. Idempotent with onChange.

modules/Queue/README

require("@builtin/modules/queue") -- Queue (also available as global 'queue')

Defer FFI mutations across frames — queue(fn) -> (ok, err, drainPromise).

Inside the body, write FFI calls (entity.spawn, component.add, position.set, etc.) enqueue rather than execute synchronously. The engine's per-frame drainer applies them in FIFO order with a per-frame budget. Returns (true, nil, drainPromise) on success, (false, err, nil) if the body throws — in which case the partial batch is cleared so it does not later apply. drainPromise is a promise handle that resolves once every mutation queued inside fn has been applied by the engine drainer. await(drainPromise) blocks the calling coroutine until full drain — use this to gate post-queue work (scene/layer load fires onLoad after the drain promise resolves). If nothing was queued (e.g. an empty fn), the promise resolves before queue() returns. Nested queue() is a no-op: the inner call just runs in the already-active queue scope. The depth counter is re-entrant. See docs/plans/2026-05-06-luau-queue-deferred-mutations.md for the full design (drain budget, error semantics, future per-key fences for reads).

Usage: local Queue = require("@builtin/modules/queue") Also available as global: queue

modules/RenderError/README

RenderError

Shared "this render is broken" marker. visibleError(entityId) swaps an entity to the builtin ERROR text model + error material, so a failed render reads as an unmistakable 3D "ERROR" sign on screen instead of empty pixels — empty silently reads as "fine" and a broken object gets mistaken for working. Every render component (built-in Model / SkinnedModel, or a user-authored one) calls this, so the error model is defined in ONE place: change it here and every render component updates.

modules/RenderError/visibleError

visibleError(entityId: string, reason: string?): boolean

Swap entityId to the builtin ERROR marker so a broken render is unmistakable on screen. Un-skins the entity first (a bad skeleton can't collapse the marker), binds the ERROR mesh + error material via the public ecs.* API, and logs the reason at error level. Returns true if the marker was applied.

modules/RenderFeatureAssetTypeRef/README

RenderFeatureAssetTypeRef

Per-instance methods exposed on every AssetRef<renderFeature>. Loaded lazily by asset_ref.module the first time a renderFeature ref is touched in a VM. A render feature is an init.luau exporting { setup?(ctx), render(ctx), teardown?(ctx) }; :enable() instantiates it (the engine begins calling its render(ctx) hook every frame) and returns a live handle, also visible under /zero/runtime/renderFeatures/.

modules/RenderFeatureShared/README

RenderFeatureShared

Shared helpers + the authoring contract for renderFeature modules.

modules/ReportFormatter/README

require("@builtin/systems/worldValidation.package/reportFormatter") -- ReportFormatter

Filtering + formatting helpers for the world validation report. Pure functions: every call returns a new value, never mutates the input report.

Three exports: filter (re-filter an existing Report without re-scanning the VFS), format (render to a string in one of four shapes), and summary (one-line health line). All three are pure — they never mutate the input report.

Usage: local ReportFormatter = require("@builtin/systems/worldValidation.package/reportFormatter")

modules/ReportFormatter/filter

filter(report, opts)

Return a new Report containing only problems that match the filter options. Counts are recomputed from the filtered set so the caller can trust counts against the visible problems list. Pure — the input report is not mutated.

modules/ReportFormatter/format

format(report, format)

Render a Report into a string in the chosen format.

modules/ReportFormatter/summary

summary(report)

Compact one-line health summary string — world: NE/NW libraries: NE/NW total: OK|FAIL.

modules/RigAssetTypeBehavior/README

RigAssetTypeBehavior

Behaviour for the rig asset type — a skeleton's disk shape. The payload is rig.json (a readable JSON document: ordered bones with names, parent hierarchy, the node index each maps to, rest local transform, and inverse-bind matrix, plus the retarget profile (role -> bone) and the humanoid classification). A rig is its own primitive: a skinned mesh references a rig, an animation references its source rig, and retargeting maps one rig onto another. The format is text, not binary, so an agent can open it and fix a mis-derived profile or a bad bone parent with an edit. Importers call asset.create("rig", name, { json }) to mint a rig.

modules/SceneAssetTypeRef/README

SceneAssetTypeRef

Per-instance methods exposed on every AssetRef<scene>. Loaded lazily by asset_ref.module.

modules/SceneAssetTypeRef/getEntrypoint

getEntrypoint(self): string?

Read the scene's entrypoint.luau body as raw text.

modules/SceneAssetTypeRef/getSceneJson

getSceneJson(self): { [string]: any }?

Parse the scene's scene.json into a Lua table.

modules/SceneAssetTypeRef/getSceneJsonRaw

getSceneJsonRaw(self): string?

Read the scene's scene.json body as raw JSON text.

modules/SceneAssetTypeRef/inspect

inspect(self): any

asset.inspect type-specific detail: { entityCount, entityNames, player }, parsed from the scene's own scene.json — never executes entrypoint.luau. A scene whose scene.json can't be read/parsed returns an empty detail rather than erroring.

modules/SceneAssetTypeRef/listEntities

listEntities(self): { string }

List the entity names declared at the top level of the scene's JSON body. Best-effort; nested children are not flattened.

modules/SceneAssetTypeRef/load_scene

load_scene(self, opts: { [string]: any }?): any

Load this scene into the root ("main") slot. Equivalent to layers.load(self) — the AssetRef envelope is passed straight through, so the loader threads scene identity by guid. Pass opts for additive overlays / persistence / origin offset (same shape as layers.load's second argument).

modules/SceneAssetTypeRef/onCreate

onCreate(_name: string, _opts: { [string]: any }?): { [string]: string }

Generic-creation hook for asset.create("scene", name). A new scene is the type's template shape verbatim.

modules/SceneAssetTypeRef/validate

validate(self): { { code: string, severity: string, message: string } }

Semantic content validation for a scene: the player-setup rule set run over this scene's scene.json, surfaced through asset.validate, plus a warning when the scene has no explicit sky entity.

modules/ScriptValidator/README

require("@builtin/systems/worldValidation.package/scriptValidator") -- ScriptValidator

Per-file Luau / Lua script validator. Wraps lsp.check to return real parser + type-check diagnostics for a single script, on top of cheap textual sanity checks (read failure, empty file).

Each script gets the full LSP pass — lsp.check(path, opts) from the engine's embedded Luau LSP. Severity, line, column, code, and message are taken straight from the LSP and re-wrapped in the validator's Problem shape. The LSP diagnostics carry stable codes (unknown-global, type-error, parse-error, …) — we forward them verbatim so callers can severity = "error" or code = "parse-error" and get exactly the rows they expect. Two cheap textual checks run BEFORE the LSP is invoked:

  1. script.read_failed (error) — vfs.read returned nil (file deleted between scan and read, or unreadable).
  2. script.empty (warning) — zero non-whitespace content. These don't duplicate anything the LSP produces — the LSP is skipped when read fails (no source to feed it) and the empty check is informational about the file rather than the code.

Usage: local ScriptValidator = require("@builtin/systems/worldValidation.package/scriptValidator")

modules/ScriptValidator/validate

validate(script, opts)

Validate a single script. Combines two cheap textual checks (script.read_failed, script.empty) with the embedded Luau LSP's full diagnostic pass via lsp.check(path).

modules/ScriptValidator/validateBatch

validateBatch(scripts, opts)

Validate a batch of scripts and flatten the per-script problem lists into one array.

modules/ServiceAssetTypeRef/README

ServiceAssetTypeRef

Per-instance methods exposed on every AssetRef<service>. Loaded lazily by asset_ref.module. These act on the SPECIFIC service the reference points to — asset.resolve("mesh_gen","service"):invoke{...} runs THAT service.

modules/ServiceAssetTypeRef/balance

balance(self): (number?, string?)

Read the caller's current credit balance — the balance every service draws from. Returns (nil, errMsg) when not signed in.

modules/ServiceAssetTypeRef/cost

cost(self, operation: string?): number?

The declared credit cost of an operation (the provider's up-front estimate; the exact charge is returned on each generation). Defaults to the primary / only operation.

modules/ServiceAssetTypeRef/getDefinition

getDefinition(self): string?

Read this service's declaration source (init.luau).

modules/ServiceAssetTypeRef/getReadme

getReadme(self): string?

Read this service's README.md body.

modules/ServiceAssetTypeRef/info

info(self): { [string]: any }

A one-line summary of this service: its offering, what it produces, and the operations it offers.

modules/ServiceAssetTypeRef/inspect

inspect(self): any

asset.inspect type-specific detail: { methods, description }, where methods is this service's declared operations — the top-level keys of its operations = { ... } table, parsed from the declaration's own source text — and description is the declaration's top-level summary, also parsed from source. A service with no readable declaration returns an empty methods list rather than erroring.

modules/ServiceAssetTypeRef/invoke

invoke(self, input: { [string]: any }?): { [string]: any }

Run a generation for this service. Picks the operation from input.operation (or the service's only / primary operation), runs its declared pipeline as a background task, and returns immediately with a generation handle. Watch it with the services toolbox status tool (or the watch tool); handle.asset_path is where the finished asset lands. Consumes credits — check :cost() and :balance() first.

modules/ServiceAssetTypeRef/onCreate

onCreate(name: string, opts: { [string]: any }): { [string]: string }

Generic-creation hook for asset.create("service", name, opts). Returns the scaffold content files for a new service declaration.

modules/ServiceAssetTypeRef/operations

operations(self): { { [string]: any } }

List this service's callable operations and their declared inputs, output, and credit cost — the per-service surface to read before invoking.

modules/ServiceFramework/README

ServiceFramework

The runtime every .service instance declares against — reached via asset.containing(__FILE__).modules.shared. The TYPE owns all the machinery (metered invoke, the submit→poll→download→write pipeline, the async generation handle, error classification); an instance only declares its surface with Service.define{...}. A normal generation service is pure data — no per-instance logic.

modules/ServiceFramework/balance

balance(_self: any): (number?, string?)

Read the caller's current credit balance (the same balance every service draws from). Returns (nil, errMsg) when not signed in.

modules/ServiceFramework/cost

cost(self: any, operation: string?): number?

The declared credit cost of an operation (the provider's estimate). The exact amount charged is returned on each generation; this is the up-front figure to budget against. Defaults to the primary/only operation.

modules/ServiceFramework/define

define(spec: { [string]: any }): any

Declare a metered service. An instance's init.luau calls this with its surface — the offering identity and one or more operations — and returns the result. The TYPE runs the pipeline; the instance writes no machinery.

modules/ServiceFramework/info

info(self: any): { [string]: any }

A one-line summary of this service for discovery: its offering, what it produces, and the operations it offers.

modules/ServiceFramework/invoke

invoke(self: any, input: { [string]: any }?): { [string]: any }

Run a generation for this service. Picks the operation from input.operation (or the service's primary / only operation), checks the operation's declared cost against the caller's balance/budget up front — raising with a legible reason if it can't be afforded — then runs the pipeline as a background task. Returns immediately with a generation handle. Watch the task — the services toolbox status tool (or the watch tool) — until status == "completed", then the finished asset is the task result's asset_path. The engine auto-imports the raw output into a spawnable asset (a mesh becomes a .bundle, an image a .texture, a sound a .audio); that imported asset — not the raw file — is what the completed result points to, ready to spawn. Consumes credits; check :cost() and :balance() first.

modules/ServiceFramework/operations

operations(self: any): { { [string]: any } }

List this service's callable operations and their declared inputs, output, and credit cost — the per-service surface an agent reads before invoking.

modules/ShaderAssetTypeRef/README

ShaderAssetTypeRef

Per-instance methods exposed on every AssetRef<shader>. Loaded lazily by asset_ref.module.

modules/ShaderAssetTypeRef/compileByName

compileByName(ref: string)

Compile a shader by reference from its .shader VFS source — the lazy compile-on-first-use entry. A material that references an as-yet-uncompiled shader makes the renderer record the want; the engine calls this (via __zero_request_shader_compile) to bring the shader online through the same generic compileShader path an edit runs. Resolution goes through the universal asset system — a reference that doesn't resolve is a bad reference in the content that owns it, not something to special-case here.

modules/ShaderAssetTypeRef/compileStatus

compileStatus(self): { status: string, error: string?, key: string? }

This shader's latest compile outcome — WITHOUT reading the engine log. Returns { status, error?, key? } where status is "compiled" (registered clean under every key), "failed" (with error = the real compiler message), or "pending" (not compiled yet / unknown). A shader registers under each of its keys — identity AND guid — and a material may look it up by either, so this checks them all and reports the WORST one (key names it): a registration that landed under the identity but not the guid reads as "pending" instead of hiding behind the healthy key. Compilation is async — a write queues it — so a "pending" right after editing means check again next frame. This is the authoritative "did my shader compile?" signal: a write succeeding and getProperties returning a schema do NOT mean the WGSL compiled.

modules/ShaderAssetTypeRef/getGlsl

getGlsl(self): string?

Read the GLSL body, when present. Returns nil for WGSL-only shaders.

modules/ShaderAssetTypeRef/getProperties

getProperties(self): { { [string]: any } }

List this shader's declared material properties (parsed from properties.yaml). Each entry is { name, type, default, min?, max? }. This is the editor-discovery surface — the SAME parse the compile uses.

modules/ShaderAssetTypeRef/getSourceCode

getSourceCode(self): string?

Read the primary shader body (shader.wgsl) as raw text.

modules/ShaderAssetTypeRef/getWgsl

getWgsl(self): string?

Read the WGSL body directly, ignoring the GLSL fallback. Use when you need to detect "is this shader WGSL-native?" vs the generic getSourceCode lookup that auto-falls-back.

modules/ShaderAssetTypeRef/listBindings

listBindings(self): { string }

List which top-level uniform / storage-buffer block names appear in the shader source — best-effort regex parse. Useful to cross-check against material:getPropertyNames() when debugging a "property doesn't exist" gap. Not a full WGSL parser; complex shaders with macros may report incomplete results.

modules/ShaderAssetTypeRef/listMaterialsUsing

listMaterialsUsing(self): { string }

List the material identities currently bound to this shader, by scanning the registered material catalogue. O(n) over the material list; cache the result if you call it on a hot path.

modules/ShaderAssetTypeRef/onChange

onChange(ref, change)

Asset-type change callback: (re)compile the shader whenever its WGSL body or properties.yaml is written. This is the ONLY thing that compiles a .shader — the implicit "WGSL written → recompile" path is gone — so it fires on the initial create (the template write) AND on every later edit, with no world reload. Convergent: see compileShader.

modules/ShaderAssetTypeRef/setWgsl

setWgsl(self, source: string): boolean

Overwrite the WGSL body on disk. Hot-reload picks the new body up on the next frame so any material using this shader recompiles. Returns true on success.

modules/SoundClipAssetTypeRef/README

SoundClipAssetTypeRef

Hooks for .soundClip assets. onCreate is the type's contribution to the generic asset.create("soundClip", name, opts) flow (mirroring texture.assetType). onChange keeps a managed container's data.zaud encoded payload in sync when its source.<ext> or .metadata settings are edited — the asset type reacting to writes inside its own instances (the type-level analogue of a component's onAssetReload). This hook owns every re-encode after the container exists.

modules/SoundClipAssetTypeRef/onChange

onChange(ref: any, change: { [string]: any })

React to a write inside a .soundClip/ instance, keeping the encoded data.zaud payload in sync. Editing source.<ext> re-encodes from the new audio. Editing the .metadata settings block re-encodes from the container's source: the managed source.<ext>, or — for a PCM-baked container — the decoded data.zaud itself. data.zaud / README.md writes are ignored. The dispatcher's same-asset guard suppresses the re-dispatch of our own synchronous data.zaud write, so no loop forms.

modules/SoundClipAssetTypeRef/onCreate

onCreate(name: string, opts: CreateOpts): { [string]: string }

modules/SoundClipAssetTypeRef/pcm

pcm(self): (string?, number?, number?)

Decode this soundClip asset's data.zaud payload into raw PCM samples.

modules/SoundClipAssetTypeRef/setSettings

setSettings(self, patch: { [string]: any })

Write a partial settings patch to this soundClip asset's .metadata. Pass any subset of the settings schema; only those keys change, the rest keep their stored value (asset.set_field deep-merges). Unknown keys error loudly. Writing .metadata re-runs the type's onChange, which re-encodes data.zaud against the new settings — so changing bitrateKbps recompresses the clip.

modules/SoundClipAssetTypeRef/settings

settings(self): { [string]: any }

Read this soundClip asset's settings, with every schema default filled in. The returned table always carries the full settings schema.

modules/StyleAssetTypeRef/README

StyleAssetTypeRef

Per-instance methods exposed on every AssetRef<style>. Loaded lazily by asset_ref.module.

modules/StyleAssetTypeRef/getInitScript

getInitScript(self): string?

Read the style's entry script as raw text.

modules/StyleAssetTypeRef/getReadme

getReadme(self): string?

Read the style's README.

modules/StyleAssetTypeRef/inspect

inspect(self): any

asset.inspect type-specific detail: { tokenCategories }, the top-level keys of the style's own M.tokens = { ... } table literal, parsed from the entry script's source text via luau_introspect.tableLiteralKeys — never requires the style. Cached on the asset's content checksum, so re-inspecting unchanged source is free. A style with no readable entry script returns an empty detail rather than erroring.

modules/StyleAssetTypeRef/loadTheme

loadTheme(self): any

Load the style module and return its exported theme table. Raises a Luau error tagged with the style identity if the require fails.

modules/SystemToolsV2.Tabs.Entities/README

SystemToolsV2.Tabs.Entities

Entities tab — entities grouped by sceneLayer, each layer collapsible with a visibility toggle and live entity count. Inside a layer, entities render as a hierarchy from the parentId / children fields the ECS-side snapshot already emits. Each row surfaces sync + lock status badges so multiplayer ownership and the lock guards are visible at a glance. Hierarchy is built from the parentId / children fields the ECS-side snapshot already emits per entity (see src/engine/systems/scripting/cache.rsparentId at the per-entity level, children: [{name, id}, ...] for the bulk iterator). Expand state lives on state.entityHierarchy.expanded so toggling a chevron survives across refreshes. Consumers: registered by system_tools/init.luau under the "entities" tab key.

modules/SystemToolsV2.Tabs.Entities/build

build(state: EntitiesState): any

Build the Entities tab widget tree for one frame. Pulls the ECS entity snapshot, applies the active filter / sort, and renders a per-layer hierarchical tree with chevron toggle, action buttons, and search / filter / sort controls.

modules/SystemToolsV2.Tabs.Entities/onCallback

onCallback(callbackId: string, data: any, state: EntitiesState): boolean

Handle a UI callback (selection, tree toggle, filter, spawn / delete / duplicate / focus action). Mutates the shell- owned state.entityDetails and state.entityHierarchy tables.

modules/SystemToolsV2.Tabs.Entities/onMount

onMount(state: EntitiesState)

One-time mount hook — seeds the per-tab state slots (entityDetails, entityHierarchy) so subsequent build calls can assume they exist. The tree highlight reads state.entityDetails.id, which the dock service keeps in step with the shared selection scope, so a selection made anywhere (viewport, gizmo, outliner) updates the highlight without a per-tab listener.

modules/SystemToolsV2.Tabs.EntityDetails/README

SystemToolsV2.Tabs.EntityDetails

Floating window showing the selected entity's hierarchy + Luau components + ECS-side components. Built alongside entities.luau — that tab manages selection state on state.entityDetails. This screen is REGISTERED as a second screen by init.luau so it can be docked / closed independently of tab focus. Returns nil when no entity is selected, so Z.app skips the update. Section order: 1. Hierarchy (parent / children link rows) 2. Luau scripts (each @builtin::components.X / authored component the entity carries) — primary surface for authoring 3. ECS components (Transform / Name / Visible / etc.) folded under a single collapsible because they're noise for most authoring tasks 4. Sync + lock status block (debug info) Data sources: - getEntityDetailsTable(id) (direct-id path) → Luau components keyed by their authoring identity (@builtin::components.X) plus hierarchy info (parentId / children). - getEntityDetailsTable() (no-arg cached path) → ECS components keyed by their Rust struct name (Transform / Name / Tags / ...). Populated by an earlier requestEntityDetails(id) call. - __native_entity.synced(id) → multiplayer-sync flag. - __locks.state(id) → destroy + per-component remove/write locks.

modules/TestSuiteAssetTypeRef/README

TestSuiteAssetTypeRef

Per-instance methods exposed on every AssetRef<testSuite>. Loaded lazily by asset_ref.module. These methods act on the SPECIFIC asset instance the reference points to — asset.resolve(id, "testSuite"):run() runs only THAT suite. Running every suite is the tests toolbox's job (it iterates asset.list("testSuite") and calls :run() on each); it is deliberately not offered here.

modules/TestSuiteAssetTypeRef/getReadme

getReadme(self): string?

Read this suite's README.md body.

modules/TestSuiteAssetTypeRef/inspect

inspect(self): any

asset.inspect type-specific detail: { testCount, testNames }, parsed from Test.it("<name>", ...) calls in this suite's own init.luau text. A string scan, not a suite run — inspecting a suite never registers or executes its tests. A suite with no readable init.luau returns an empty testNames list rather than erroring.

modules/TestSuiteAssetTypeRef/run

run(self, opts: { quiet: boolean? }?): any

Run THIS test suite (only this one) and return its results. Refuses in play mode — tests are an edit-mode / authoring concern, not gameplay. The run is cooperative (the framework yields a frame between tests) so it never blocks the engine or trips the execute watchdog.

modules/TestSuiteAssetTypeRef/tests

tests(self): { { name: string, skip: boolean } }

List the tests this suite declares (loads THIS asset's init.luau to read its registered Test.it / Test.skip names; does not run them).

modules/TextureAssetTypeRef/README

TextureAssetTypeRef

Hooks for .texture assets. onCreate is the type's contribution to the generic asset.create("texture", name, opts) flow (mirroring material.assetType). onChange keeps a MANAGED container's data.ztex encoded payload in sync when its source.<ext> or .metadata settings are edited — the asset type reacting to writes inside its own instances (the type-level analogue of a component's onAssetReload). The loose-image → container PROMOTE step is the separate texture.importer (loose-write seam); this hook owns every re-encode AFTER the container exists.

modules/TextureAssetTypeRef/handle

handle(self)

modules/TextureAssetTypeRef/load

load(self)

Load this .texture asset's pixels into the guid-keyed CPU store and return a CPU handle for per-pixel access (no GPU readback). The handle holds no pixels — only the guid, dims, and the read/write/encode/unload ops. Upload to the GPU with renderer.texture.create(handle); the DEFAULT is to handle:unload() after. The handle's :encode() re-encodes the (possibly edited) pixels into a fresh ZTEX blob for asset.create("texture", …).

modules/TextureAssetTypeRef/onChange

onChange(ref: any, change: { [string]: any })

React to a write inside a .texture/ instance, keeping the encoded data.ztex payload in sync. Editing source.<ext> re-encodes from the new image. Editing the .metadata settings block re-encodes from the container's source: the managed source.<ext>, a plain container's image primary, or — for a raw-pixel container — the decoded data.ztex itself. Every pixel change also regenerates the container's preview.png. README.md writes are ignored. The dispatcher's same-asset guard suppresses the re-dispatch of our own synchronous data.ztex write, so no loop forms.

modules/TextureAssetTypeRef/onCreate

onCreate(name: string, opts: CreateOpts): { [string]: string }

modules/TextureAssetTypeRef/preview

preview(self, opts: { [string]: any }?)

Render a preview of this texture as a flat swatch.

modules/TextureAssetTypeRef/setSettings

setSettings(self, patch: { [string]: any })

Write a partial settings patch to this texture asset's .metadata. Pass any subset of the settings schema; only those keys change, the rest keep their stored value (asset.set_field deep-merges). Unknown keys error loudly. Writing .metadata re-runs the type's onChange, which re-encodes data.ztex against the new settings — so changing filter recompiles the texture and the renderer rebinds it with the new sampler.

modules/TextureAssetTypeRef/settings

settings(self): { [string]: any }

Read this texture asset's settings, with every schema default filled in. The returned table always carries the full settings schema.

modules/ToolAssetTypeRef/README

ToolAssetTypeRef

Per-instance methods exposed on every AssetRef<tool>. Loaded lazily by asset_ref.module.

modules/ToolAssetTypeRef/getDefinition

getDefinition(self): string?

Read the tool's definition — its init.luau source, which carries the typed signature and the --!desc/--!arg/--!return/--!example docstring that together form the tool's schema.

modules/ToolAssetTypeRef/getInitScript

getInitScript(self): string?

Read the tool's entry script as raw text.

modules/ToolAssetTypeRef/getReadme

getReadme(self): string?

Read the tool's README body.

modules/ToolAssetTypeRef/inspect

inspect(self): any

asset.inspect type-specific detail: { desc, args, returns, examples, signature }, parsed from the tool's own entry script (getInitScript) via luau_introspect.docstrings — the same --!desc/--!arg/--!return/--!example docstring that documents the tool's schema. Cached on the asset's content checksum, so re-inspecting unchanged source is free. A tool with no readable entry script returns an empty detail rather than erroring.

modules/ToolAssetTypeRef/run

run(self, args: any): any

Invoke the tool's entry script. Requires the tool's identity and calls the first function it exports — the canonical function M.<name>(args) shape every tool ships. args is forwarded verbatim. Raises a Luau error (tagged with the tool's identity) on load or invocation failure rather than returning a swallowed nil — failing loudly matches the rest of the engine's tool dispatch surface.

modules/ToolAssetTypeRef/wrap

wrap(fn: (...any) -> ...any, regionName: string): (...any) -> any

Standardize a single tool function into a ZmToolResult-returning call. THIS is where the tool-result contract lives — the engine's tool-bind paths (boot + runtime) apply wrap to every tool function so the result type is a system guarantee, never the individual tool's choice. The tool body returns its raw value, returns the Lua failure convention (nil, reason), or raises with error(msg); wrap produces the canonical envelope { ok, value | error, durationMs, tool } from all three — a (nil, reason) return becomes { ok = false, error = reason } instead of silently dropping the reason and reporting a bare success with no value. Timing is measured with the engine profiler: a region named for the tool is opened when the call is invoked and closed when it returns, so each invocation is both timed (region elapsed -> durationMs) and visible as a profiler region. Each envelope is also reported into the running task's tool-result buffer, which execute() auto-surfaces as the response's toolResults.

modules/ToolAssetTypeRef/wrapToolbox

wrapToolbox(box: { [string]: any }, namespace: string): { [string]: any }

Wrap every function on a toolbox table so each tool call returns a ZmToolResult (see wrap). Non-function fields pass through untouched.

modules/ToolboxAssetTypeRef/README

ToolboxAssetTypeRef

Per-instance methods exposed on every AssetRef<toolbox>. Loaded lazily by asset_ref.module.

modules/ToolboxAssetTypeRef/getReadme

getReadme(self): string?

Read the toolbox's README.md body.

modules/ToolboxAssetTypeRef/hasShared

hasShared(self): boolean

Check whether the toolbox ships a shared.module/.

modules/ToolboxAssetTypeRef/inspect

inspect(self): any

asset.inspect type-specific detail: { tools }, where tools is { name, desc } for every *.tool/ child the toolbox owns, walked directly from the toolbox's own folder — desc is the child tool's own --!desc docstring line, read from its entry script without requiring the tool module. A toolbox with no readable folder returns an empty tools list rather than erroring.

modules/ToolboxAssetTypeRef/listTools

listTools(self): { any }

List the tool refs the toolbox carries. Walks the toolbox folder, finds every *.tool/ child, and resolves each to an AssetRef<tool>.

modules/Transform/README

Transform (global)

Math helpers for positions, rotations, and directions on transforms. Exposed as the global Transform table; entity-aware helpers accept an id string or an entity proxy. Also available as global: Transform

modules/Transform/direction

direction(fromX: number, fromY: number, fromZ: number, toX: number, toY: number, toZ: number): (number, number, number)

Normalized direction vector from point A to point B. Returns zeros when the two points coincide (within ~0.001 units).

modules/Transform/directionBetween

directionBetween(entityA: string | EntityRef, entityB: string | EntityRef): (number, number, number)

Normalized direction from one entity to another. Returns zeros if either entity can't be resolved.

modules/Transform/distance

distance(x1: number, y1: number, z1: number, x2: number, y2: number, z2: number): number

Euclidean distance between two world-space positions.

modules/Transform/distanceBetween

distanceBetween(entityA: string | EntityRef, entityB: string | EntityRef): number?

Distance between two entities in world space.

modules/Transform/euler

euler(qx: number, qy: number, qz: number, qw: number): (number, number, number)

Convert quaternion to euler angles (yaw, pitch, roll) in radians.

modules/Transform/eulerToQuat

eulerToQuat(yaw: number, pitch: number?, roll: number?): (number, number, number, number)

Identity-aware overload of euler-to-quaternion. Uses the negative-yaw convention shared with quatFromYaw, quatFromYawPitch, lookAtQuat, and T.euler extraction — so T.euler(T.eulerToQuat(y, p, r)) returns (y, p, r). Order is yaw (Y) then pitch (X) then roll (Z).

modules/Transform/lerp

lerp(ax: number, ay: number, az: number, bx: number, by: number, bz: number, t: number): (number, number, number)

Linearly interpolate between two positions.

modules/Transform/lerp1

lerp1(a: number, b: number, t: number): number

Linearly interpolate two scalars.

modules/Transform/lerpAngle

lerpAngle(a: number, b: number, t: number): number

Lerp between two angles via the shortest arc; returns a value in [-pi, pi].

modules/Transform/localToWorld

localToWorld(px: number, py: number, pz: number, pqx: number, pqy: number, pqz: number, pqw: number, lx: number, ly: number, lz: number): (number, number, number)

Transform a local-space position into world space using a parent pose.

modules/Transform/lookAt

lookAt(entityOrId: string | EntityRef, txOrTarget: number | string | EntityRef, ty: number?, tz: number?)

Make an entity face a world position. The target slot accepts either three explicit coordinates or an entity (id string or proxy) whose position is resolved.

modules/Transform/lookAtQuat

lookAtQuat(fx: number, fy: number, fz: number, tx: number, ty: number, tz: number): (number?, number?, number?, number?)

Compute quaternion to look from origin position toward a target. Returns four components (qx, qy, qz, qw), or nil when the from and to points are too close to derive a meaningful direction.

modules/Transform/normalizeAngle

normalizeAngle(a: number): number

Normalize an angle into [-pi, pi].

modules/Transform/orbit

orbit(centerX: number, centerY: number, centerZ: number, radius: number, height: number, angle: number): (number, number, number, number, number, number, number)

Position + rotation for orbiting around a center point. Returns the world position followed by the orientation that faces the center.

modules/Transform/quatFromAxisAngle

quatFromAxisAngle(ax: number, ay: number, az: number, angle: number): (number, number, number, number)

Create quaternion from axis and angle (radians). Returns the identity quaternion when the axis is degenerate (length < 0.001).

modules/Transform/quatFromYaw

quatFromYaw(yaw: number): (number, number, number, number)

Create quaternion from yaw (Y-axis rotation) in radians. Uses the negative-yaw convention shared with quatFromYawPitch, lookAtQuat, and T.euler extraction — so T.euler(T.quatFromYaw(y)) round-trips to y.

modules/Transform/quatFromYawPitch

quatFromYawPitch(yaw: number, pitch: number): (number, number, number, number)

Create quaternion from yaw and pitch in radians.

modules/Transform/quatIdentity

quatIdentity(): (number, number, number, number)

Identity quaternion (0, 0, 0, 1).

modules/Transform/quatInverse

quatInverse(qx: number, qy: number, qz: number, qw: number): (number, number, number, number)

Quaternion inverse. Equal to the conjugate for unit quaternions.

modules/Transform/quatMul

quatMul(ax: number, ay: number, az: number, aw: number, bx: number, by: number, bz: number, bw: number): (number, number, number, number)

Quaternion multiplication: returns qa * qb (composition: rotate by qb then qa).

modules/Transform/quatRotateVec

quatRotateVec(qx: number, qy: number, qz: number, qw: number, vx: number, vy: number, vz: number): (number, number, number)

Rotate a 3-vector by a quaternion.

modules/Transform/quatToEuler

quatToEuler(qx: number, qy: number, qz: number, qw: number): (number, number, number)

Convert quaternion to (yaw, pitch, roll). Alias of euler with the explicit name so callers don't have to remember the order.

modules/Transform/readVec3

readVec3(value: Vec3Input, label: string?): { number }

Normalize a vector a caller wrote to a plain { x, y, z } array. Accepts a positional array {1, 2, 3}, a keyed table {x =, y =, z =}, or a live vec handle. Missing components read as 0. Raises when the value is not a vector; label names the caller in that error.

modules/Transform/slerp

slerp(ax: number, ay: number, az: number, aw: number, bx: number, by: number, bz: number, bw: number, t: number): (number, number, number, number)

Spherical linear interpolation between two quaternions. Picks the shortest path (flips sign if dot < 0). Falls back to lerp+normalize when the two quats are very close (avoids div-by-zero on near-parallel inputs).

modules/Transform/snapVec3

snapVec3(v: { number }, step: number | Vec3Input): { number }

Quantize each component of a vector to the nearest multiple of step — a number for uniform steps, or a vector for per-axis steps. A step of 0 on an axis leaves that axis at its exact value.

modules/Transform/toQuaternion

toQuaternion(rotation: RotationInput, label: string?): { number }

Normalize a rotation a caller wrote to a { qx, qy, qz, qw } quaternion. Accepts a quaternion ({x,y,z,w} or {x=,y=,z=,w=}) or euler DEGREES ({pitch,yaw,roll} or {pitch=,yaw=,roll=}), so one call site takes whichever form the caller finds natural. Raises when the value matches no form; label names the caller in that error.

modules/Transform/vec.add

vec.add(ax: number, ay: number, az: number, bx: number, by: number, bz: number): (number, number, number)

Component-wise vec3 addition.

modules/Transform/vec.cross

vec.cross(ax: number, ay: number, az: number, bx: number, by: number, bz: number): (number, number, number)

Cross product a x b.

modules/Transform/vec.dot

vec.dot(ax: number, ay: number, az: number, bx: number, by: number, bz: number): number

Dot product of two vec3s.

modules/Transform/vec.length

vec.length(x: number, y: number, z: number): number

Euclidean length of a vec3.

modules/Transform/vec.normalize

vec.normalize(x: number, y: number, z: number): (number, number, number)

Normalize a vec3. Returns zeros when the input is degenerate (length < 1e-8).

modules/Transform/vec.scale

vec.scale(x: number, y: number, z: number, s: number): (number, number, number)

Component-wise scalar multiplication of a vec3.

modules/Transform/vec.sub

vec.sub(ax: number, ay: number, az: number, bx: number, by: number, bz: number): (number, number, number)

Component-wise vec3 subtraction (a - b).

modules/Transform/worldToLocal

worldToLocal(px: number, py: number, pz: number, pqx: number, pqy: number, pqz: number, pqw: number, wx: number, wy: number, wz: number): (number, number, number)

Transform a world-space position into a parent's local space.

modules/Validator/README

require("@builtin/systems/worldValidation.package/validator") -- Validator

Orchestrator for the world validator. Composes vfsScanner + scriptValidator + assetValidator + reportFormatter into one call that produces a Report with world and library buckets cleanly separated.

The validator does ONE pass over the VFS to discover every script and asset, then dispatches each entry to the appropriate per-type validator (scripts → scriptValidator → lsp.check; asset folders → assetValidator → asset.validate). It does not spawn entities or allocate GPU resources. Repeated calls are independent — there is no shared mutable state across runs. The four helper modules (vfsScanner, scriptValidator, assetValidator, reportFormatter) sit alongside this module at the package root and are pulled in via the ~.X package-relative form so the whole package is position-independent — moving it to any identity continues to resolve correctly without code edits.

Usage: local Validator = require("@builtin/systems/worldValidation.package/validator")

modules/Validator/check

check(opts)

Validate authored content. By DEFAULT scopes to YOUR world — everything under /source/ EXCEPT /source/libs/ — because imported libraries and engine builtins are not yours to validate (and scanning the whole builtin tree is slow and noisy). Pass opts.scope to widen: "libraries" for every imported library, "library:<name>" for one, "all" for world + libraries together. The cargo check equivalent for a Zero world.

modules/Validator/filter

filter(report, opts)

Re-filter an existing Report without re-scanning the VFS. Forwards to reportFormatter.filter; the resulting Report's counts are recomputed from the visible problems.

modules/Validator/format

format(report, format)

Render a Report into a string. format selects the renderer.

modules/Validator/placeholders

placeholders()

Enumerate the registered placeholder checks. Each placeholder is a check the validator runs today but with a stub implementation — listing them tells callers which validations still need real primitives wired up.

modules/Validator/saveReport

saveReport(report, path, opts)

Render a Report and write it to path. Format is taken from opts.format if present, otherwise inferred from the destination extension (.md → markdown, .json → json, anything else → human). Returns (ok, message?).

modules/Validator/summary

summary(report)

Compact one-line health summary — world: NE/NW libraries: NE/NW total: OK|FAIL.

modules/Validator/validateLibraries

validateLibraries(opts)

Validate every imported library under /source/libs/. The returned Report has world = nil and libraries populated for every library that exists on disk.

modules/Validator/validateLibrary

validateLibrary(name, opts)

Validate ONE named library under /source/libs/<name>/. When the library does not exist, the Report carries a single library.missing error against that name so callers can tell a clean run from a missing-dependency run.

modules/Validator/validateWorld

validateWorld(opts)

Validate ONLY the world's authored content (/source/ excluding /source/libs/). The returned Report has world populated and libraries = {}.

modules/VfsScanner/README

require("@builtin/systems/worldValidation.package/vfsScanner") -- VfsScanner

Recursive /source/ walker for the world validator. Separates world content from imported libraries so downstream validators can attribute every problem to the right bucket.

An asset root is any folder whose name has a registered asset type suffix (Foo.component, bar.module, Baz.toolbox, …). When the scanner crosses one it records the root and does NOT recurse into it as raw files — the asset validator handles the interior. Scripts (.luau, .lua) discovered outside any asset root are also recorded (e.g. _shared.luau siblings inside a toolbox, or stray top-level scripts).

Usage: local VfsScanner = require("@builtin/systems/worldValidation.package/vfsScanner")

modules/VfsScanner/assetSuffixes

assetSuffixes(): { string }

Expose the registered asset-suffix list (read-only). External callers that want to recognise asset folders the same way the scanner does can iterate this list.

modules/VfsScanner/listLibraryNames

listLibraryNames(): { string }

Enumerate the immediate children of /source/libs/. Each child is a library identity (e.g. @builtin, @mylib). Returns an empty array if /source/libs/ does not exist.

modules/VfsScanner/scanAll

scanAll()

Convenience: scan world + every imported library in one call.

modules/VfsScanner/scanLibraries

scanLibraries(): { [string]: any }

Walk every imported library and return a map keyed by name.

modules/VfsScanner/scanLibrary

scanLibrary(name: string)

Walk one named library under /source/libs/<name>/. The returned Bucket's rootPath is the library root so callers can derive relative paths cheaply.

modules/VfsScanner/scanWorld

scanWorld()

Walk only the world bucket — everything under /source/ except /source/libs/. The scanner stops recursing whenever it reaches an asset-suffixed folder; asset interiors are handled by the assetValidator.

modules/World/README

require("@builtin/modules/world") -- World (also available as global 'world')

Public Luau API for the bound world. Composes internal __world FFI primitives with per-mode slot defaults (world_defaults), source-control toolbox (world_vcs), and the connected-users registry (connected_users) into a single namespace. Usage: world.guid() -- current world GUID, nil if none world.swap(guid) -- bind to another world (promise) world.avatar_default_edit = ref -- per-mode avatar default world.on("player_join", cb) -- world-level event hooks Implemented as a thin Luau wrapper. The world global is a plain table whose __index metatable falls through to the internal __world namespace for unknown reads. Other library modules (world_defaults, world_vcs, connected_users) attach their surfaces via installInto(world) from prelude.luau; their metatable wrappers chain through this base layer correctly.

Usage: local World = require("@builtin/modules/world") Also available as global: world

modules/Yaml/README

Yaml

YAML decode + encode for authored engine content. Supports the YAML subset engine configs use: block mappings + sequences, flow collections, typed plain scalars, quoted strings, comments, literal and folded block scalars, an optional leading ---. Unsupported constructs (anchors, aliases, tags, directives, multi-document streams, tab indentation) raise with the offending line number.

modules/Yaml/decode

decode(text: string): any

Decode a YAML document into a Luau value. Raises (with the line number) on malformed input or constructs outside the supported subset — never misparses silently.

modules/Yaml/encode

encode(value: { [any]: any }): string

Encode a Luau table as a YAML document (block style, two-space indent, sorted keys). Raises on values YAML can't represent (functions, userdata, non-string mapping keys).

modules/animGraph/README

require("@builtin/modules/api/engine/animGraph") -- animGraph (also available as global 'animGraph')

Per-entity engine animation graph over the __animGraph FFI: clips, mixers, 2D blend spaces, crossfade, weights, and graph state. Each call routes onto the entity's AnimGraphComponent (auto-resolved from script context or passed explicitly).

Usage: local animGraph = require("@builtin/modules/api/engine/animGraph") Also available as global: animGraph

modules/asset/README

require("@builtin/modules/api/engine/asset") -- asset (also available as global 'asset')

Asset resolver, ref envelope builder, sidecar metadata. Public Luau surface over the __asset Internal FFI namespace.

Usage: local asset = require("@builtin/modules/api/engine/asset") Also available as global: asset

modules/asset/add_tag

add_tag(ref: RefArg, tag: string)

Add a tag to the asset's .metadata.tags. Idempotent. Creates the sidecar and the tags array if missing.

modules/asset/categories

categories(): { string }

List every asset category the engine currently recognises. Use to discover valid type argument values for the rest of asset.*.

modules/asset/containerize

containerize(ref: RefArg, outputName: string): string

Copy an asset and every dependency it transitively references into /source/<outputName>.bundle/ with fresh guids. Returns a promise — await(...) it before treating the bundle as ready.

modules/asset/containing

containing(path: string): AssetRef?

Walk path's ancestors and return an AssetRef handle for the OUTERMOST category-folder containing it (e.g. main.scene for "/source/scenes/main.scene/scene.json"). Returns nil for paths outside any registered asset type.

modules/asset/create

create(typeName: string, name: string, opts: { [string]: any }?): AssetRef

Instance a new asset of an existing type. Runs the type's behavior.luau onCreate(name, opts) hook to produce the asset's files, then writes them under /source/<name>.<type>/. This is the single generic asset-creation API. Refuses to clobber an existing edit-mode asset unless opts.overwrite = true, which re-authors it in place and keeps the existing guid (only the checksum changes). Pairs with asset.exists for content generators that re-run over the same names.

modules/asset/deps

deps(ref: RefArg, type: string?): DepsResult

Return the asset's outbound dependency graph — every other asset recorded as a content dependency of it.

modules/asset/describe

describe(typeName: string): DescribeResult

The creation contract for an asset type: the parameters its onCreate(name, opts) hook accepts, as data. kind is "schema" (typed contract), "legacy" (untyped opts — anything passes), "none" (template scaffold — takes no opts), or "error" (the type's schema failed to parse; error says why). contract is the human-readable rendering validation errors print.

modules/asset/exists

exists(name: string, typeName: string): boolean

Whether an asset named name of type typeName exists in the current mode's store (/source in edit, the runtime fork in play). A plain existence probe — it does NOT resolve a handle or pin a content dependency, so it is safe to call with a COMPUTED name (unlike asset.resolve, whose handle would become a static-pinned dependency).

modules/asset/get_field

get_field(ref: RefArg, key: string): any

Read one top-level field from the asset's .metadata.

modules/asset/guid

guid(ref: RefArg, type: string?): string

Return the guid for an asset.

modules/asset/has_field

has_field(ref: RefArg, key: string): boolean

True when the asset's .metadata carries the named field.

modules/asset/has_tag

has_tag(ref: RefArg, tag: string): boolean

True when the asset's .metadata.tags contains tag.

modules/asset/identity

identity(ref: RefArg, type: string?): string

Return the canonical identity for an asset.

modules/asset/import

import(path: string): string?

Import a raw source file NOW and return the produced asset path (a .bundle for a model, .texture for an image, .audio for a sound, …), or nil if no importer claims it. This is the deterministic, on-demand counterpart to the engine's automatic import-on-write: it runs in the calling task and returns only when the import is complete. Pair it with a quiet write — vfs.write(path, bytes, { quiet = true }) lands the raw bytes without firing the automatic importer, then asset.import(path) imports them under your control, so you can act on the result instead of polling for the import to appear.

modules/asset/inspect

inspect(ref: RefArg, type: string?): InspectRecord

Inspect an asset. Resolves ref, builds the common envelope every asset shares (identity, name, guid, source, typeName, typeDefinitionPath, scope, origin, description, tags), then dispatches to the resolved type's ref.inspect(self) (declared on its behavior.luau) for the type-specific detail. A type with no inspect hook leaves detail nil — the envelope alone. A hook that raises leaves detail nil and sets warning with the error text; asset.inspect itself never raises for a resolved ref.

modules/asset/list

list(type_or_opts: (AssetCategory | ListOpts)?, scope: string?, opts: ListOpts?): ListResult

Query registered assets, returning each match as a resolved AssetRef handle. Every filter narrows the same enumeration and they compose: path selects a VFS subtree (the folder and everything under it), type keeps only those asset types within it, scope keeps only that scope, and fields keeps only assets whose .metadata matches. type and path each take one value or a list matching any of its entries, and all / any / none group whole filters — none excludes what it matches. order, limit, and offset shape the result: matches come back ordered by identity unless order names another field (name / path / type / guid). Each entry is the same envelope asset.resolve returns (__ref / type / name / guid / identity / path), so it can be passed anywhere an AssetRef is accepted, and the result carries :first() / :random() / :filter() / :sort() and friends. type takes the same values asset.categories() lists. The first positional argument is a path when it is absolute, a type otherwise. An unknown key raises, as does a table setting both type and its older spelling category. A static (literal) type or path makes the enumeration part of the calling file's content dependencies when it is saved — the set travels with published content, so consumers get at-least the authoring world's assets.

modules/asset/list_field_values

list_field_values(key: string): { any }

Distinct values seen for the named field across every asset's .metadata.

modules/asset/list_fields

list_fields(): { string }

Distinct top-level field keys observed across every asset's .metadata. Useful for tooling discovering custom keys in use.

modules/asset/meta

meta(ref: RefArg, type: string?): AssetMeta

Read the asset's engine-owned identity record (guid / checksum). Distinct from .metadata (agent-editable); for that use asset.metadata.

modules/asset/metadata

metadata(ref: RefArg, type: string?): AssetMeta

Read the asset's agent-editable .metadata sidecar as a Lua table. Missing sidecar returns {}. Distinct from asset.meta (engine-owned).

modules/asset/preview

preview(ref: RefArg, opts: { [string]: any }?, type: string?): { [string]: any }

Render a preview of an asset. Resolves the ref and dispatches to its type's preview ref-method when present; otherwise returns the { available = false } sentinel ("no preview available for this type").

modules/asset/ref

ref(ref: RefArg, type: string?): AssetRef?

Build a reference handle for an asset — the canonical ref envelope constructor. Identical shape to asset.resolve; preferred name for the author-side use case (embedding refs in YAML / JSON / Luau output).

modules/asset/remove_field

remove_field(ref: RefArg, key: string)

Remove one top-level field from the asset's .metadata. No-op when the field isn't present.

modules/asset/remove_tag

remove_tag(ref: RefArg, tag: string)

Remove a tag from the asset's .metadata.tags. No-op when the tag isn't present.

modules/asset/resolve

resolve(ref: RefArg, type: string?): AssetRef?

Find an asset. The returned handle carries every name form the asset has (guid, identity, path, type) so downstream code can read any one of them without calling resolve again. Raises when ref resolves to no asset — or, with a type, to no asset of that type — and when a bare no-type name is ambiguous across categories (it never silently prefers one category). For a presence check that never raises, use asset.exists(name, type); to handle a possible miss inline, wrap the call in pcall.

modules/asset/set_field

set_field(ref: RefArg, key: string, value: any)

Set one field in the asset's .metadata, creating the sidecar if missing. Sibling fields are preserved. When the new value AND the existing value are both maps (objects), the new value DEEP-MERGES into the existing one, so writing one sub-key never drops the others — set_field(ref, "settings", { keepCpu = true }) keeps every other setting. Arrays and scalars replace. Clear a whole field with asset.remove_field; replace the entire sidecar with asset.set_metadata.

modules/asset/set_metadata

set_metadata(ref: RefArg, data: AssetMeta)

Replace the asset's .metadata sidecar with the given table. Pass an empty table to clear all fields.

modules/asset/source

source(ref: RefArg, type: string?): string

Return the VFS source path for an asset.

modules/asset/tags

tags(ref: RefArg): { string }

Convenience read of the .metadata.tags array.

modules/asset/typeRef

typeRef(target: RefArg): string?

Return the pinned asset_type reference (the type's guid) that the asset is an instance of. Resolve the full type with asset.resolve(asset.typeRef(target)). Returns nil for loose files / assets with no pinned type.

modules/asset/validate

validate(ref: RefArg, type: string?): ValidateResult

Validate an asset folder against its type's type.yaml, plus the type's own semantic validation. Structural problems come from type.yaml — missing required files, unsatisfied one_of_group alternatives, and (when allow_unlisted: false) unexpected children. validated = false when no type.yaml is registered — nothing structural to check. On top of that, when the asset's type ships a behavior.luau exporting a top-level validate(assetRef) -> { { code, message, severity? } }, its reported problems (severity defaults to "error") are appended to problems; error-severity problems flip ok to false, warnings leave it untouched. A hook that raises or returns a non-table is itself reported as a validate.hook_failed error problem — a broken hook blocks. A type with no validate export behaves exactly as the structural check alone. world.push calls this per user asset, so a type's semantic validation is enforced at publish time with no further wiring.

modules/asset/warmup

warmup(ref: RefArg, opts: WarmupOpts?): WarmupResult

Warm an asset's bytes into CPU memory and follow its declared content dependencies to each referenced asset, deduped by guid. Type-agnostic (reads the generic ref graph) and CPU-only — never touches the GPU. Side-effect-free name resolution (uses asset.guid/asset.deps, not asset.resolve).

modules/audio/README

require("@builtin/modules/api/engine/audio") -- audio (also available as global 'audio')

Engine-native audio codec: encode audio (or raw PCM) into the ZAUD compressed payload, decode/inspect it. Thin wrapper over __audio.

Usage: local audio = require("@builtin/modules/api/engine/audio") Also available as global: audio

modules/audio/decode

decode(zaud: string): (string?, number?, number?)

Decode a ZAUD payload into interleaved f32 PCM.

modules/audio/encode

encode(sourceBytes: string, opts: { [string]: any }?): (string?, string?)

Encode container audio bytes (ogg / mp3 / wav / flac) into a ZAUD payload.

modules/audio/encodePcm

encodePcm(pcm: any, sampleRate: number, channels: number, opts: { [string]: any }?): (string?, string?)

Encode raw interleaved f32 PCM into a ZAUD payload.

modules/audio/info

info(zaud: string): (AudioInfo?, string?)

Read a ZAUD payload's header without decoding the audio.

modules/av/README

require("@builtin/modules/api/engine/av") -- av (also available as global 'av')

Audio/video encode + mux control — live streaming, recording, encoder/muxer primitives. Public Luau surface over the __av Internal FFI namespace.

Usage: local av = require("@builtin/modules/api/engine/av") Also available as global: av

modules/av/is_live

is_live(): boolean

True if a live-stream session is currently active.

modules/av/is_recording

is_recording(): boolean

True if a recording session is currently active.

modules/av/live

live(opts: LiveOpts?): string?

Start the live-stream encoder. The stream is served at /engine/live.stream and reverse-proxied at /stream/<instance>/live.stream as a binary length-prefixed protocol consumed by the multiviewer UI's WebCodecs decoder. When texture_handle is set, the encoder reads from that GPU texture's guid (a Camera pointed at it via setTargetTexture) instead of the scene's viewport — that's how spectator cameras work. Returns a stream URL, or nil when unsupported or a session is already active.

modules/av/record

record(path: string, opts: RecordOpts?): string

Start recording the engine output to a VFS path. Default dir is /zero/runtime/recordings/ when path is not absolute. With no chroma/range opts the format defaults to full-range 4:4:4 HEVC where the GPU supports it, else 4:2:0 limited. Returns a promise handle — use task.await() for the final result.

modules/av/status

status(): AvStatus

Report the encoder subsystem's state. Always available regardless of GPU support. Returns { supported, reason, backend, live, recording }backend is the hardware encode backend in use ("vulkan" or "vaapi") when supported.

modules/av/stop_live

stop_live(): boolean

Stop any active live-stream session.

modules/av/stop_recording

stop_recording(handle: string?): boolean

Stop the active recording (or the one for the given promise handle). The promise resolves with the final result once stop completes.

modules/base64/README

require("@builtin/modules/api/engine/base64") -- base64 (also available as global 'base64')

Base64 encode / decode between binary and text Luau strings (standard RFC 4648 alphabet, padded). Public Luau surface over the __base64 Internal FFI namespace.

Usage: local base64 = require("@builtin/modules/api/engine/base64") Also available as global: base64

modules/base64/decode

decode(text: string): (string?, string?)

Decode standard-alphabet base64 text back to the original binary string.

modules/base64/encode

encode(bytes: string): string

Encode a binary string to standard-alphabet (padded) base64 text.

modules/blend/README

require("@builtin/modules/api/engine/blend") -- blend (also available as global 'blend')

Record-stride blend primitives over Buffer slices — layout registry + weighted/lerp combiners. Public Luau surface over the __blend Internal FFI namespace.

Usage: local blend = require("@builtin/modules/api/engine/blend") Also available as global: blend

modules/blend/destroyLayout

destroyLayout(handle: number): boolean

Drop the layout from the registry.

modules/blend/layout

layout(slots: { BlendSlot }, totalStride: number?): number?

Register a record-stride layout. Each slot is { offset, stride, op } where op is "lerp" / "slerp" / "sum" / "step". Slerp slots must have stride 4. totalStride defaults to max(offset + stride) across slots; pass an explicit value when records contain padding past the last slot.

modules/blend/lerpInto

lerpInto(outBuffer: number, layout: number, aBuffer: number, bBuffer: number, t: number): boolean

Two-input crossfade shortcut. Equivalent to blend.weightedInto(out, layout, { {a, 1-t}, {b, t} }). Faster for the common A/B fade case because it skips the inputs-table walk.

modules/blend/weightedInto

weightedInto(outBuffer: number, layout: number, inputs: { BlendInput }): boolean

Combine N weighted input Buffers into the output Buffer using the layout's slot ops. The output buffer's length must be a whole multiple of layout.totalStride; every input buffer must be at least as long as the output. Returns false on any handle / size mismatch.

modules/camera/README

require("@builtin/modules/api/engine/camera") -- camera (also available as global 'camera')

Script-facing camera queries: the main scene camera, the on-screen render camera, the editor fly-camera, and per-frame view data. Public Luau surface over the __camera Internal FFI namespace.

Usage: local camera = require("@builtin/modules/api/engine/camera") Also available as global: camera

modules/camera/active

active(): string?

Entity id of the on-screen render camera this frame — whichever camera wins the viewport by priority (the editor fly-camera in edit mode, the gameplay camera in play). Render features, billboards, and input bases that must follow the human's on-screen view read this.

modules/camera/editor

editor(): string?

Entity id of the editor fly-camera (the EditorOnly authoring camera), or nil if the scene has none.

modules/camera/main

main(): string?

Entity id of the main scene camera — the highest-priority active camera that is NOT the editor fly-camera. The gameplay/PlayerPrototype camera, an agent-placed scene camera, or a cutscene camera. Never the editor camera; nil if the scene has only the editor camera. For the camera currently drawn on screen, use camera.active().

modules/camera/viewData

viewData(): CameraView?

The active viewport camera's render data this frame: world position, vertical FOV, viewport pixel size, the 6 world-space frustum planes (the same inward-pointing, normalized planes the renderer culls with), and the view-projection matrix. The camera state a render feature needs for camera-relative work — LOD selection, frustum culling, billboards. Render features also get it as ctx.camera.

modules/camera_spawner/README

camera_spawner

Spawns the scene's primary Camera entity on non-additive layers.onLoad, for legacy v6 scenes. The per-mode world default (world.camera_default_<mode>) is the fallback; scene-level overrides via sceneProxy.settings.camera win when present. Missing refs → log.error + skip (no hardcoded fallback).

modules/channel/README

require("@builtin/modules/api/engine/channel") -- channel (also available as global 'channel')

Keyframe-channel sampling primitives — registry + sampleInto variants. Public Luau surface over the __channel Internal FFI namespace.

Usage: local channel = require("@builtin/modules/api/engine/channel") Also available as global: channel

modules/channel/create

create(opts: ChannelOpts): number?

Register a keyframe channel. times is the sorted keyframe time array; values is the packed value array (layout depends on interp); stride is the floats-per-sample width; interp is "step" | "linear" | "slerp" | "cubicHermite". Returns the channel handle, or nil on malformed input.

modules/channel/destroy

destroy(handle: number): boolean

Drop the channel from the registry.

modules/channel/sampleInto

sampleInto(ch: number, time: number, buf: number, offset: number): boolean

Sample the channel at time and write stride floats into the Buffer's data starting at f32 index offset. Returns false on unknown handle, layout mismatch, or out-of-bounds; the buffer is unchanged on failure.

modules/channel/sampleManyInto

sampleManyInto(ch: number, time: number, buf: number, offsets: { number }): boolean

Sample once, blit the result into every position in offsets. Saves the per-offset binary search when one channel feeds many bones / particles / parameters.

modules/channel/sampleQuat

sampleQuat(ch: number, time: number): (number?, number?, number?, number?)

Convenience accessor for stride-4 quaternion channels.

modules/channel/sampleVec3

sampleVec3(ch: number, time: number): (number?, number?, number?)

Convenience accessor for stride-3 channels. Returns the three components as multiret, or nil if the channel is unknown / has a different stride.

modules/color/README

require("@builtin/modules/api/engine/color") -- color (also available as global 'color')

Color construction, conversion, and perceptual ops (RGB / HSL / HSV / Oklch / hex). Public Luau surface over the __color Internal FFI namespace.

Usage: local color = require("@builtin/modules/api/engine/color") Also available as global: color

modules/color/complementary

complementary(c: Color): Color

Complementary color — rotate hue 180° in Oklch space.

modules/color/darken

darken(c: Color, amount: number): Color

Decrease the lightness of a color in Oklch perceptual space.

modules/color/desaturate

desaturate(c: Color, amount: number): Color

Decrease the chroma (saturation) of a color in Oklch space.

modules/color/hex

hex(hexString: string): Color?

Parse a hex color string into an sRGB color table. Accepts 3, 4, 6, or 8 hex digits with or without a leading # (e.g. "#f00", "f00f", "#ff0000", "ff000080"). Returns nil on parse failure.

modules/color/hsl

hsl(h: number, s: number, l: number): Color

Build a color from HSL (h: 0-360, s: 0-1, l: 0-1). Returned as sRGB.

modules/color/hsla

hsla(h: number, s: number, l: number, a: number): Color

Build a color from HSLA, returned as sRGB.

modules/color/hsv

hsv(h: number, s: number, v: number): Color

Build a color from HSV (h: 0-360, s: 0-1, v: 0-1).

modules/color/lighten

lighten(c: Color, amount: number): Color

Increase the lightness of a color in Oklch perceptual space.

modules/color/linear

linear(r: number, g: number, b: number, a: number?): Color

Build a color from linear RGB values (not gamma-corrected), output converted to sRGB. Useful for GPU-correct blending. Alpha defaults to 1.

modules/color/mix

mix(c1: Color, c2: Color, t: number): Color

Perceptually blend two colors in Oklch space — better than RGB mixing for gradients.

modules/color/mixRgb

mixRgb(c1: Color, c2: Color, t: number): Color

Linearly blend two colors in sRGB space — simple, but not perceptually uniform. Prefer color.mix for natural gradients.

modules/color/oklch

oklch(l: number, c: number, h: number): Color

Build a color from Oklch perceptual color space (l: 0-1, c: 0-0.4, h: 0-360). Ideal for perceptually uniform gradients and color manipulation.

modules/color/rgb

rgb(r: number, g: number, b: number): Color

Build an sRGB color from CSS-style 0-255 RGB channels. Alpha defaults to 1. Channels are normalised to 0-1 on the way out so the result composes with every other color helper.

modules/color/rgba

rgba(r: number, g: number, b: number, a: number): Color

Build an sRGB color from CSS-style 0-255 RGB channels with explicit alpha. RGB are normalised to 0-1; alpha is taken as-is in the 0-1 range.

modules/color/rotateHue

rotateHue(c: Color, degrees: number): Color

Rotate the hue of a color by a given number of degrees in Oklch space.

modules/color/saturate

saturate(c: Color, amount: number): Color

Increase the chroma (saturation) of a color in Oklch space.

modules/color/toHex

toHex(c: Color): string

Convert a color to a hex string. Returns "#rrggbb" or "#rrggbbaa" if alpha is not 1.

modules/color/toHsl

toHsl(c: Color): HslColor

Convert a color to HSL.

modules/color/toLinear

toLinear(c: Color): Color

Convert a color from sRGB to linear RGB space — useful for GPU calculations that need linear-space values.

modules/color/toOklch

toOklch(c: Color): OklchColor

Convert a color to Oklch perceptual color space.

modules/color/withAlpha

withAlpha(c: Color, a: number): Color

Return a copy of a color with a different alpha value.

modules/compute/README

require("@builtin/modules/api/engine/compute") -- compute (also available as global 'compute')

GPU compute pipelines — register shaders, manage buffers, dispatch workgroups, read back results. Public Luau surface over the __compute Internal FFI namespace.

Usage: local compute = require("@builtin/modules/api/engine/compute") Also available as global: compute

modules/compute/buildBvh

buildBvh(instances: { any }, opts: { [string]: any }?): (any, string?)

Build a bounding-volume hierarchy over the world-space triangles of a set of mesh instances and upload it as two named compute buffers — geometry never passes through the scripting heap. Each instance is { guid, transform, attributes? }: guid names a mesh resident in the meshcpu store (materialise with ref:load() / meshcpu.load), transform is 16 numbers, row-major, translation in slots 4/8/12, and attributes is up to 40 floats stamped onto every triangle of that instance (surface colors, material ids, physics tags — whatever the consuming shader wants per-surface). Triangles pack 18 vec4 each (v0/v1/v2, n0/n1/n2, uvs, then 10 attribute vec4s — float slots 32.. carry the instance attributes, zero when absent) in BVH leaf order; nodes 2 vec4 each (min + first-or-left, max + leaf-tagged count-or-right). Consumers: GI baking, ray-traced passes, GPU picking, navmesh and SDF generation.

modules/compute/compile

compile(nameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }?): boolean

Compile a compute shader from inline WGSL + a declarative binding schema — the same codegen a .computeShader asset uses. The engine generates the @group/@binding declarations from bindings/params, so the source writes only @compute fn main. Symmetric with registerShader, but with zero-scaffolding bindings (incl. textures, samplers, storage textures and a params uniform). For asset-backed shaders prefer authoring a .computeShader (compiled automatically); use this for dynamic/generated compute shaders.

modules/compute/compileByName

compileByName(ref: string | { [string]: any } | AssetRef)

Optional explicit pre-warm for a .computeShader asset (idempotent — fingerprint-guarded). NORMALLY UNNECESSARY: compute.dispatch / dispatchEx auto-compile a .computeShader on first use. Reach for this only to avoid the one-frame first-dispatch warm-up in a latency-critical spot. Accepts an identity/guid string or a resolved asset handle (its .identity is used).

modules/compute/copyBufferToTexture

copyBufferToTexture(bufferName: string, textureKey: string, width: number, height: number, format: string?): boolean

Copy a compute buffer into a cached GPU texture under textureKey, staying on the GPU. The path for an image a compute pass produced: the buffer holds tightly-packed rows in the format's texel layout, and the result is an ordinary cached texture — sample it from a material, or pack it into the shared feature-texture array. Rows must be a multiple of 256 bytes (at rgba16f, any width from 32 up in powers of two).

modules/compute/createBuffer

createBuffer(name: string, opts: BufferOpts): boolean

Create a named GPU storage buffer.

modules/compute/createSampler

createSampler(name: string, opts: { [string]: any }?): boolean

Create a named GPU sampler. opts: filter/wrap settings.

modules/compute/createStorageTexture2D

createStorageTexture2D(name: string, opts: { [string]: any }): boolean

Create a 2D storage texture (compute-writable render target). opts: { width, height, format? }.

modules/compute/createTexture3D

createTexture3D(name: string, opts: { [string]: any }): boolean

Create a 3D texture volume. opts: { width, height, depth, format?, storage? }.

modules/compute/createTextureHistory

createTextureHistory(name: string, opts: { [string]: any }): boolean

Create a temporal history buffer (ping-pong textures) for a target. opts: { width, height, format? }.

modules/compute/destroyBuffer

destroyBuffer(name: string): boolean

Destroy a named GPU compute buffer and free its memory.

modules/compute/destroyShader

destroyShader(name: string): boolean

Destroy a named compute shader pipeline.

modules/compute/destroyShaderEx

destroyShaderEx(name: string): boolean

Destroy a shader registered via registerShaderEx.

modules/compute/destroyStorageTexture2D

destroyStorageTexture2D(name: string): boolean

Destroy a named 2D storage texture.

modules/compute/destroyTexture3D

destroyTexture3D(name: string): boolean

Destroy a named 3D volume and free its GPU memory.

modules/compute/destroyTextureHistory

destroyTextureHistory(name: string): boolean

Destroy a named texture-history buffer.

modules/compute/dispatch

dispatch(shaderNameOrHandle: string | { [string]: any } | AssetRef, opts: DispatchOpts): boolean

Dispatch a compute shader with bound buffers. Accepts a shader name string or an asset handle from asset.load().

modules/compute/dispatchEx

dispatchEx(shaderNameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }): boolean

Dispatch a compute shader with extended texture/storage/sampler bindings. Asset-backed .computeShaders resolve to their stable guid (collision-safe, lazily compiled on first dispatch); raw registerShaderEx names pass through.

modules/compute/dispatchOnVertices

dispatchOnVertices(shaderNameOrHandle: string | { [string]: any } | AssetRef, opts: DispatchOnVerticesOpts): boolean

Dispatch a compute shader with a model's vertex buffer bound at binding 0 (read_write). Use to mutate vertex positions directly.

modules/compute/getReadbackResult

getReadbackResult(resultKey: string): { number }?

Poll for a completed read-back. Returns array of f32 values if ready, nil if pending. Result is consumed on retrieval.

modules/compute/getReadbackResultBytes

getReadbackResultBytes(resultKey: string): buffer?

Poll for a completed read-back and get its raw bytes as a buffer, copied once. The read counterpart of writeBufferBytes: read values out with buffer.readf32 / buffer.readu32, or hand the buffer straight to writeBuffer — a payload that stays packed never becomes a table. Result is consumed on retrieval.

modules/compute/getReadbackResultU32

getReadbackResultU32(resultKey: string): { number }?

Poll for a completed read-back interpreting bytes as u32. Returns array of integer values if ready, nil if pending.

modules/compute/isReadbackReady

isReadbackReady(resultKey: string): boolean

Check if a readback result is available without consuming it.

modules/compute/readBuffer

readBuffer(bufferName: string): string

Start an async GPU→CPU read-back of a named buffer. The buffer must have been created with readback = true. Returns a result key to poll with getReadbackResult().

modules/compute/readTexture3D

readTexture3D(name: string): string

Start a GPU→CPU readback of a named 3D volume. Returns a result key to poll.

modules/compute/registerShader

registerShader(nameOrHandle: string | { [string]: any } | AssetRef, opts: ShaderOpts?): boolean

Register a compute shader. Accepts an asset handle from asset.load(), or (name, opts) with inline WGSL source.

modules/compute/registerShaderEx

registerShaderEx(nameOrHandle: string | { [string]: any } | AssetRef, opts: { [string]: any }?): boolean

Register a compute shader with extended texture/sampler bindings. Accepts a name + opts or an asset handle.

modules/compute/setParam

setParam(name: string, prop: string, value: number): boolean

Set a named scalar parameter on a .computeShader (a params: entry in its bindings.yaml). Updates the shader's params uniform in place; the next dispatch sees the new value. No effect on raw registerShader shaders, which have no params block.

modules/compute/textureFormatBytes

textureFormatBytes(format: string): number

Bytes-per-voxel for a texture format string (rgba16f, r8, ...).

modules/compute/writeBuffer

writeBuffer(name: string, data: buffer | { number }, offset: number?): boolean

Write floats to a named GPU buffer (little-endian f32 bytes). A buffer is copied across whole; an array is converted element by element, which costs four FFI calls per number — for anything large enough to time, fill a buffer and pass that.

modules/compute/writeBufferBytes

writeBufferBytes(name: string, bytes: string, offset: number?): boolean

Write the raw bytes of a binary string to a named GPU buffer, verbatim. For packed binary that already matches a GPU layout (decoded vertex / index / record pools, file bytes, a network message) — no number-array round trip, unlike writeBuffer / writeBufferU32 which reinterpret each element as one f32 / u32.

modules/compute/writeBufferU32

writeBufferU32(name: string, data: buffer | { number }, offset: number?): boolean

Write unsigned integers to a named GPU buffer (little-endian u32 bytes). A buffer is copied across whole; an array is converted element by element.

modules/compute/writeFloatsTexture3D

writeFloatsTexture3D(name: string, floats: { number }, formatOrOpts: (string | { [string]: any })?): boolean

Upload float values into a named 3D volume, packed via the given format (default rgba16f).

modules/compute/writeTexture3D

writeTexture3D(name: string, data: { number }): boolean

Upload raw bytes (u8) into a named 3D volume.

modules/connected_users/README

connected_users

world.connectedUsers.* — server-replicated user registry. This module is user-scope and user-editable. The file you're reading is /zero/source/libs/@builtin/modules/connected_users.module/init.luau — anyone (any script, any agent, any user with VFS write) can rewrite it; hot-reload picks the change up and the engine uses the new version. There is no security boundary here. The read-only metatable below catches accidental writes loudly (API hygiene); it doesn't enforce anything. Trust boundaries that DO exist: - The Rust FFI (__connected_users.local_identity()) is registered into the VM state by the engine at boot and cannot be replaced from Luau. When it's called, it returns the real JWT sub. - The trusted VM (src/lua/trusted/**) is a separate Luau state, not on the user-writable VFS root, holds the auth.* namespace. User-scope Luau cannot reach it. - The server verifies the JWT on every RPC. Anything the server gates on flows through that check, not through whatever this module reports. What IS load-bearing: - __connected_users.local_identity() (Rust FFI) cannot be replaced. It always returns the real JWT sub of the engine's UserCredential, or nil for anonymous sessions. - The JWT bytes and session token NEVER cross any FFI exposed here; only the extracted identity string does. A malicious user-scope script can lie about identity but cannot exfiltrate the bearer token. - Authority on the server is established by the JWT the SDK presents on every request, not by anything this module reports. Multi-user replication (other connected users' records) plugs in on top of the same User metatable shape; today the registry contains only localUser because the boot path is the only populated source. The list/get/exists/count surface is shaped so it stays correct once server replication populates the remaining records.

modules/data_contract/README

data_contract

The dataContract field-constraint validator: a constrained value must be a .data instance whose dataType contract chain includes constraint.contract. Registers itself with the generic field_constraints registry on load.

modules/debugger/README

require("@builtin/modules/api/engine/debugger") -- debugger (also available as global 'debugger')

Luau debugger — breakpoints, stepping, stack inspection, watches. Public Luau surface over the __debugger Internal FFI namespace.

Usage: local debugger = require("@builtin/modules/api/engine/debugger") Also available as global: debugger

modules/debugger/__diagnostics

__diagnostics(): DebuggerDiagnostics

Internal diagnostic counters for debugging the debugger itself: { installs, debugbreakHits }.

modules/debugger/addWatch

addWatch(expr: string): number

Register an expression to re-evaluate on every pause.

modules/debugger/continue_

continue_(): boolean

Resume the paused thread.

modules/debugger/disableAll

disableAll()

Disable every registered breakpoint. Records persist; bytecode BREAK ops are cleared.

modules/debugger/disconnect

disconnect(handle: number): boolean

Disconnect an onBreak or onResume callback.

modules/debugger/enableAll

enableAll()

Enable every registered breakpoint and re-install them in the VM bytecode.

modules/debugger/evaluate

evaluate(expr: string, frame: number?): (string?, string?)

Evaluate an expression against the paused frame's environment. Returns (value, error).

modules/debugger/getLocals

getLocals(frame: number?): { [string]: string }

Locals captured at the active pause for the given frame index (1 = top). Values are stringified for safe display.

modules/debugger/getPauseInfo

getPauseInfo(): PauseInfo?

Info about the active pause, or nil if nothing is paused.

modules/debugger/getStack

getStack(): { Frame }

Captured stack from the active pause, top frame first. Empty when nothing is paused.

modules/debugger/getUpvalues

getUpvalues(frame: number?): { [string]: string }

Upvalues captured at the active pause for the given frame.

modules/debugger/getWatchValue

getWatchValue(id: number): (string?, string?)

Re-evaluate the watch expression against the paused frame's environment and return (value, error).

modules/debugger/getWatches

getWatches(): { Watch }

Snapshot of all watches with their last evaluated value and error, sorted by id.

modules/debugger/isPauseOnError

isPauseOnError(): boolean

Current pause-on-error toggle state for this VM.

modules/debugger/isPaused

isPaused(): boolean

Whether the debugger currently has a paused thread.

modules/debugger/listBreakpoints

listBreakpoints(): { Breakpoint }

Snapshot of every registered breakpoint, sorted by id ascending.

modules/debugger/onBreak

onBreak(fn: (PauseInfo) -> ()): number

Register a callback invoked on every pause with { path, line, reason }. Returns a handle usable with debugger.disconnect.

modules/debugger/onResume

onResume(fn: () -> ()): number

Register a callback invoked when the paused thread is resumed.

modules/debugger/removeBreakpoint

removeBreakpoint(id: number): boolean

Remove the breakpoint with the given id.

modules/debugger/removeWatch

removeWatch(id: number): boolean

Remove the watch with the given id.

modules/debugger/setBreakpoint

setBreakpoint(path: string, line: number, opts: BreakpointOpts?): Breakpoint

Set a breakpoint at line in the script at VFS path.

modules/debugger/setPauseOnError

setPauseOnError(enabled: boolean)

When true, uncaught Luau errors fire the onBreak callback (observation only — the error still propagates).

modules/debugger/stepInto

stepInto(): boolean

Run until the next line, descending into any function call.

modules/debugger/stepOut

stepOut(): boolean

Run until the current frame returns; pauses in the caller.

modules/debugger/stepOver

stepOver(): boolean

Run until the next line in the current frame. Calls inside the current line are skipped.

modules/debugger/toggleBreakpoint

toggleBreakpoint(path: string, line: number): Breakpoint?

Toggle a breakpoint at the given line: removes if present, adds otherwise.

modules/egress/README

require("@builtin/modules/api/engine/egress") -- egress (also available as global 'egress')

Credential-injecting HTTP for BYO-key world egress (Mechanism B). Public Luau surface over the __egress Internal FFI namespace.

Usage: local egress = require("@builtin/modules/api/engine/egress") Also available as global: egress

modules/egress/clearCredential

clearCredential(name: string): boolean

TRUSTED ONLY. Remove a named credential.

modules/egress/credentialNames

credentialNames(): { string }

List the names of configured credentials. Names only — secret values are never exposed to Luau.

modules/egress/fetch

fetch(name: string, method: string, url: string,

Perform an HTTP request with a named credential injected server-side (in Rust). Returns a promise handle for task.await(), or nil when the credential is unknown or url is outside the credential's allowed base_url. The secret is never exposed to Luau. This is the seam that production points at the ZeroMind egress endpoint.

modules/egress/hasCredential

hasCredential(name: string): boolean

Whether a named credential is configured. Returns only a boolean — never the value. Service handlers use this to fail with a clear "not configured" message.

modules/egress/setCredential

setCredential(name: string, base_url: string,

TRUSTED ONLY. Register a named credential whose header is injected into matching egress.fetch calls. The value is held in Rust and never returned to Luau.

modules/engine.dirty_hot_reload/README

require("@builtin/modules/engine/dirty_hot_reload") -- engine.dirty_hot_reload

Per-entity hot reload on <scene>.dirty/entities/. Subscribes to the layer's dirty entities directory via the generic vfs.watch(folder, callback) primitive and reapplies changed bodies to live entities — the consumer side of the collaborative-via-VFS-sync edit model. Local edits write a dirty file; VFS sync replicates the bytes to every peer's local VFS; the watcher fires for both Local and Remote writes; the callback reads the replicated file and applies it to the peer's live entity. No polling. Local writes fire watchers synchronously inside the FFI call. Peer-driven writes are queued in zero_vfs::write_events and drained by the engine schedule's apply_vfs_mutations system, which fires watchers within the same frame the bytes land. Cost is O(0) when nothing changes. Mark-suppression contract: Body application happens inside __scene_load.begin() / __scene_load.finish() so the Rust mark sites (mark_entity_dirty, mark_manifest_dirty) early-return — without the gate, applying a peer's edit would re-mark the entity dirty locally, the saver would re-write the same body to disk, VFS sync would re-broadcast back, and the feedback loop would saturate the relay. The same gate the scene loader uses on full-scene loads. Echo-loop suppression: Local writes round-trip through the watcher too (lua_vfs_write fires synchronously). The known per-eid content cache short- circuits re-apply when the file content matches the last-seen string — we just wrote it, no need to re-apply our own write. Lifecycle: layers.M.fireLoad(proxy) calls M.install(proxy) for every scene (additive or not) that just loaded — the install seeds known from current dir contents and registers one vfs.watch on <entitiesDir> (folder watch). M.fireUnload(proxy) calls M.uninstall(proxy) which calls vfs.unwatch(id) and drops the per-layer state. Authored content lives in additive overlays too (editor side-panels, side games, custom HUD overlays) — they get the same hot-reload treatment. Scenes that genuinely don't resolve to a dirty subtree (e.g. an engine-internal scaffolding overlay) early-return inside entitiesDirFor.

Usage: local engine.dirty_hot_reload = require("@builtin/modules/engine/dirty_hot_reload")

modules/engine.dirty_hot_reload/debugFire

debugFire(guid, path, kind)

Diagnostic: synchronously dispatch a synthetic write event for the given guid + path. Used by tests / debugging — production reads come from vfs.watch callbacks.

modules/engine.dirty_hot_reload/debugState

debugState(guid)

Diagnostic: return a shallow copy of the per-layer state for inspection. Returns { entitiesDir, knownKeys, active, watcherId, eventCount }.

modules/engine.dirty_hot_reload/install

install(sceneProxy)

Subscribe to the layer's dirty/entities/ directory via vfs.watch (folder-watch — fires for any descendant write/remove, both Local and Remote origins). Called automatically from layers.M.fireLoad for EVERY loaded scene (additive or root) — scenes don't need to install it manually. Scenes that don't resolve to a dirty subtree (engine-internal scaffolding overlays, persistent UI layers with no on-disk authoring path) early-return inside entitiesDirFor.

modules/engine.dirty_hot_reload/installed

installed()

Return the set of currently-installed layer guids. Observability hook for tests / debugging.

modules/engine.dirty_hot_reload/uninstall

uninstall(sceneProxy)

Stop the watcher for the given layer. Called from layers.M.fireUnload. Idempotent — safe to call when no install ran or after a prior uninstall.

modules/environment/README

require("@builtin/modules/api/engine/environment") -- environment (also available as global 'environment')

Environment / reflection capture — bake the scene into reflection-probe cube slots from world positions, persist them as faces6 .texture assets, and set per-probe blend data so surfaces reflect the nearest probe(s). Public Luau surface over the __environment Internal FFI namespace. The generic "render the scene into a cubemap from a point" capability the reflection probe system is built on.

Usage: local environment = require("@builtin/modules/api/engine/environment") Also available as global: environment

modules/environment/capture

capture(x: number, y: number, z: number): boolean

Bake the scene into the environment from (x, y, z) as the single global reflection (slot 0 + one full-coverage probe). Every PBR surface reflects it. Queued — takes effect on the next frame. For multiple proximity-blended probes use the reflectionProbe system instead.

modules/environment/captureSlot

captureSlot(slot: number, x: number, y: number, z: number): boolean

Bake the scene into reflection-probe slot (cube-array index) from (x, y, z). Renders the FULL scene (geometry + sky) six times from that point into that slot. Register the probe's position+radius via setProbes so surfaces blend it by proximity. Queued — takes effect next frame.

modules/environment/captureSlotToAsset

captureSlotToAsset(name: string, slot: number, x: number, y: number, z: number, timeoutFrames: number?): (string?, string?)

Bake the scene into reflection-probe slot from (x, y, z) AND persist the 6 rendered faces into a faces6 .texture cubemap asset at /source/<name>.texture/ (px/nx/py/ny/pz/nz PNGs + a cube.yaml sidecar). Survives an engine restart and syncs like any other texture. Yields a few frames while the bake + GPU readback complete; must be called from a task/coroutine context (component hook, task.spawn, or execute). NATIVE only — the wasm async-readback path is a tracked follow-up.

modules/environment/captureToAsset

captureToAsset(name: string, x: number, y: number, z: number): (string?, string?)

Bake the single global reflection AND persist it to a faces6 .texture asset (slot 0). Yields a few frames; call from a task/coroutine context.

modules/environment/loadFromAsset

loadFromAsset(name: string): (boolean, string?)

Load a persisted global reflection asset into slot 0 and make it the active single reflection (one full-coverage probe).

modules/environment/loadSlotFromAsset

loadSlotFromAsset(name: string, slot: number): (boolean, string?)

Load a persisted faces6 .texture cubemap (written by captureSlotToAsset) into reflection-probe slot WITHOUT re-rendering the scene. Reads the 6 face PNGs from /source/<name>.texture/ and uploads them into the slot's cube layers. How a persisted probe restores its baked environment on reload.

modules/environment/setProbes

setProbes(probes: { any }): boolean

Set the active reflection probes' blend data. probes is an array of { x, y, z, radius } (or { position = {x,y,z}, radius = r }); index i maps to cube slot i. Surfaces blend the probe slots by proximity to these positions, so objects reflect the nearest probe(s). Queued for next frame.

modules/field_constraints/README

field_constraints

Generic field-constraint dispatch. A Field descriptor may carry an opaque constraint table with a kind; the engine calls _G.__zero_check_field_constraint(value, constraint) on every write to (and default of) a constrained field. This module owns that hook and the validator registry — the engine core carries the constraint verbatim and never interprets it.

modules/field_constraints/register

register(kind: string, fn: (value: any, constraint: any) -> (boolean, string?))

Register the validator for a constraint kind. One validator per kind; re-registering a kind is an error (one path).

modules/fluidSurface/README

require("@builtin/modules/api/engine/fluidSurface") -- fluidSurface (also available as global 'fluidSurface')

Screen-space fluid surfaces for GPU particle sims: splat-rasterized depth/thickness/color, narrow-range depth filtering, and an HDR refraction/absorption/reflection composite that mirrors the scene in the water. Attach a named compute buffer of particle records and the engine renders it as a continuous liquid.

Usage: local fluidSurface = require("@builtin/modules/api/engine/fluidSurface") Also available as global: fluidSurface

modules/fluidSurface/__render

__render(ctx: any)

Internal: enqueue every attached surface's passes for this frame. Called by the fluidSurface render feature; content never calls it.

modules/fluidSurface/__teardown

__teardown()

Internal: feature teardown — detaches every surface.

modules/fluidSurface/attach

attach(name: string, spec: any): any

Attach a fluid surface: the named particle buffer renders as a continuous liquid every frame until detach. particles describes the record layout of a compute.createBuffer f32 array. Options: radius (world particle radius, required), stretch (velocity-stretch seconds, default 0.03), resolutionScale (fluid buffers as a fraction of screen resolution, default 0.5), fov (vertical degrees of the viewing camera for the smoothing footprint, default 60), smoothing (world-space smoothing width, default 2.5 radii), band (filter depth band, default 1.5 radii), iterations (filter passes, default 1), foamRadius (foam sprite radius as a multiple of radius, default 1.6), maxRadiusPx (cap on a splat's projected pixel radius so near-camera particles stay droplet-sized, default 48), and shading (composite property overrides — absorption, deep color, f0, specular, foam tint, sky tint, sun direction, and the reflection march: ssr_int blend, ssr_dist world reach, ssr_thick hit tolerance).

modules/fluidSurface/detach

detach(name: string)

Detach a surface: stops its passes and frees every RT, shader, and buffer it owns. The particle buffer itself belongs to the caller.

modules/fluidSurface/update

update(name: string, patch: any)

Update a live surface: particle count, radius, sunDir (world direction, projected for the composite), or any shading composite property. Cheap — call per frame for a moving sun or a growing sim.

modules/font/README

require("@builtin/modules/api/engine/font") -- font (also available as global 'font')

Font primitive — parse a font file ONCE into a baked, vectorized glyph format (ZFNT), then drive every text surface from it. A font is a general CPU resource addressed by name (the CPU counterpart of a renderer GPU resource), so a single registration is usable from UI text, 2D text, and true 3D glyph geometry. Public Luau surface over the __font Internal FFI namespace. Authored fonts are .font assets whose onRegister hook calls font.register.

Usage: local font = require("@builtin/modules/api/engine/font") Also available as global: font

modules/font/glyph

glyph(name: string, codepoint: number): any

Read one glyph's vectorized outline from a registered font, in font units (resolution-independent — scale by fontSize / unitsPerEm).

modules/font/list

list(): { string }

List every registered font family name.

modules/font/parse

parse(bytes: string): string?

Parse a font file (TTF / OTF raw bytes) ONCE into the baked, vectorized glyph format (ZFNT): per-glyph vector outlines + metrics + character map, plus the original bytes. Heavy — run at import time (the .font assetType's onCreate / the font importer), then store the result as the asset payload. font.register loads it cheaply.

modules/font/register

register(name: string, zfnt: string): any

Register a baked font (ZFNT from font.parse) under name, making it usable on every text surface via fontFamily = "<name>". Loads the vectorized glyph data into the runtime store (for font.glyph / font.textMesh) and feeds the embedded face to the 2D text and egui UI systems. Passing raw font bytes still works but logs a slow-path warning — bake with font.parse at import. Re-registering the same name replaces it.

modules/font/textMesh

textMesh(name: string, text: string, opts: table?): any

Tessellate a string into renderable mesh geometry from a registered font's glyph outlines — true 3D text, laid out left-to-right by advance (newlines drop a line). Hand the result to renderer.mesh.create() (GPU) or asset.create("mesh") (persistable).

modules/http/README

require("@builtin/modules/api/engine/http") -- http (also available as global 'http')

Async HTTP — GET/POST returning JSON or raw bytes. Public Luau surface over the __http Internal FFI namespace.

Usage: local http = require("@builtin/modules/api/engine/http") Also available as global: http

modules/http/get_bytes

get_bytes(url: string, headers: Headers?): PromiseId

Async HTTP GET returning raw bytes (binary-safe string). Suitable for piping into vfs.write to download a file.

modules/http/get_json

get_json(url: string, headers: Headers?): PromiseId

Async HTTP GET returning JSON. Returns a promise handle — wrap with task.await() to block until the response arrives.

modules/http/post_bytes

post_bytes(url: string, headers: Headers?, body: JsonBody?): PromiseId

Async HTTP POST returning raw bytes — use for APIs that accept JSON input but return binary output (audio, images).

modules/http/post_json

post_json(url: string, headers: Headers?, body: JsonBody?): PromiseId

Async HTTP POST returning JSON. Body is a Luau table; the FFI layer JSON-encodes it before the request goes out.

modules/http/request

request(method: string, url: string, headers: Headers?, body: JsonBody?): PromiseId

Async HTTP request with an arbitrary verb (GET/POST/PUT/PATCH/ DELETE/…) returning JSON. Body is a Luau table; an empty 2xx response resolves to an empty table.

modules/http/request_raw

request_raw(method: string, url: string, headers: Headers?, body: string?): PromiseId

Async HTTP request with an arbitrary verb and a RAW binary request body (a binary-safe string), for content-addressed blob uploads. The resolved value is the response body text.

modules/layers/README

layers (global)

Top-level scene-management namespace + Scene proxy. Owns the public layer-management API surface exposed on _G.layers. Pure Luau composition over internal __layers.* ECS-glue primitives; the LSP discovers the public shape via this --!global layers directive. Also available as global: layers

modules/layers/find

find(ref: AssetRef<scene> | string): any?

modules/layers/fireBeforeLoad

fireBeforeLoad(proxy: any): nil

modules/layers/fireLoad

fireLoad(proxy: any): nil

modules/layers/fireUnload

fireUnload(proxy: any): nil

modules/layers/install

install(): nil

modules/layers/is_loaded

is_loaded(ref: AssetRef<scene> | string): boolean

modules/layers/list

list(): { any }

modules/layers/load

load(ref: AssetRef<scene> | string, opts: LoadOpts?): any

Load a scene into the root non-additive slot ("main") OR as an additive overlay alongside it. Identity is ref-based: pass an AssetRef<scene> envelope (preferred — caught at the callsite by the LSP) or an identity string (resolved via asset.ref at entry, hard-error if no stable guid comes back). For non-additive, idempotency is by guid: re-loading the same scene logs and returns the existing proxy without tearing anything down. Different guid → unloads the current root + cascades every additive overlay it spawned + transitions the multiplayer room + loads the new scene. Logs every step at info level so a silent no-op is impossible.

modules/layers/loadInFlight

loadInFlight(): number

Returns the number of scene loads currently in flight (queued but not yet visible via onLoad dispatch). Returns 0 when the engine is in a stable load state. Used by engine.mode = ... to block flips while a load is mid-air; agents can read this to wait for a load to finish before driving the next operation.

modules/layers/offBeforeLoad

offBeforeLoad(h: number): boolean return remove(beforeLoadCbs, h) end

modules/layers/offEntityChanged

offEntityChanged(h: number): boolean

Remove a subscription made with layers.onEntityChanged.

modules/layers/offLoad

offLoad(h: number): boolean return remove(loadCbs, h) end

modules/layers/offUnload

offUnload(h: number): boolean return remove(unloadCbs, h) end

modules/layers/onBeforeLoad

onBeforeLoad(cb: (any) -> ()): number return push(beforeLoadCbs, cb) end

modules/layers/onEntityChanged

onEntityChanged(cb: (any) -> ()): number

modules/layers/onLoad

onLoad(cb: (any) -> ()): number return push(loadCbs, cb) end

modules/layers/onUnload

onUnload(cb: (any) -> ()): number return push(unloadCbs, cb) end

modules/layers/reload

reload(ref: (AssetRef<scene> | string)?): any?

modules/layers/unload

unload(refOrProxy: (AssetRef<scene> | string | any)?): nil

modules/library/README

require("@builtin/modules/api/engine/library") -- library (also available as global 'library')

Discover, check, and import library assets (@builtin/* and imported @namespace/* trees). Public Luau surface over the __library Internal FFI namespace.

Usage: local library = require("@builtin/modules/api/engine/library") Also available as global: library

modules/library/has

has(path: string): boolean

Check if a library asset exists at the given path.

modules/library/import

import(namespace: string, worldName: string)

Import a saved world as a library. The world directory at data/worlds/<worldName>/ is loaded and registered under the given namespace. After import, all files in the world are accessible via require('@namespace/path') and visible at /zero/source/libs/@namespace/.

modules/library/list

list(assetType: string?): { LibraryAsset }

List all available library assets. Optionally filter by asset type — call asset.categories() for the live set.

modules/logs/README

require("@builtin/modules/api/engine/logs") -- logs (also available as global 'logs')

Read-only query surface over the engine's in-memory log ring buffer. Public Luau surface over the __logs Internal FFI namespace.

Usage: local logs = require("@builtin/modules/api/engine/logs") Also available as global: logs

modules/logs/clear

clear(): boolean

Drop all buffered log entries. Lifetime per-level counts (logs.count) are preserved.

modules/logs/count

count(): LogCounts

Aggregate counters for the log ring. Lifetime counts survive eviction, so errors reflects the total seen even if the lines have scrolled out of the buffer.

modules/logs/errors

errors(limit: number?): { LogEntry }

Most-recent ERROR-level entries (newest first). limit defaults to 100.

modules/logs/find

find(text: string, limit: number?): { LogEntry }

Case-insensitive substring search over log messages. limit defaults to 200 (keeps the most recent matches).

modules/logs/query

query(opts: LogQueryOpts?): { LogEntry }

Query the engine's in-memory log ring. The live alternative to grepping data/logs/engine-<port>.log.

modules/logs/tail

tail(limit: number?): { LogEntry }

Most-recent entries of any level in chronological order. limit defaults to 100.

modules/logs/warnings

warnings(limit: number?): { LogEntry }

Most-recent WARN+ entries (newest first). limit defaults to 100.

modules/lsp/README

require("@builtin/modules/api/engine/lsp") -- lsp (also available as global 'lsp')

Embedded Luau language server — check / search / inspect Public Luau surface over the __lsp Internal FFI namespace.

Usage: local lsp = require("@builtin/modules/api/engine/lsp") Also available as global: lsp

modules/lsp/check

check(path: string, opts: CheckOpts?): { Diagnostic }

Validate a single .luau file in the VFS and return its diagnostics.

modules/lsp/checkAll

checkAll(opts: CheckAllOpts?): CheckAllResult

Validate the user's Luau scripts and return an aggregate summary plus diagnostic list. opts.scope = "user" (default) skips library mounts; "all" includes them. The sweep is time-budgeted (ZERO_EXECUTE_INLINE_BUDGET_MS, ~20s by default): if the budget elapses it returns the partial result gathered so far with budgetExceeded = true rather than blocking the engine.

modules/lsp/checkCode

checkCode(source: string, opts: CheckOpts?): { Diagnostic }

Validate inline Luau source without a backing file. Useful for checking code before writing it to disk.

modules/lsp/checkDirty

checkDirty(): { Diagnostic }

Drain the dirty-file set populated by the hot-reload hook, validate each, and return the combined diagnostic list.

modules/lsp/describe

describe(path: string): DocEntry?

Inspect a single documented entry. Returns the full doc table (signature, args, returns, examples, level), or nil. The path is resolved independently of which root the doc is registered under and of separator style, so the spelling that reads off the API surface (renderer.texture.create) finds the entry registered as globals/renderer/texture/create. When a path does not resolve, lsp.describePaths says what the registry holds near it.

modules/lsp/describePaths

describePaths(path: string): { string }

List the registered doc paths related to path. A path that names an entry returns every root it is registered under (the first is what lsp.describe resolves to); a path that names a namespace returns the entries registered under it. Empty when the registry holds nothing near the path — so a lookup that returns nil can always be turned into the list of what does exist.

modules/lsp/describeTool

describeTool(path: string): string?

Return the full documentation text for a code-mode tool.

modules/lsp/docsByKind

docsByKind(kind: string): { MethodSummary }

List every doc whose registration kind matches kind. Valid: "binding", "runtime_tool", "module", "component", "library", "lua_export".

modules/lsp/getStrictMode

getStrictMode(): StrictMode

Return the current strict mode.

modules/lsp/isStrict

isStrict(): boolean

Is the pre-execute LSP gate fully strict? False when off or in soft mode.

modules/lsp/lastCheckGen

lastCheckGen(): number

Generation counter — bumped each time the cache is rebuilt. UI polls this to know when to redraw.

modules/lsp/methods

methods(namespace: string, opts: MethodsOpts?): { MethodSummary } | { string }

List every documented method / entry under a namespace. A broad namespace (ui, renderer) returns a large dump by default, so two options narrow it: opts.filter keeps only methods whose name (or doc path) contains the substring, case-insensitively; opts.namesOnly returns a plain list of method-name strings instead of the full per-method summary tables — much smaller, and nothing to unwrap. Backward compatible: no opts returns every method's full summary.

modules/lsp/modules

modules(): { ModuleEntry }

List every Luau library module the engine currently knows about — discovered via --!module headers, library scans, and manually-recorded docs.

modules/lsp/namespaces

namespaces(opts: NamespacesOpts?): { NamespaceEntry }

List the documentation namespaces reachable from Luau. By default only namespaces exposing at least one PUBLIC method are returned, so the list matches what you can actually call — internal FFI plumbing (e.g. pause, native_entity), whose public surface lives elsewhere (engine.paused, the entity proxy, …), is left out. Pass { includeInternal = true } to list every namespace, internal ones included.

modules/lsp/readDirectives

readDirectives(source: string): DirectiveBlock

Parse the leading --! directive block of a Luau source string. Used by UIs that audit which files have skip directives and what they suppress.

search(query: string, opts: SearchOpts?): { MethodSummary }

Case-insensitive substring search across every registered doc's path, signature, and description.

modules/lsp/setStrict

setStrict(enabled: boolean): boolean

Toggle the pre-execute LSP gate. Returns true when the change was persisted to .world_settings, false when the play-mode write lock blocked the write.

modules/lsp/setStrictMode

setStrictMode(mode: StrictMode): boolean

Set the pre-execute strict gate's mode. Returns true when the change was persisted to .world_settings, false when the play-mode write lock blocked the write.

modules/lsp/summary

summary(): Summary

Counts only — does not re-run validation.

modules/lsp/tools

tools(): { ToolEntry }

List every code-mode tool registered in the VFS under /zero/docs/tools/<category>/<tool>.

modules/lsp/typeOf

typeOf(expr_source: string, context_path: string?): TypeDescriptor

Infer the static type of a Luau expression. When context_path is given, the file is loaded and walked so the inference env contains every local + alias in scope at its end.

modules/luau_coverage/README

require("@builtin/modules/api/engine/luau_coverage") -- luau_coverage (also available as global 'luau_coverage')

Luau source-based coverage dump (LCOV + sources manifest). Public Luau surface over the __luau_coverage Internal FFI namespace.

Usage: local luau_coverage = require("@builtin/modules/api/engine/luau_coverage") Also available as global: luau_coverage

modules/luau_coverage/dump_lcov

dump_lcov(path: string): DumpResult

Walk every Luau chunk loaded since the VM started (or since the last reset), collect per-line hit counts via Luau's built-in coverage API, render the aggregate as LCOV, and write it to path. Also writes a sibling luau_sources/ directory next to the LCOV file containing one source file per tracked chunk plus a manifest.json mapping chunk names to filenames — used by the proxy /ui/coverage page for per-file source drill-down. Raises a Luau error on failure.

modules/luau_coverage/level

level(): number

The coverageLevel the Luau compiler was configured with (1 = statement coverage, 2 = statement + expression). Returns 0 when coverage is off.

modules/luau_coverage/reset

reset(): boolean

Drop every tracked chunk ref and clear the coverage accumulator. The VM stops reporting hits for previously loaded scripts — use before exercising a specific scenario to isolate its coverage.

modules/luau_profile/README

require("@builtin/modules/api/engine/luau_profile") -- luau_profile (also available as global 'luau_profile')

Sampling profiler + manual regions for Luau scripts. Public Luau surface over the __luau_profile Internal FFI namespace.

Usage: local luau_profile = require("@builtin/modules/api/engine/luau_profile") Also available as global: luau_profile

modules/luau_profile/begin

begin(name: string): number

Open a named manual region. Returns an opaque integer id; pass it back to end_region(id) to close and record elapsed wall-clock under name.

modules/luau_profile/dump

dump(path: string): DumpResult

Write the folded-stack dump to path, one line per stack as <ticks> <stack_csv> — the format upstream Luau emits and tools/perfgraph.py consumes unchanged.

modules/luau_profile/dump_regions

dump_regions(path: string): DumpRegionsResult

Write per-region stats to path as JSON.

modules/luau_profile/end_region

end_region(id: number)

Close a region previously opened by begin(name). Records elapsed wall-clock under the region's name. Silently no-ops on unknown id (typically a double-close or swapped-out VM).

modules/luau_profile/is_running

is_running(): boolean

True iff the background sampler is currently running.

modules/luau_profile/reset

reset()

Clear every accumulated sample and region stat. The sampler keeps running if it was already on; only the data is wiped.

modules/luau_profile/sampling_available

sampling_available(): boolean

True on platforms where the background sampler can run (native targets), false on WASM. Manual regions work everywhere — only the sampler is platform-gated.

modules/luau_profile/snapshot

snapshot(top_n: number?): Snapshot

Snapshot the current accumulator without touching the filesystem — cheap enough for per-frame UI polling. top_n truncates stacks to the N hottest entries; omitting it returns all stacks sorted descending by self_us. regions is always returned in full (sorted by total_us).

modules/luau_profile/span<T...>

span<T...>(name: string, fn: () -> T..., ...): T...

Call fn(...) inside a manual region named name. The region is closed even if fn raises (the call goes through pcall internally). Returns whatever fn returned.

modules/luau_profile/start

start(hz: number?): StartResult

Start the background Luau sampling profiler at hz samples per second (default 1000, clamped to [1, 100000]). Idempotent — calling while already running is a no-op. Returns { available, hz }available = false on WASM (no std::thread). Manual regions work regardless.

modules/luau_profile/stop

stop()

Stop the background sampler. Blocks until the sampler thread joins (typically <1ms). Safe when not running. Does not clear the accumulator — call reset() to drop samples.

modules/mathx/README

require("@builtin/modules/api/engine/mathx") -- mathx (also available as global 'mathx')

Batch math kernels over Buffer slices (damp, lerp/slerp, transform). Public Luau surface over the __mathx Internal FFI namespace.

Usage: local mathx = require("@builtin/modules/api/engine/mathx") Also available as global: mathx

modules/mathx/addScaledVec3

addScaledVec3(dstBuffer: number, srcBuffer: number,

dst[i] += src[i] * scale for count vec3 elements. Both buffers must hold at least count * 3 floats. Useful for particle integration (position += velocity * dt) and accumulator passes.

modules/mathx/dampScalar

dampScalar(buffer: number, offset: number, count: number,

Critically-damped exponential approach toward target for count scalars at buffer[offset .. offset+count]. smoothTime is the time constant (~ 0.16 ⇒ ~63% per frame at 60 Hz). Pass smoothTime <= 0 to snap to the target.

modules/mathx/lerpVec3

lerpVec3(buffer: number, offset: number, count: number,

Element-wise linear blend of count vec3s in buffer[offset .. offset+count*3] toward (tx, ty, tz) by t.

modules/mathx/normalizeQuat

normalizeQuat(buffer: number, offset: number, count: number): boolean

Re-normalise count quaternions in place. Zero-length quats become identity (0, 0, 0, 1) so downstream code never sees NaN.

modules/mathx/slerpQuat

slerpQuat(buffer: number, offset: number, count: number,

Slerp count quaternions (xyzw) at buffer[offset..] toward (tx, ty, tz, tw) by t. Falls back to nlerp+normalize for very-close quats. Always picks the shortest-arc path.

modules/mathx/transformVec3

transformVec3(buffer: number, offset: number,

Treat each vec3 in buffer[offset..] as a position (w = 1), multiply by the 4x4 column-major matrix mat16 (16-element array), write .xyz of the result back. Layout matches glam, wgpu, and GLSL conventions.

modules/mcpLog/README

require("@builtin/modules/api/engine/mcpLog") -- mcpLog (also available as global 'mcpLog')

Read-only MCP tool-call log ring buffer. Public Luau surface over the __mcpLog Internal FFI namespace.

Usage: local mcpLog = require("@builtin/modules/api/engine/mcpLog") Also available as global: mcpLog

modules/mcpLog/clear

clear(): boolean

Clear all entries from the engine's MCP log ring buffer.

modules/mcpLog/query

query(limit: number?): { McpLogEntry }

Return the most-recent MCP tool-call entries from the engine's MCP log ring buffer (newest last). Pass limit to cap how many entries are returned — omit for the full ring (up to 500 entries).

modules/modelImport/README

require("@builtin/modules/api/engine/modelImport") -- modelImport (also available as global 'modelImport')

The model-import pipeline: decompose any assimp-supported 3D model (fbx, obj, dae, gltf, glb, stl, ply, 3ds, …) into meshes, materials, textures, animation clips and a node graph; extract a single clip to its .zanim payload; and derive a .rig from a skinned mesh or from an animation-only file's driven skeleton. Public Luau surface over the __model and __rig Internal FFI namespaces.

Usage: local modelImport = require("@builtin/modules/api/engine/modelImport") Also available as global: modelImport

modules/modelImport/decompose

decompose(bytes: string, format: string): string

Parse raw model bytes on a background thread. format is the real source extension ("fbx", "obj", "dae", "gltf", "glb", "stl", "ply", "3ds", …), forwarded to assimp as the format hint. Returns a promise handle: task.await it, then read the data with result(handle).

modules/modelImport/decomposeFiles

decomposeFiles(files: { ModelFile }, mainName: string): string

Parse a model plus its companion files on a background thread, so assimp resolves the model's external references (a .gltf's external .bin and image files, an .obj's .mtl colors/textures, MD5's .md5anim, …). files is an array of { name = basename, bytes = <bytes> } that MUST include the model file itself; mainName is that file's basename. Returns a promise handle: task.await it, then read the data with result(handle) — the same shape decompose produces.

modules/modelImport/extractAnimation

extractAnimation(sourcePath: string, clipName: string): string

Read a model source file and extract one animation clip to its .zanim payload, stashed for retrieval. The source extension decides the parser, so this is format-agnostic. Returns a promise handle: task.await it, then extractAnimationResult(handle) returns the bytes.

modules/modelImport/extractAnimationResult

extractAnimationResult(handle: string): string?

After awaiting an extractAnimation handle, return the extracted .zanim bytes (binary-safe), consuming them. The bytes to hand to asset.create("animation", name, { bytes }). Returns nil on failure or if already taken.

modules/modelImport/result

result(handle: string): any?

Read the decomposed model after decompose's handle has been awaited. Every format decomposes into the same shape, so this is format-agnostic. Runs on the main thread; consumes the stored result.

modules/modelImport/retryHandle

retryHandle(makeHandle: () -> any, retries: number?, yield: (() -> ())?): string?

Call makeHandle — which returns a promise-handle string, or a falsy value on a transient failure (e.g. a source read that raced a pending write during a parallel import) — up to retries + 1 times, yielding via yield between attempts so a pending write can land before the next try. Returns the handle string once one is produced, or nil when every attempt failed. Callers task.await the result only when it is non-nil, so a transient miss never reaches task.await as a non-string.

modules/modelImport/rigFromMeshSkin

rigFromMeshSkin(meshBytes: string): string?

Lift the skeleton out of a skinned .mesh (ZMSH) payload and return it as a .rig JSON document: bones (hierarchy, rest pose, inverse-bind), the auto-derived humanoid profile, and the humanoid classification. The source rig a skinned mesh's clips retarget through. Returns nil when the bytes are not a mesh or carry no skin.

modules/modelImport/rigFromSkeleton

rigFromSkeleton(skeleton: AnimationSkeleton): string?

Build a .rig JSON document from the skeleton an animation-only file was authored on — result(handle).skeleton, the bones its clips drive with their local rest transforms. Forward kinematics over the locals resolves globals + inverse-bind; the humanoid profile + classification are derived as for rigFromMeshSkin. The source rig a standalone clip retargets through. Returns nil on malformed input.

modules/multiplayer/README

require("@builtin/modules/api/engine/multiplayer") -- multiplayer (also available as global 'multiplayer')

Multiplayer sync state and operations — connection, peers, ownership, rooms, undo/redo, mode. Public Luau surface over the __multiplayer Internal FFI namespace.

Usage: local multiplayer = require("@builtin/modules/api/engine/multiplayer") Also available as global: multiplayer

modules/multiplayer/beginOperation

beginOperation(description: string)

Begin recording an undoable operation. All mutations until commitOperation() are grouped into one undo entry.

modules/multiplayer/canRedo

canRedo(): boolean

Check if this client has any redoable operations.

modules/multiplayer/canUndo

canUndo(): boolean

Check if this client has any undoable operations.

modules/multiplayer/cancelOperation

cancelOperation()

Cancel the current operation and restore all properties to their values at begin time.

modules/multiplayer/claimOwnership

claimOwnership(entityId: string?): boolean

Request ownership of an entity. Returns true if the claim was tentatively granted (relay confirmation pending).

modules/multiplayer/commitOperation

commitOperation()

Finalize the current operation and push it onto the undo stack. Only changes that actually differ from the start state are recorded.

modules/multiplayer/connect

connect(relayUrl: string)

Connect to a multiplayer relay server for the current world. Uses the loaded world's world_id as the room prefix for scene isolation. A world must be loaded before connecting.

modules/multiplayer/disconnect

disconnect()

Disconnect from the multiplayer relay server.

modules/multiplayer/getDiagnostics

getDiagnostics(): SyncDiagnostics

Get sync diagnostics — bandwidth, latency, peer count, top consumers.

modules/multiplayer/getPeerId

getPeerId(): number?

Get this client's peer ID in the current session.

modules/multiplayer/getPeers

getPeers(): { PeerInfo }

Get a list of all connected peers in the current session.

modules/multiplayer/getTickRate

getTickRate(): number

Get the current sync tick rate (network updates per second).

modules/multiplayer/isConnected

isConnected(): boolean

Check if a multiplayer session is active and connected to a relay.

modules/multiplayer/isHost

isHost(): boolean

Whether THIS client is the host (authoritative owner) of the current scene's play room — the relay room CREATOR, or offline / single-player. Host code spawns the shared synced world (via entity.spawnSynced or a scene's onHostLoad) and runs authoritative simulation; a non-host (JOINER) receives that content from the relay snapshot and must NOT re-create it. Gate ANY code that spawns synced entities or owns shared state with this so it runs on exactly one client — running it on every peer is the double-spawn 'explosion'.

modules/multiplayer/isOwner

isOwner(entityId: string?): boolean

Check if the local client owns the given entity (or the current entity if called from a component). Only the owner can modify synced properties directly.

modules/multiplayer/isRoomCreator

isRoomCreator(roomKey: string): boolean?

Whether this client created the given room — it was the FIRST peer to join it (race-free; the relay assigns it on join). In play mode the creator instantiates the scene's entities (synced) and every other joiner receives them from the relay snapshot, so the scene is never double-instantiated.

modules/multiplayer/joinRoom

joinRoom(roomKey: string)

Join a relay room. Room keys are built as {worldGuid}/{mode}/{sceneGuid} and partition the relay's fan-out — only peers in the same room receive each other's broadcasts. No-op when not connected or already joined.

modules/multiplayer/leaveRoom

leaveRoom(roomKey: string)

Leave a relay room. No-op when not connected or not joined.

modules/multiplayer/loopback

loopback(): { [string]: any }

Loopback testing harness. Returns a table with enable(), disable(), flush(), receive() methods for testing sync without a relay server.

modules/multiplayer/on

on(channel: string, callback: (number, ...any) -> ())

Subscribe to a custom message channel. The callback runs as callback(fromPeerId, ...args) whenever another peer calls multiplayer.send(channel, ...). Multiple callbacks per channel fire in registration order.

modules/multiplayer/redo

redo(): boolean

Redo this client's last undone operation.

modules/multiplayer/releaseOwnership

releaseOwnership(entityId: string?): boolean

Release ownership of an entity.

modules/multiplayer/send

send(channel: string, ...: any)

Broadcast a message on a named channel to every OTHER peer in the room. The relay forwards it transparently; peers receive it via multiplayer.on. Arguments may be any synced value (nil, boolean, number, string, Vec3, entity/component proxy, or table) and are delivered to listeners in order. No-op when not connected.

modules/multiplayer/undo

undo(): boolean

Undo this client's last edit-mode operation.

modules/notices/README

require("@builtin/modules/api/engine/notices") -- notices (also available as global 'notices')

Post an event record that surfaces to the operating agent on its next tool call. Public Luau surface over the __notices_post Internal FFI global.

Usage: local notices = require("@builtin/modules/api/engine/notices") Also available as global: notices

modules/notices/post

post(template: string, params: { [string]: any }?, opts: NoticeOpts?)

Post a notice. template is a fixed sentence used to collapse repeats; put varying values in params. opts.severity defaults to "info"; opts.includeLocation attaches the emitting call site.

modules/packages/README

require("@builtin/modules/api/engine/packages") -- packages (also available as global 'packages')

Discover and inspect registered packages (folders marked with package.yaml). Public Luau surface over the __packages Internal FFI namespace.

Usage: local packages = require("@builtin/modules/api/engine/packages") Also available as global: packages

modules/packages/list

list(): { PackageEntry }

List every registered package across scopes.

modules/packages/lookup

lookup(name_or_scope: string, name: string?): PackageEntry?

Look up a single package by name (any scope) or by exact (scope, name).

modules/persist/README

persist

modules/persist_origin/README

persist_origin

Provenance / origin-context layer for the persist (create-flow) system. origin kinds: "execute" — agent ad-hoc (the default when nothing else is on the stack) "scene" — scene loader / entrypoint onLoad (__scene_load.inProgress()) "component" — a script-component lifecycle callback (awake/update/...)

modules/persist_player_camera/README

persist_player_camera

Freeze the LIVE player + camera into a scene's player/camera config. The persist north star is "what you see is what you get": the freeze PRESERVES the exact live state, it never synthesizes. PLAYER. The live player body is layers.active.players.localPlayer.avatar — whatever entity is in that slot right now, however it was built (procedurally spawned, instantiated, hand-assembled). The scene CONFIG (player.avatar_<mode>) is a BUNDLE ref the spawner instantiates on the next load. So the freeze CONVERTS the live avatar entity-tree into a bundle and points the config at it: * If the live avatar is an UNMODIFIED instance of an existing bundle (its bundleProvenance carries exactly one source bundle with no added components/entities), reference THAT bundle — don't duplicate. * Otherwise compose the live tree into a bundle (new on first freeze, re-composed in place on re-freeze) and reference it. The entity-tree -> bundle work goes through the bundle assetType's own composeTemplate path: first freeze = one-step asset.create("bundle", name, { entity = ref }) (onCreate composes the live tree), re-freeze = bundleRef:update(entityId) in place. Both capture LIVE component state via serialized component snapshots — exactly what's on screen. CAMERA. The live primary camera's behavior is a COMPONENT (the agent authors one to drive the camera); the freeze references it as camera.behavior_<mode>. Camera parameters (fov/near/far/follow) are baked into the live Camera component and preserved with it. No synthesis. Bundle CREATION is side-effecting, so it runs from confirm(), never plan() (a cancelled plan must leave no orphan bundle in /source).

modules/persist_serializer/README

persist_serializer

Live-state serializer driver for persist. The missing half of the scene-save story: scene_saver.serializeEntity already turns a LIVE entity into the v6 per-entity body, but it is only ever fed ids from the EDIT-mode dirty-mark queue (empty in play). This driver supplies the play-mode side — it ENUMERATES the active layer's live entities, filters them by PROVENANCE (execute-origin only, via persist.origin) + authored-ness (scene_saver's spawner-managed / temporary filter), and assembles a loadable v6 scene body. Lighting stays ENTITY-PRIMARY: lighting comes from Light components on entities, which serialize as normal entities — so we deliberately do NOT emit scene_saver's global lighting manifest block (it would compete with / override the entity lights). Only the player + camera config blocks are carried over.

modules/playerSetupValidation/README

require("@builtin/modules/api/engine/playerSetupValidation") -- playerSetupValidation

Agent-facing validation for authored player setups. A player-spawn scene is built from PlayerSpawn entities (each naming a PlayerPrototype subtree to instantiate on join) and PlayerPrototype roots (marked PrototypeOnly, with a body and a camera field each naming a DESCENDANT of the prototype — the entity adopted as the avatar and the entity carrying the player's Camera). The prototype is cloned as a self-contained subtree, so both refs must resolve inside it and it may hold no camera other than the one camera names. checkEntity inspects one such entity and returns human-readable messages that name what is wrong and what to do about it; checkScene validates the scene's player intent against its PlayerSpawn / Camera counts. checkActiveScene runs the full rule set across the live active-layer scene; checkSceneJson runs it statically over a scene.json document — the path the scene assetType's validate hook uses so asset.validate, worldValidation, and the world.push gate all catch a broken player setup at authoring time.

Usage: local playerSetupValidation = require("@builtin/modules/api/engine/playerSetupValidation")

modules/playerSetupValidation/checkActiveScene

checkActiveScene(): { { entity: string, message: string } }

Gather every player-setup validation message for the live active-layer scene: the per-entity rules across all PlayerSpawn / PlayerPrototype entities, the competing-camera rule, and the scene-intent rule. Returns a flat list an agent can read to see what to fix.

modules/playerSetupValidation/checkEntity

checkEntity(entityId: string): { string }

Validate a single PlayerSpawn or PlayerPrototype entity, returning agent-facing messages naming what is wrong and what to do. An entity carrying neither component (or one that does not exist) yields no messages.

modules/playerSetupValidation/checkScene

checkScene(opts: { playerIntent: string, spawnCount: number, cameraCount: number }): { string }

Validate a scene's player intent against its PlayerSpawn / Camera counts, returning agent-facing messages. A "spawns" scene with no PlayerSpawn, or a "none" scene with no Camera, yields a message; every other combination is clean.

modules/playerSetupValidation/checkSceneJson

checkSceneJson(sceneJson: { [string]: any }): { { code: string, severity: string, message: string } }

Validate a decoded scene.json document statically: the full player-setup rule set (per-entity, competing-camera, scene-intent) run over the scene's authored entity tree without loading it. This is what the scene assetType's validate hook calls, so asset.validate / worldValidation / the world.push gate all report a broken player setup at authoring time.

modules/playerSetupValidation/playReadinessProblems

playReadinessProblems(): { { entity: string, message: string } }

The player-setup problems that must block a flip into play: the per-entity spawn/prototype rules (body + camera refs set, resolving to descendants, a single referenced camera) and the scene-intent rule, over the LIVE active scene. A "spawns" scene with none of these problems is ready to play. The competing-camera rule is deliberately excluded — the editor's own free-fly camera is a live viewport camera outside every prototype, so running it here would false-positive on every edit session; that rule stays a static / publish-time concern. Empty for a non-"spawns" scene (no player requirement).

modules/player_lifecycle/README

player_lifecycle

Bridges the UserIdentity component's awake / onDestroy into the per-scene players registry. Installed by the prelude. The UserIdentity component requires this module and calls playerJoined(eid) in awake, playerLeft(eid) in onDestroy, and localAvatarBound(eid, av) when the local avatar slot fills. Each routes into layers.active.players._addPlayer / _removePlayer / _onLocalAvatarBound, which update the per-scene set AND fire onPlayerJoined / onPlayerLeft in one atomic step. onLocalReady is fired separately when the joining entity matches world.connectedUsers.localUser.entity. install() also registers __layers_* dispatch channels the player_spawner uses.

modules/player_prototype_spawn/README

require("@builtin/modules/api/engine/player_prototype_spawn") -- player_prototype_spawn

The deterministic spawn core for authored player prototypes. A PlayerSpawn entity names a PlayerPrototype subtree to instantiate; spawnFor clones that subtree, activates and reveals the clone, prunes clone-subtree nodes whose networkScope excludes the caller's owner/authority role, marks the clone RuntimeOnly, and turns the clone ROOT into the joining user's hidden identity: it removes the PlayerPrototype / PlayerSpawn markers from the root and adds the UserIdentity component, so the root plugs into the existing players registry / join-leave / ownership / sync as a first-class local player. The clone root's authored children are detached to standalone world entities — nothing is parented to the identity. The joining user's avatar is the body named by the root's PlayerPrototype.body ref; it is positioned at the PlayerSpawn's world transform (placement at_spawn_transform) and bound by assigning the identity's avatar, which links it back through PlayerAvatar.owner. The clone's Camera node follows the bound body. Runtime provenance attributes (source prototype, owner user, owner player) are stamped on the root and read back with runtimeSpawnedInfo. The attributes are runtime-only: they live on the live entity and are not serialized into the world. installJoinHook wires spawnFor to a scene's user-join event so a joining user is placed from a PlayerSpawn automatically.

Usage: local player_prototype_spawn = require("@builtin/modules/api/engine/player_prototype_spawn")

modules/player_prototype_spawn/applyNetworkScope

applyNetworkScope(cloneRootId: string, isOwner: boolean, isAuthority: boolean)

Prune a clone subtree by each node's networkScope against the caller's role. Walks the subtree from cloneRootId; a node scoped OwnerOnly is despawned when the caller is not the owner, AuthorityOnly when the caller is not the authority, and Replicated (or any other value) is kept.

modules/player_prototype_spawn/captureTemplatesFromEntities

captureTemplatesFromEntities(entities: { any })

Build the prototype-template registry from a scene's authored entity records (the parsed scene data, not live entities). This is the primary capture path: it is independent of scene-load order and runtime composition, so it captures the clean authored subtree (no composed avatar) and works in the runtime profile, which boots straight to play. Called by the scene loader for v7 scenes.

modules/player_prototype_spawn/chooseSpawn

chooseSpawn(ctx): (string?, { [string]: any }?, string?)

Pick the PlayerSpawn-carrying entity to spawn from. Enumerates entities carrying the PlayerSpawn component in the joining user's ROOT scene, skipping any that live in an additive overlay layer (editor UI, HUD scenes). When the root scene resolves (ctx.rootSceneGuid, else layers.active.guid), only spawns in that scene's layer are considered; otherwise every non-overlay spawn is eligible. Spawns with no layer attribution yet belong to the world root and stay eligible either way. Honors an optional ctx.spawnId override (used for deterministic selection), otherwise returns the first matching spawn.

modules/player_prototype_spawn/clearJoinHook

clearJoinHook(guid: string)

Clear a scene's join-hook flag. Called when a "spawns" scene unloads so the once-registered connect / play-entry handlers stand down (they no-op while no wired scene remains). Idempotent for an unknown guid.

modules/player_prototype_spawn/installJoinHook

installJoinHook(sceneProxy): boolean

Wire spawnFor to the world's connected-user join event. When a user connects, the hook picks a PlayerSpawn and instantiates that user's prototype instance (hidden identity + avatar + camera-follow) via spawnFor. The trigger is world.connectedUsers.onConnect — the WORLD-level "a user joined the session" event — not the room players registry, so the hidden identity the clone becomes (which folds into that registry) does not re-trigger a spawn. Entering play spawns every already-connected user (their onConnect fired in edit, ignored then). A scene wired while ALREADY in play — the runtime profile boots straight into play, or a scene swapped in mid-play — gets that same sweep immediately, since no play flip follows to trigger it. Every spawn path is per-user idempotent: a user who already owns a live clone is skipped, so overlapping paths and re-flips never produce a second player. Idempotent per scene proxy: a second call for the same scene installs nothing further.

modules/player_prototype_spawn/runtimeSpawnedInfo

runtimeSpawnedInfo(id: string): { [string]: any }?

Read back the runtime provenance stamped on a clone root by spawnFor.

modules/player_prototype_spawn/spawnFor

spawnFor(ctx): string?

Spawn a player instance for a joining user from the chosen PlayerSpawn's prototype. Chooses a spawn (honoring ctx.spawnId), resolves and validates its prototype, clones the prototype subtree, activates and reveals the clone, prunes it by networkScope against the caller's owner/authority role (both default true), marks the clone RuntimeOnly, stamps provenance attributes, places the clone at the spawn's world transform, and registers it with the prototype lifecycle so it is despawned on the return to edit.

modules/player_prototype_spawn/storePrototypeTemplate

storePrototypeTemplate(prototypeId: string)

Capture a PlayerPrototype's authored subtree into the template registry. Called by PlayerPrototype.awake (before its Asset composes and before it deactivates) so spawnFor can instantiate the authored structure per player.

modules/player_prototype_spawn/userHasSpawnedPlayer

userHasSpawnedPlayer(userId: string?): boolean

Whether a live clone spawned by spawnFor already carries this user's owner provenance. Scans the live entities for a root whose ownerUserId attribute matches. The idempotency guard the auto-spawn paths use so a user who already has a spawned player never gets a second one.

modules/player_spawner/README

player_spawner

Binds the per-mode avatar bundle to the engine-default identity entity on non-additive layers.onLoad, for legacy v6 scenes. The avatar ref resolves as: per-scene settings.player.avatar_<mode> when set, else the world default world.avatar_default_<mode>. Missing-but-required is log.error + skip — no hardcoded fallback bundle. A single avatar slot combines visual + controller in one bundle. It spawns a fresh body entity from that bundle, then binds it by assigning the identity's avatar field, which marks the body synced + PlayerOwned and attaches its PlayerAvatar link. Scene-level avatar_<mode> = "" is the explicit opt-out — the scene builds its own body in entrypoint.luau::onLocalReady.

modules/players/README

players

Per-scene players registry. Tied to a non-additive Scene proxy. Reached as layers.active.players or layers.find(name).players. Returns curated player handles — never entity or component proxies — so the caller reads/writes player data (.userId, .displayName) and reaches the body via player.avatar (an entity ref) uniformly across all consumers. Both colon (p:onJoin(cb)) and dot (p.onJoin(cb)) call styles are supported on every method; the registry is a namespace surface, not an object, so neither style is canonical. Additive layers do NOT carry a players surface today (multiplayer support for players living inside additive overlays is a follow-up — see the layers.module additive-scene comment). layers.find(<additive>).players returns nil; this module is only instantiated on the root scene proxy.

modules/postprocess/README

require("@builtin/modules/api/engine/postprocess") -- postprocess (also available as global 'postprocess')

Fullscreen post-process effects — register / remove / toggle / named-property updates / list. Public Luau surface over the __postprocess Internal FFI namespace.

Usage: local postprocess = require("@builtin/modules/api/engine/postprocess") Also available as global: postprocess

modules/postprocess/add

add(name: string, source: string, opts: PostprocessOpts?): boolean

Queue registration of a fullscreen post-process effect. The effect is applied on the next frame. source is WGSL providing ONLY fn fragment(in: PostInput) -> vec4<f32>; the engine generates the group(0) framework + schema-driven group(1) from opts.properties. Effects run in priority order (lower first, default 100).

modules/postprocess/list

list(): { string }

List all registered post-process effect names in renderer priority order (lower priority runs first).

modules/postprocess/remove

remove(name: string): boolean

Queue removal of a post-process effect. Takes effect on the next frame. Removing a name that isn't registered is a silent no-op.

modules/postprocess/setEnabled

setEnabled(name: string, enabled: boolean): boolean

Queue an enable/disable toggle on a registered post-process effect. Targeting an unknown name is a silent no-op.

modules/postprocess/setProperty

setProperty(name: string, prop: string, value: (number | { number })): boolean

Queue a named material-property update on a registered post-process effect. The property must be declared in the effect's properties schema; read in WGSL as material.<prop>. value is a number or a number array (vec/color). Targeting an unknown effect or an undeclared property is a silent no-op.

modules/postprocess/setSampler

setSampler(name: string, opts: { [string]: any }): boolean

Configure the per-effect user sampler shared by the effect's declared texture properties. opts.filter = "linear" (default) or "nearest". opts.wrap (alias .address) = "clamp" (default), "repeat", or "mirror" — applied to all axes.

modules/postprocess/setTexture

setTexture(name: string, prop: string, path: string): boolean

Bind a texture to one of an effect's declared texture properties. Declare it in properties ({ name = "noise", type = "texture" }) and sample in WGSL as textureSample(noise, noise_sampler, in.uv). path is any TextureCache-resolvable spec (@builtin::textures.foo, color:1,0,0, default:white, a render-target name, ...). Targeting an unknown effect / property or unresolvable path silently no-ops.

modules/profiler/README

require("@builtin/modules/api/engine/profiler") -- profiler (also available as global 'profiler')

Frame-level profiler capture, EMA stats, and named profiling blocks. Public Luau surface over the __profiler Internal FFI namespace.

Usage: local profiler = require("@builtin/modules/api/engine/profiler") Also available as global: profiler

modules/profiler/begin

begin(name: string)

Start a named profiling block. Call profiler.finish(name) to record the duration. Blocks appear in profiler.stats() under "script.<name>" and inside captures.

modules/profiler/disableRing

disableRing()

Disable the ring buffer and clear its history.

modules/profiler/enableRing

enableRing(seconds: number?): boolean

Enable the always-recording ring buffer, retaining the last seconds of per-frame data (default 20). Query it AFTER the fact with profiler.retro() — latency-immune, since the data is historical. Editor profile only: returns false in the runtime profile. The enable is the gate; the ring costs nothing until on.

modules/profiler/finish

finish(name: string?): number?

Finish a profiling block and record the elapsed duration as "script.<name>". Without an argument, closes the most-recently- begun block (LIFO stack). With a name, closes the most recent block whose name matches — useful when blocks of different names are nested.

modules/profiler/gpuFrame

gpuFrame(): GpuFrameReport

Label-aggregated GPU pass timings of the most recent resolved frame, measured with GPU timestamp queries. supported is false when the device lacks timestamp queries — spans stays empty. Each span sums every render/compute pass recorded under one label that frame (count passes): compute.<shader> per compute dispatch, scene.* for the scene passes, post.<effect> per post-process effect, feature.* for render-feature passes. The GPU may overlap passes, so total_ms can exceed frame_span_ms (first pass begin to last pass end). The readback is asynchronous: the report lags the live frame by a few frames. Steady per-frame workloads read reliably; a lone one-shot dispatch submitted in isolation can under-report on some drivers.

modules/profiler/hits

hits(label: string?): string?

Drain the watchdog's recorded hit frames into a capture stored under label (default "watch_hits") and clear the buffer. Returns the capture JSON (same shape as stopCapture), or nil if there were no hits.

modules/profiler/isCapturing

isCapturing(): boolean

Check if a profiler capture is currently active.

modules/profiler/lastCapture

lastCapture(): string?

Get the most recent completed capture result as a JSON string. Same shape as profiler.stopCapture(). Returns nil if no capture has been completed yet.

modules/profiler/measure<T...>

measure<T...>(name: string, fn: () -> T...): T...

Run a function inside a profiling block. Equivalent to a begin/finish pair but handles errors correctly. Returns the function's return values.

modules/profiler/retro

retro(seconds: number?, label: string?): { [string]: any }?

Retroactively aggregate the last seconds of the ring (default: the whole ring). The full per-frame capture is retained under label (default "retro") for in-engine drill-down (profiler.frame/hotspots); this RETURNS a compact structured aggregate table (frame-time distribution

  • per-system summary), never the raw per-frame array — bounded, so it is safe over the ZeroMind bridge. Code-facing primitive; the retro tool renders the agent-facing report. Latency-immune: the data is historical.

modules/profiler/ringStatus

ringStatus(): string

Ring buffer status as a JSON string: { enabled, frames, capacity, span_seconds }.

modules/profiler/startCapture

startCapture(label: string?): boolean

Start recording per-frame profiler data. Each frame's system timings are captured until stopCapture() is called. Results are accessible via profiler.lastCapture() and VFS at /zero/runtime/profiler/<label>.json.

modules/profiler/stats

stats(pattern: string?): { ProfilerStat }

Get current EMA profiling statistics from the SystemProfiler. Optional glob pattern filters by metric name (supports * and ? wildcards).

modules/profiler/stopCapture

stopCapture(): string?

Stop the active profiler capture and return its result as a JSON string. The capture is also saved to VFS at /zero/runtime/profiler/<label>.json. Top-level fields: label, frame_count, started_at, ended_at, frames, summary. Compute duration as ended_at - started_at.

modules/profiler/unwatch

unwatch()

Disarm the watchdog. Recorded hits are kept for a final profiler.hits().

modules/profiler/watch

watch(ceilingMs: number, mode: string?, excludeAgent: boolean?, maxHits: number?): boolean

Arm the frame-time watchdog. When a frame's EFFECTIVE time (total minus agent-injected execute cost) crosses ceilingMs, mode "record" logs every offending frame (read with profiler.hits()), and mode "pause" pauses gameplay ONCE to freeze the bad state, then disarms. Editor profile only: returns false in the runtime profile. excludeAgent (default true) keeps the agent's own calls from tripping it.

modules/profiler/watchStatus

watchStatus(): string

Watchdog status as a JSON string: { armed, ceiling_ms, mode, exclude_agent, hits, dropped_hits, tripped }.

modules/prototype_lifecycle/README

require("@builtin/modules/api/engine/prototype_lifecycle") -- prototype_lifecycle

The play-mode invariant for authored player prototypes and EditorOnly entities. In play a PlayerPrototype subtree is the clone source, not a live scene entity: the scene loader spawns it only in edit and the spawn-on-join flow instantiates a clone per joining user in play. This module deactivates and hides any prototype still live when play begins (the fallback when the loader spawned one) and every EditorOnly entity, so neither is simulated nor rendered in play. In edit both are fully live. Runtime clones are tracked here and despawned on the return to edit. Deactivating and hiding a root cascades to its subtree through the hierarchy, so operating on the root covers its children.

Usage: local prototype_lifecycle = require("@builtin/modules/api/engine/prototype_lifecycle")

modules/prototype_lifecycle/activatePrototypes

activatePrototypes()

Reactivate and unhide every prototype root and EditorOnly entity.

modules/prototype_lifecycle/deactivatePrototypes

deactivatePrototypes()

Deactivate and hide every prototype root and EditorOnly entity so play mode neither simulates nor renders them.

modules/prototype_lifecycle/despawnClones

despawnClones()

Despawn every still-existing tracked clone and clear the tracking list.

modules/prototype_lifecycle/enterEdit

enterEdit()

Enter edit mode: despawn runtime clones, then reactivate prototypes and EditorOnly entities.

modules/prototype_lifecycle/enterPlay

enterPlay()

Enter play mode: deactivate and hide prototypes and EditorOnly entities.

modules/prototype_lifecycle/hideEditorSurface

hideEditorSurface()

Deactivate and hide the EditorOnly authoring surface without touching player-prototype roots. Used when play mode resumes so the surface disappears and gameplay cameras take the viewport back.

modules/prototype_lifecycle/prototypeRoots

prototypeRoots(): { string }

Ids of every active-layer entity carrying the PlayerPrototype component.

modules/prototype_lifecycle/registerClone

registerClone(id: string)

Track a runtime clone root so it can be despawned on the return to edit.

modules/prototype_lifecycle/showEditorSurface

showEditorSurface()

Reactivate and unhide the EditorOnly authoring surface (free camera + editor-only visualizers) without touching player-prototype roots. Used when play mode is paused so the editor camera returns over the frozen world.

modules/reflectionProbe/README

require("@builtin/modules/api/engine/reflectionProbe") -- reflectionProbe (also available as global 'reflectionProbe')

Reflection-probe system — multiple proximity-blended reflection probes in a scene. Each probe bakes the scene into its own cube slot from its position; surfaces reflect the nearest probe(s), blended by proximity (the renderer's per-fragment probe blend). One-liner authoring (reflectionProbe.add) and a one-call bake-everything (reflectionProbe.bakeAll).

Usage: local reflectionProbe = require("@builtin/modules/api/engine/reflectionProbe") Also available as global: reflectionProbe

modules/reflectionProbe/add

add(x: number, y: number, z: number, opts: { [string]: any }?): string

Add a reflection probe at (x, y, z) in one call: spawns a probe entity carrying a ReflectionProbe component (which registers it and, unless opts.bake == false, bakes it). The probe is an editor gizmo — invisible in play mode. Returns the probe entity id.

modules/reflectionProbe/apply

apply(): number

Push the current active-probe blend data (live positions + radii) to the renderer. Builds a dense slot array so each probe's data lands at its cube slot; freed/missing slots become inert placeholders. Called automatically by add / bake / remove; call it directly after moving a probe entity.

modules/reflectionProbe/bake

bake(id: string): (string?, string?)

Bake the scene into probe id's cube slot from its current position AND persist it to a faces6 .texture asset (so it survives reload + syncs), then re-apply the probe set. Yields a few frames; call from a task/coroutine context (component hook via task.spawn, bakeAll, or execute).

modules/reflectionProbe/bakeAll

bakeAll(): { baked: number, failed: number, errors: { string } }

Bake EVERY registered probe in the active layers, in one call. Captures each probe's scene from its position into its slot, persists it, and applies the full probe set. The agent/editor one-liner. Yields; call from a task/coroutine context (execute, a tool, or task.spawn).

modules/reflectionProbe/count

count(): number

Number of registered probes.

modules/reflectionProbe/list

list(): { any }

List every registered probe: { { id, slot, radius, asset, position }, ... }.

modules/reflectionProbe/loadBaked

loadBaked(id: string): boolean

Load probe id's PERSISTED baked cube (probe_<key>.texture) into its slot WITHOUT re-rendering the scene — the runtime path. A probe bakes once at authoring time and loads the asset on every subsequent scene load. Returns false (not an error) when no baked asset exists yet.

modules/reflectionProbe/register

register(id: string, radius: number, key: string?): number?

Register a reflection probe for entity id with influence radius. Assigns a free cube slot and applies the updated probe set. Idempotent — a re-register keeps the same slot and just updates the radius. Called by the ReflectionProbe component's awake; rarely called directly.

modules/reflectionProbe/setRadius

setRadius(id: string, radius: number)

Update a probe's influence radius and re-apply.

modules/reflectionProbe/unregister

unregister(id: string)

Unregister entity id's probe, freeing its cube slot, and re-apply.

modules/renderer/README

require("@builtin/modules/api/engine/renderer") -- renderer (also available as global 'renderer')

GPU-resource factory + CPU codec/store wrappers — the GPU/CPU half of the asset↔resource split. This module (under modules/api/) is the SOLE caller of the internal __mesh / __meshcpu / __meshgpu / __texture / __texturecpu / __texturegpu FFI; assetType behaviours, components, and every other module call renderer.*, never the __ internals.

Usage: local renderer = require("@builtin/modules/api/engine/renderer") Also available as global: renderer

modules/renderer/captureView.channelId

captureView.channelId(name: string): number?

The debug channel a registered view draws on — what a feature passes as its pass debugChannel. Nil when no view is registered under name.

modules/renderer/captureView.list

captureView.list(): { any }

Every registered capture view as { name, channel, description } records — what backs the discoverability of capture pass=<name> and the unknown-view error's suggestion list.

modules/renderer/captureView.register

captureView.register(name: string, config: any): number

Register (or update) a content capture view under name and return the debug CHANNEL number assigned to it. A render feature gates its pass to this channel (debugChannel = channel) so the pass draws only when a capture selects the view. Idempotent: re-registering the same name keeps its channel.

modules/renderer/captureView.resolve

captureView.resolve(name: string): any

Resolve a capture view by name to its { channel, ensure, description } record, or nil when no view is registered under name.

modules/renderer/captureView.unregister

captureView.unregister(name: string): boolean

Withdraw a capture view. A subsequent capture pass=<name> no longer resolves to it (falls through to the unknown-view error).

modules/renderer/debugPass.builtins

debugPass.builtins(): { string }

The built-in debug-pass names, one per channel in channel order — the engine's built-in pass vocabulary (final, albedo, normal, depth, …).

modules/renderer/debugPass.channel

debugPass.channel(name: string): number?

The channel a debug-pass NAME renders on: a built-in pass, else a content capture view registered via renderer.captureView. Nil when the name is neither — the signal a selector uses to reject an unknown pass.

modules/renderer/debugPass.list

debugPass.list(): { string }

Every selectable debug-pass name: the built-in passes plus every registered content capture view. What a debug-pass selector offers.

modules/renderer/debugPass.name

debugPass.name(channel: number): string?

The canonical NAME for a debug channel: a built-in pass name for a built-in channel, else a registered capture view's name. Channel 0 is "final" (the lit image). Nil when no pass owns the channel.

modules/renderer/destroy

destroy(handle: any): boolean

Free the GPU resource behind a MeshHandle / TextureHandle (the GPU-destroy verb). Routes by the handle's category. The on-disk asset, if any, is untouched. A CPU handle's :unload() frees the CPU copy separately.

modules/renderer/feature.create

feature.create(ref: any, guid: string?): any

Instantiate a render feature so the engine calls its render(ctx) hook every frame. ref is an AssetRef<renderFeature> whose init.luau returns { setup?, render, teardown? }. Returns a live RenderFeatureHandle (its guid is the stable id, same as mesh/texture handles); tear it down with renderer:destroy(handle). Pass guid to assign a specific id.

modules/renderer/feature.destroy

feature.destroy(handleOrGuid: any): boolean

Tear down a live render feature by its RenderFeatureHandle OR its guid string — the by-id path for when the handle was lost (e.g. across execute calls). Same effect as renderer.destroy(handle). Returns true if a feature was live under that id.

modules/renderer/feature.list

feature.list(): { { guid: string, identity: string } }

List every render feature currently live (running its render(ctx) each frame). Each entry is { guid, identity } — the guid is the same id a RenderFeatureHandle carries, so you can tear a feature down by guid even after losing its handle (e.g. across separate execute calls).

modules/renderer/getRaytrace

getRaytrace(): boolean

Whether ray tracing is currently enabled.

modules/renderer/mainCameraView

mainCameraView(): { number }?

The main camera's inverse view-projection (column-major, 16 numbers) followed by its world position (3 numbers) — {m0..m15, px,py,pz} — for reconstructing world positions from the depth buffer in a ray-tracing pass. Nil before the first render.

modules/renderer/material.create

material.create(content: MaterialContent, key: string): MaterialHandle

Register (or update) a material's GPU resource under key and return its MaterialHandle. content = { shader, properties, textures, aliases?, render? }. The referenced textures must already be GPU-resident (the material assetType materialises each slot via texRef:handle() before calling this) — this only files the shader+property+texture-slot record the renderer binds. Mirrors renderer.mesh.create / renderer.texture.create; idempotent upsert by key. render sets pipeline render-state (cull / depthWrite / blend) — e.g. { cull = "front", depthWrite = false } for an inverted-hull outline program.

modules/renderer/material.describe

material.describe(key: string): any

The recoverable definition ({ shader, properties, textures, name }) this module registered under key via renderer.material.create, or nil for keys registered elsewhere (e.g. material assets resolved by the assetType).

modules/renderer/material.destroy

material.destroy(key: string): boolean

Drop a runtime material registered via renderer.material.create: clears its recoverable definition, unregisters its runtime-resource stamp so it is no longer swept into the material freeze/save flow, and frees the GPU record. Use for transient materials (e.g. a preview swatch) that must not outlive their use. The on-disk asset, if any, is untouched.

modules/renderer/material.sessionKeyFor

material.sessionKeyFor(entityId: string): string

The canonical registry key for an entity's SESSION material — the runtime material a system (e.g. GI baking) shows on an entity in place of its authored material for the lifetime of the engine session. One session material per entity: create it under this key, hand the handle to Model:applySessionMaterial, and the component re-adopts it across VM reloads by probing this key with describe.

modules/renderer/material.setProperty

material.setProperty(key: string, name: string, value: any): ()

Push one changed uniform property to a registered material's GPU record (frame-fast incremental update; no re-register). Keyed by the material's registry key.

modules/renderer/material.setTexture

material.setTexture(key: string, slot: string, ref: string): ()

Push one changed texture slot to a registered material's GPU record. The texture must already be GPU-resident (materialise it with texRef:handle() first). Keyed by the material's registry key.

modules/renderer/mesh.buildClusters

mesh.buildClusters(guid: string): string?

Build a cluster-LOD DAG (Nanite-style virtualized geometry) for the static CPU mesh held under guid and return its serialized data.clusters bytes. The native offline bake; returns nil when the mesh is degenerate or the platform has no builder (the cross-platform runtime consumes pre-baked clusters). Pair with renderer.mesh.uploadClusters.

modules/renderer/mesh.clusterComponents

mesh.clusterComponents(clusterBytes: string): (ClusterComponents?, string?)

Split a cluster blob (from renderer.mesh.buildClusters) into its GPU-ready component byte pools — the cluster vertex pool, the u32 index pool, and the per-cluster record array — plus their counts. A pure decode (no GPU work): upload the pools to compute buffers (compute.createBuffer + compute.writeBufferBytes) to drive a cluster draw from Luau.

modules/renderer/mesh.create

mesh.create(src: any, guid: string?): MeshHandle

Create (or fetch) a GPU mesh resource and return its MeshHandle. src: a MeshCpuHandle from meshRef:load() (CPU→GPU upload under the asset's guid, idempotent — returns the resident handle if already uploaded); raw geometry {positions, indices, normals?, uvs?, colors?, uvs1?, unwrapUvs?, tangents?, skinning?, skins?} (a new runtime mesh — uvs1 is the lightmap UV set, unwrapUvs generates one, skinning/skins bind a skeleton); GPU compute buffers {vertexBuffer, indexBuffer, vertexCount, indexCount, aabbMin?, aabbMax?} (size the vertex buffer at vertexCount * engine.vertexStride bytes, the engine's standard Vertex layout); or a MeshHandle (returned as-is). NEVER takes an AssetRef — load the CPU first.

modules/renderer/mesh.decode

mesh.decode(zmsh: string): (MeshGeometry?, string?)

Decode engine-native ZMSH bytes back into a MeshGeometry. Inverse of renderer.mesh.encode; each optional stream is present only when the blob carries it.

modules/renderer/mesh.encode

mesh.encode(geom: MeshGeometry): string?

Encode raw geometry into engine-native ZMSH bytes (the on-disk mesh payload). The CPU codec behind the mesh assetType's onCreate. Every stream the format carries — including tangents, per-vertex skinning, and the skeleton — round-trips back through renderer.mesh.decode.

modules/renderer/mesh.encodeCpu

mesh.encodeCpu(guid: string): string

Encode the CPU mesh resident under guid into ZMSH bytes. Reads the ONE guid-keyed CPU store — meshRef:load() populates it for assets, and renderer.mesh.readback(guid) populates it for a runtime mesh. Errors loudly when no CPU mesh is resident under the guid.

modules/renderer/mesh.getVertices

mesh.getVertices(guid: string): { any }

Read the vertices of the CPU mesh resident under guid — one entry per vertex, { pos = {x,y,z}, normal = {x,y,z}, uv = {u,v} }. Reads the resident CPU store directly (no re-decode). Errors when no CPU mesh is resident under the guid — load it (meshRef:load()) or retain it (keepCpu) first.

modules/renderer/mesh.isCpuResident

mesh.isCpuResident(guid: string): boolean

True if a CPU mesh is resident under guid in the guid-keyed CPU store.

modules/renderer/mesh.isResident

mesh.isResident(guid: string): boolean

True if a GPU mesh is resident under guid.

modules/renderer/mesh.loadCpu

mesh.loadCpu(ref: any): MeshCpuHandle

Load a .mesh asset's geometry into the ONE guid-keyed CPU store (the Disk→CPU step) and return a CPU handle. The handle holds NO geometry — only the guid plus counts and the per-handle read/encode/unload ops (which read the Rust-side store). Called by meshRef:load(). DEFAULT lifecycle: upload to the GPU then handle:unload(); the store is populated only by this call.

modules/renderer/mesh.readback

mesh.readback(guid: string): MeshCpuHandle

Read a runtime GPU mesh's geometry back to CPU and return a MeshCpuHandle for it — the GPU→CPU half of the runtime-mesh freeze path. A mesh made with renderer.mesh.create keeps no CPU copy, so persisting it (:encode()asset.create("mesh", …)) reads it back here first. Yields until the readback completes (a frame or two). After it returns the geometry is resident in the guid-keyed CPU store: :getTriangles, :getVertices, :getBounds, :encode, :unload all work. Errors if no GPU mesh is resident in the vertex pool under guid.

modules/renderer/mesh.setVertices

mesh.setVertices(guid: string, positions: { number })

Replace the resident CPU mesh's vertex positions (flat { x,y,z, ... }) under guid, IN PLACE — indices, normals/uvs, and skinning are preserved, the AABB recomputes, and the GPU re-fetches the new geometry so it shows on screen. The mesh must be CPU-resident (load it, or retain it with keepCpu). Errors with the reason otherwise, or when the vertex count doesn't match.

modules/renderer/mesh.unloadCpu

mesh.unloadCpu(guid: string)

Drop the CPU mesh resident under guid from the guid-keyed CPU store. The explicit release for a runtime geometry mesh's recoverable definition.

modules/renderer/mesh.update

mesh.update(handle: MeshHandle, src: any): MeshHandle

Overwrite the GPU resource behind handle IN PLACE, under the same guid, from new geometry or compute buffers. Never writes a .mesh file — the play-mode mutate path. A Model bound to handle.guid reflects the change with no re-bind. Returns the same handle with refreshed bounds.

modules/renderer/mesh.uploadClusters

mesh.uploadClusters(guid: string, clusters: string): boolean

Attach a cluster-LOD DAG (bytes from renderer.mesh.buildClusters) to the GPU mesh keyed by guid, enabling the continuous-cut cluster draw path for that mesh.

modules/renderer/raytraceCapability

raytraceCapability(): string

The active ray-tracing backend: "hardware" (GPU ray query) or "compute" (software traversal — the path on devices without hardware ray query, e.g. the web). The same ray-tracing features work on both.

modules/renderer/setRaytrace

setRaytrace(enabled: boolean): ()

Enable or disable GPU ray tracing. While enabled the engine builds the scene acceleration structure each frame so ray-tracing render features can trace against it; disabling stops the build (so it costs nothing until a ray-traced effect is active). Required before any ray-traced shadows / AO / reflections render.

modules/renderer/texture.capture

texture.capture(handle: TextureHandle): string

Request a CPU readback of the GPU texture behind handle (e.g. a camera's rendered output). Returns a result key to pass to a TextureCpuHandle's :encode() once the readback completes.

modules/renderer/texture.cpuCreate

texture.cpuCreate(width: number, height: number, fill: any?): TextureCpuHandle

Allocate a blank CPU image (RGBA8) filled with a solid colour and return a TextureCpuHandle. Compose into it with canvas:blit(src, x, y, w, h), then canvas:encodeJpeg() / :encodePng() for the bytes; :unload() drops it.

modules/renderer/texture.create

texture.create(src: any, guid: string?): TextureHandle

Create (or fetch) a GPU texture resource and return its TextureHandle. src: a TextureCpuHandle from texRef:load() (CPU→GPU under the asset's guid, idempotent); raw pixels {rgba, width, height, srgb?, format?} (a flat widthheight4 byte buffer, 0-255, row-major, top-to-bottom, RGBA; format = "rgba16f" uploads an HDR texture instead, where rgba is a flat float array of channel values); a TextureHandle (returned as-is); or render-target dimensions {width, height, name?} with no pixel source — an empty GPU texture a render pass writes into (camera output, UI surface) and that samples like any other texture. NEVER takes an AssetRef — load the CPU first.

modules/renderer/texture.decode

texture.decode(ztex: string): (any, any, any)

Decode an engine-native ZTEX payload to RGBA8 pixels.

modules/renderer/texture.destroy

texture.destroy(handle: TextureHandle)

Release the GPU texture behind handle. For an empty render-into texture (camera output, UI surface) this also frees its render scratch; for an uploaded runtime texture it drops the GPU resource (and any CPU shadow).

modules/renderer/texture.encode

texture.encode(rgba: any, width: number, height: number, opts: any): (string?, string?)

Encode raw RGBA8 pixels into an engine-native ZTEX payload (the on-disk texture content). The CPU codec behind the texture assetType's onCreate.

modules/renderer/texture.encodeFromImage

texture.encodeFromImage(bytes: string, opts: any): (string?, string?)

Encode source image bytes (png/jpg/webp/…) into an engine-native ZTEX payload. Used by the texture importer / assetType onChange.

modules/renderer/texture.info

texture.info(ztex: string): (any, any)

Read the header of an engine-native ZTEX payload without copying the pixels. Returns its format, dimensions, mip count, and filter ("nearest" or "linear" — the sampler baked into the blob from the asset's settings.filter).

modules/renderer/texture.isResident

texture.isResident(guid: string): boolean

True if a GPU texture is resident under guid.

modules/renderer/texture.loadCpu

texture.loadCpu(ref: any, encodeOpts: any?): TextureCpuHandle

Load a .texture asset's pixels into the ONE guid-keyed CPU store (the Disk→CPU step) and return a CPU handle for per-pixel access (no GPU readback). The handle holds NO pixels — only the guid plus dims and the read/write/encode/unload ops (which read the Rust store). Called by texRef:load(). DEFAULT: upload to the GPU then handle:unload().

modules/renderer/texture.readback

texture.readback(guid: string): TextureCpuHandle

Read a runtime GPU texture's pixels back to CPU and return a TextureCpuHandle for them — the GPU→CPU half of the runtime-texture freeze path. A texture made with renderer.texture.create keeps no CPU copy, so persisting it (:encode()asset.create("texture", …)) reads it back here first. Yields until the readback completes (a frame or two). After it returns the pixels are resident in the guid-keyed CPU store: :readPixel, :writePixel, :getInfo, :encode, :unload all work. Errors if no GPU texture is resident under guid.

modules/renderer/texture.update

texture.update(handle: TextureHandle, src: any): TextureHandle

Overwrite the GPU texture behind handle IN PLACE, under the same guid, from new raw pixels. Never writes a .texture file — the play-mode mutate path. Returns the same handle (refreshed dims).

modules/retarget/README

require("@builtin/modules/retarget") -- retarget

Skeletal animation retargeting — map a clip authored on one rig onto another humanoid rig, preserving the target's shape. Pure, readable Luau: the behavior an agent follows and tweaks. bake is the cold, cached transform (clip + source rig + target rig -> a clip in the target's bone space); the hot path just samples the baked clip. plan reports which canonical roles map across the two rigs and which don't.

Usage: local retarget = require("@builtin/modules/retarget")

modules/retarget/animation

animation(clipRef: any, targetMeshRef: any, sourceMeshRef: any?): (boolean, string)

Retarget an animation clip onto a target rig, returning the VFS path of a new .anim whose channels name the target skeleton's bones with bind-pose corrected rotations. The source rig is the clip's embedded rig.zmsh (else sourceMeshRef's skin, else the skinned mesh beside the clip in its bundle); the target rig is targetMeshRef's skin. Play the result with animGraph.addClip(entity, path). Pure asset transform — no entity/ECS state.

modules/retarget/bake

bake(clip, srcRig, tgtRig, opts)

Retarget a decoded clip from its source rig onto a target rig, producing a clip in the TARGET's bone space (channels named for target bones). Per frame it forward-kinematics the source pose on the rig AS AUTHORED (the bind the clip's channel rotations are local to), measures each mapped role's world rotation as a deviation from the source's CANONICAL bind, re-applies that deviation to the target's CANONICAL bind, and converts back to a target-local rotation through the target's ANIMATED parent. The target chain is rebuilt top-down, so error never accumulates down a limb. Both rigs are re-posed to the geometry-derived canonical T-pose (normalizeToTPose) for that deviation, so the result depends only on the T-pose the two rigs share — never on whatever arbitrary pose either was authored in (an A-pose source idle lands the target's arms down, not splayed out at the A-pose offset). The apply path seeds that same canonical rest. Translation routes through the role and is size-scaled by the hip-height ratio; the apply path makes it relative to the target's bind. This is the cold step — bake once per (clip, target rig) and cache (see bakeBytes).

modules/retarget/bakeBytes

bakeBytes(clipBytes, srcRig, tgtRig, cacheKey, opts)

Bake from clip BYTES to retargeted clip BYTES — clipDecode -> bake -> clipEncode — with an in-memory cache. Retarget is cold: pass a stable cacheKey (e.g. clip identity + target rig identity) and the second call for the same pair returns the cached bytes. The hot path then just samples the result like any native clip.

modules/retarget/clearCache

clearCache(cacheKey: string?)

Drop every cached bake (or just cacheKey when given). Call after editing a rig's profile so clips re-bake against the corrected mapping.

modules/retarget/extractRig

extractRig(meshBytes: string): string?

Strip a .mesh (ZMSH) payload to a lean skin-only rig: the skeleton with geometry removed, re-encoded as a ZMSH whose only content is the skin. Returns the rig bytes, or nil when the mesh carries no skin. A .animation composite embeds this as rig.zmsh so a clip travels with its own source rig.

modules/retarget/humanoidProfile

humanoidProfile(meshBytes: string): HumanoidHolder?

Derive the humanoid retarget holder for a rig from a .mesh (ZMSH) payload, when that skeleton has the essential humanoid structure (a hips root, a head or neck, at least one full arm chain and one full leg chain). Returns nil for a rig that is not a humanoid — a prop, a plant whose leaves animate, a quadruped — so a clip from it stays a plain clip rather than joining the shared humanoid-animation pool.

modules/retarget/isHumanoid

isHumanoid(meshBytes: string): boolean

Whether a rig is a humanoid avatar — true when humanoidProfile resolves a holder for it. Use this to tell a humanoid character apart from a generic animated mesh (a prop, a plant, a quadruped) before treating its clips as shareable humanoid animations.

modules/retarget/loadRig

loadRig(ref)

Resolve a .rig asset and return its parsed, FK-enriched rig (ready for plan / bake). The rig payload is the asset's rig.json.

modules/retarget/normalizeToTPose

normalizeToTPose(rig, opts)

Re-pose a rig's bind to the EXACT canonical T-pose, so a clip's source rig and the avatar it drives share one reference pose. Retarget transfers RELATIVE motion, so any residual bind mismatch (hands rolled the wrong way, feet pointing askew, an A-pose vs a T-pose) shows up as broken hands/feet in the result. Each bone is posed to a canonical world frame for its role — direction AND roll: arms horizontal palms-down, legs straight down, spine up. Only the limb bones whose bind direction differs between an A-pose and a T-pose are aimed; limb ENDPOINTS (hands, feet, toes) and bones with no role keep their authored orientation relative to the re-posed parent (their pose is mesh-defined, so aiming them twists the hand / tips the foot). The whole body is also rigidly de-rotated into an upright, forward-facing frame (handling a baked root rotation) and centered on its sagittal plane. Bind correction is RELATIVE by default — bone offsets/lengths/inverse-bind are untouched, so the rig keeps its own proportions and any authored left/right asymmetry. Pass opts.symmetrize = true for ABSOLUTE correction: left/right bones are mirrored across the sagittal plane for a perfectly symmetric bind, overriding the rig's authored asymmetry/proportions.

modules/retarget/plan

plan(srcRig, tgtRig)

Report how a source rig's clips map onto a target rig: which canonical roles both rigs fill (mapped), which the source has but the target lacks (unmappedSource — those channels are dropped), and which the target has spare (unmappedTarget). Use it to see why a retarget is partial and which bone to hand-map in a rig's profile.

modules/retarget/serializeProfile

serializeProfile(holder: HumanoidHolder): string

Serialize a humanoid holder to the humanoid.profile file body: an editable YAML role -> bone-name map. Roles list hips-first head-to-toe through the limbs, then any extras name-sorted, so the file reads top-down and diffs stably. Edit a value to correct an auto-derived mapping.

modules/runtime_participation/README

require("@builtin/modules/api/engine/runtime_participation") -- runtime_participation

Reads and writes the RuntimeParticipation lifecycle axis for an entity, and answers the save / edit-liveness / play-liveness questions that follow from a mode. One source of truth for the four modes (WorldEntity, PrototypeOnly, EditorOnly, RuntimeOnly) so save and play-mode code agree on what each mode means.

Usage: local runtime_participation = require("@builtin/modules/api/engine/runtime_participation")

modules/runtime_participation/isSaved

isSaved(mode: string): boolean

Whether an entity with this mode is written to the persisted world. True for WorldEntity, PrototypeOnly, and EditorOnly; false for RuntimeOnly.

modules/runtime_participation/liveInEdit

liveInEdit(mode: string): boolean

Whether an entity with this mode is live while authoring in edit mode. True for WorldEntity, PrototypeOnly, and EditorOnly; false for RuntimeOnly.

modules/runtime_participation/liveInPlay

liveInPlay(mode: string): boolean

Whether an entity with this mode is live during play. True for WorldEntity and RuntimeOnly; false for PrototypeOnly and EditorOnly.

modules/runtime_participation/modeOf

modeOf(entityId: string): string

The entity's RuntimeParticipation mode. Defaults to "WorldEntity".

modules/runtime_participation/set

set(entityId: string, mode: string)

Sets the RuntimeParticipation mode on an entity. A mode that is not saved marks the entity temporary so the scene-save exclusion drops it.

modules/scene_instantiable/README

scene_instantiable

The sceneInstantiable field-constraint validator: a constrained value must be an asset whose type can be instantiated into a scene — ref:canInstantiate(), i.e. its assetType defines a sceneInstantiate hook. This is what makes Field.instantiableRef accept by CAPABILITY instead of a hardcoded type list. Registers itself with the generic field_constraints registry on load. nil (no asset) passes — the field is optional.

modules/scene_loader/README

scene_loader

Luau-side scene loader. Reads scene.json v6 and v7, refuses versions outside that range with a typed error, dispatches to entity.spawn / component.add / lights.setup, auto-discovers the sibling entrypoint.luau via vfs.exists. v6 attaches the declarative player + camera blocks to the Scene proxy for procedural resolution; v7 stashes the string player intent and does no procedural player/camera spawn. Replaces the legacy Rust __layers.load FFI path; the Rust FFI remains for one release as a safety net but is no longer invoked by any in-tree caller. Delta-overlay aware: when a scene_dirty/ directory exists next to canonical scene.json, the loader hands the merge off to scene_saver.composeMerged — canonical scene.json + dirty manifest overrides + per-entity overlay files = the assembled body. Single merge point so the load + promote paths can never disagree. See § 10 of the player-camera-unification integration design for the architecture rationale (pure Luau, performance budget ~5 ms per 1k-entity scene, ECS FFI is sufficient without new primitives).

modules/scene_saver/README

scene_saver

Luau-side scene saver. Persists scene state via a delta-overlay model: Canonical (committed work) /scene.json Dirty (work-in-progress, deltas only) /scene_dirty/manifest.json scene-level config overrides (lighting, player, camera, format, version) /scene_dirty/entities/.json one file per CHANGED entity: * full body — modified or newly spawned * tombstone — { tombstone = true } for despawn The dirty directory carries ONLY the deltas. Entities unchanged since the last canonical save have NO entry under entities/. Editing one cube in a 100k-entity scene writes exactly one ~1KB file per drain; the manifest is touched only when scene-level config changes. The entity-id list is NOT carried in the manifest — it's derived at load / promote time from canonical.entities[] plus a filesystem listing of entities/. Concurrent writes to per-entity files therefore never produce manifest-conflict orphans (the conflict surface is one entity at a time, not the whole-scene index). Tombstones survive in dirty until the next save (when clearDirty wipes the directory). An orphan tombstone — one targeting an entity that wasn't in canonical either (i.e. the entity was spawned and deleted inside the same edit session before save) — is a no-op at compose time, so the file shape is robust regardless of operation order. The composer (composeMerged) is the canonical merge point used by both this saver's promote/save paths AND scene_loader.M.load, so there's exactly one place that knows how to combine canonical with the overlay. Captures authored intent only: no spawner-managed entities (player identities, primary camera), no temporary entities, no runtime player position. FFI shapes (discovered at runtime — differ from plan): - entity.findAll() → array of {id: string, name: string} tables. - localPosition / localScale expose number components .x/.y/.z. - localRotation exposes quaternion components .x/.y/.z/.w. - entity(id).getParent() → string id or nil, NOT a proxy table. - entity(id).name → string name (direct field, no function call). - lighting snapshot is read directly from settings.lighting.

modules/scene_swap_orchestrator/README

scene_swap_orchestrator

Drives multiplayer room transitions + the scene-swap gate around scene load/unload events. Subscribes to layers.onUnload + layers.onBeforeLoad and translates them into a leaveRoom -> gate.begin -> teardown -> gate.finish -> joinRoom sequence. Room key is {worldGuid}/{profile}/{mode}/{sceneGuid} — never name-based; world and scene segments are always GUIDs (stable across renames and collaborators), and {profile} (runtime/editor) keeps published and live peers apart. Without this orchestrator, scene swaps world would broadcast EntityDespawn to peers still subscribed to the old room, and transient teardown state would land in the dirty file.

modules/service/README

require("@builtin/modules/api/engine/service") -- service (also available as global 'service')

Credit-metered service invoke. Public Luau surface over the __service Internal FFI namespace.

Usage: local service = require("@builtin/modules/api/engine/service") Also available as global: service

modules/service/authenticated

authenticated(): boolean

Whether a platform identity (JWT) is available to attach to service calls. Returns only a boolean — never the token.

modules/service/balance

balance(): string?

Read the caller's credit balance from ZeroMind. Returns a promise handle for task.await() resolving the balance JSON, or nil when the gateway is unconfigured or no caller identity is available.

modules/service/configureGateway

configureGateway(baseUrl: string): boolean

TRUSTED ONLY. Set the ZeroMind base URL that service.invoke and service.balance target. The trusted-VM auth bootstrap calls this with the resolved issuer.

modules/service/gatewayConfigured

gatewayConfigured(): boolean

Whether the ZeroMind service gateway has been configured. Service handlers use this to distinguish "gateway not configured" from "not signed in" when invoke returns nil.

modules/service/invoke

invoke(offering: string, endpoint: string, opts: InvokeOpts?): string?

Invoke a provider offering's logical endpoint through ZeroMind. Returns a promise handle for task.await() resolving the InvokeResponse JSON, or nil when the gateway is unconfigured or no caller identity is available. The JWT and real upstream URL are never exposed to Luau.

modules/shell/README

require("@builtin/modules/api/engine/shell") -- shell (also available as global 'shell')

Engine emulated Unix shell — same shell that powers the MCP bash tool. Public Luau surface over the __shell Internal FFI namespace.

Usage: local shell = require("@builtin/modules/api/engine/shell") Also available as global: shell

modules/shell/run

run(command: string): ShellResult

Execute a command in the engine's emulated Unix shell. Blocks until the command completes. This is the same shell as the MCP bash tool — 60+ builtins (ls, cat, grep, find, echo, ...) operating on the virtual scene filesystem.

modules/shell/runAsync

runAsync(command: string): string

Asynchronous version of shell.run. Returns a promise ID that resolves to a JSON-encoded result string. Use with task.await().

modules/skeleton/README

require("@builtin/modules/api/engine/skeleton") -- skeleton (also available as global 'skeleton')

The skeleton pose-data pipeline: sample a clip into a pose buffer, and bind/apply a pose buffer onto a Skeleton + Model entity. Public Luau surface over the __skeleton and __clip Internal FFI namespaces.

Usage: local skeleton = require("@builtin/modules/api/engine/skeleton") Also available as global: skeleton

modules/skeleton/applyPose

applyPose(sinkHandle: number, bufferHandle: number): boolean

Snapshot the buffer's first layout.total_floats values and queue a pending apply for the next ECS drain. Returns false on unknown sink/buffer or buffer too small for the layout. The Buffer is unchanged.

modules/skeleton/bindClip

bindClip(zanimBytes: string, boneOrder: { string }): ClipBindInfo?

Decode a zanim payload and bind it to boneOrder, precomputing which of the clip's channels feed each bone so per-frame sampleClip is allocation-free. Returns { handle, matched, total, duration }, or nil on a malformed payload / empty bone order. Check matched: 0 means the clip drives none of these bones.

modules/skeleton/bindPose

bindPose(entityId: string?, opts: SkeletonLayout): number?

Register a pose sink targeting entityId. The opts table carries the layout: boneOrder is the bone-name array ({"hip", "spine", ...}), stride defaults to 10 (translation.xyz + rotation.xyzw + scale.xyz). Pass entityId as nil to use the current component's owning entity.

modules/skeleton/clipBones

clipBones(zanimBytes: string): { string }?

Decode a zanim payload and return its bone-name array. Pure: build a bind order or a retarget map from a clip without binding a sampler. Returns nil on bytes that aren't a valid zanim payload.

modules/skeleton/clipDecode

clipDecode(zanimBytes: string): string?

Decode a zanim payload to its readable JSON form ({ name, duration, channels, bone_names }). The binary parse is the engine's; json.decode the result to inspect or transform a clip's channels (e.g. the retarget bake) in Luau. Returns nil on bytes that aren't a valid zanim payload. Inverse of clipEncode.

modules/skeleton/clipEncode

clipEncode(jsonString: string): string?

Encode a clip's JSON form (the shape clipDecode returns) back to a zanim payload — the bytes a .animation stores and bindClip/sampleClip consume. Inverse of clipDecode. Returns nil on invalid JSON.

modules/skeleton/jointTransforms

jointTransforms(entityId: string): table

Read a skinned entity's per-joint world transforms for the current animated pose.

modules/skeleton/sampleClip

sampleClip(handle: number, time: number, buffer: number): boolean

Sample the bound clip at time (clamped to [0, duration]) and write one stride-10 pose record per bound bone into the Buffer, starting at index 0. Bones the clip does not drive are written as identity. Returns false on unknown handle/buffer or a buffer too small for the bone count.

modules/skeleton/unbindClip

unbindClip(handle: number): boolean

Drop the bound clip sampler from the registry.

modules/skeleton/unbindPose

unbindPose(sinkHandle: number): boolean

Remove the sink from the registry.

modules/sky/README

require("@builtin/modules/api/engine/sky") -- sky (also available as global 'sky')

Sky configuration — type, time of day, day/night cycle, procedural parameters, presets, explicit sun direction. Public Luau surface over the __sky Internal FFI namespace.

Usage: local sky = require("@builtin/modules/api/engine/sky") Also available as global: sky

modules/sky/get

get(): { [string]: any }

Get all current sky configuration as a table. Returns the same fields as sky.set accepts, plus read-only fields like material_name and type. Color values are returned as positional arrays [r, g, b].

modules/sky/getTimeOfDay

getTimeOfDay(): number

Get the current time of day in hours (0-24).

modules/sky/installFallback

installFallback()

Register the engine fallback sky material and install it as the engine-level fallback (rendered when a scene has no sky entity). Idempotent; requires a live renderer — the scene loader calls it.

modules/sky/preset

preset(name: string)

Apply a named sky preset. Available: clear_day, sunset, sunrise, overcast, night, studio, none. Raises a Luau error for unrecognized names — wrap in pcall if uncertain.

modules/sky/set

set(opts: SkyOpts)

Configure the sky system. All fields are optional — only provided fields are updated. Color fields accept both named {x=r, y=g, z=b} and positional {r, g, b} forms. color is an alias for solid_color.

modules/sky/setSunDirection

setSunDirection(dir: SkyColor)

Set an explicit sun direction and disable time-based sun positioning. The directional light is updated to match.

modules/sky/setTimeOfDay

setTimeOfDay(time: number)

Set the time of day (0-24 hours). 0 = midnight, 6 = sunrise, 12 = noon, 18 = sunset.

modules/subscriptions/README

require("@builtin/modules/api/engine/subscriptions") -- subscriptions (also available as global 'subscriptions')

Runtime inspection and cancellation of component-event subscriptions. Public Luau surface over the __event_inspect_* / __event_track_* Internal FFI globals; the same data is browsable at /zero/runtime/events/.

Usage: local subscriptions = require("@builtin/modules/api/engine/subscriptions") Also available as global: subscriptions

modules/subscriptions/cancel

cancel(id: string): boolean

Cancel a subscription by id: disconnects the live connection immediately and marks the row cancelled. Returns true when a live subscription was cancelled, false for an unknown or already-disconnected id.

modules/subscriptions/get

get(id: string): SubscriptionRow?

One subscription row by id, or nil when the id is unknown (never tracked, or evicted after its publisher was destroyed).

modules/subscriptions/list

list(filter: SubscriptionFilter?): { SubscriptionRow }

Every tracked subscription row, optionally filtered by publisher instance id, publisher entity id, event name, and/or connected state.

modules/subscriptions/publishers

publishers(): { PublisherRow }

Every live event publisher: component instance, entity, and per-event fire stats (fires happen whether or not anyone subscribes) plus current subscriber ids.

modules/text/README

require("@builtin/modules/api/engine/text") -- text (also available as global 'text')

Text rasterisation resource — create a text handle, set its content and style, then rasterise it to a texture for display. Public Luau surface over the __text Internal FFI namespace.

Usage: local text = require("@builtin/modules/api/engine/text") Also available as global: text

modules/text/create

create(options: table): any

Create a text handle from an initial content + style table. The handle owns a runtime GPU texture (see text.textureGuid); pass it to every other call.

modules/text/destroy

destroy(handle: any)

Destroy a text handle and release its raster + glyph layout.

modules/text/listFonts

listFonts(): { string }

List the font families currently available to the text system.

modules/text/loadFont

loadFont(ref: any): any

Load a font from an asset reference so it becomes available to setStyle's fontFamily.

modules/text/measure

measure(handle: any): any

Measure the rasterised text in pixels without producing a texture.

modules/text/rasterize

rasterize(handle: any, texture: any, scale: number?): any

Rasterise the handle's current text + style into the given runtime GPU texture. Bind that texture's guid as a material's base_color_texture to display the text; re-rasterising the same texture overwrites it in place.

modules/text/setStyle

setStyle(handle: any, style: table)

Replace the handle's style. Fields not present keep their current value.

modules/text/setText

setText(handle: any, content: string)

Replace the handle's text content.

modules/text/textureGuid

textureGuid(handle: any): string?

The runtime GPU texture guid this handle rasterises into — bind it as a material texture (base_color_texture) to display the text.

modules/trusted/README

require("@builtin/modules/trusted_client") -- trusted

User-space client for the trusted -> user call bridge (trusted.call).

Lets user-space scripts TRIGGER orchestration that lives in the engine's trusted VM (auth / connection / service flows we author in trusted Luau) WITHOUT exposing the trusted source or the privileged primitives those flows use. Backed by the single __trustedBridge.invoke Rust transport: the named call is marshalled into the trusted VM, run against its __TRUSTED_EXPORTS registry, and the result marshalled back as plain data. Trusted methods are registered on the other side by the trusted module /zero/trusted/exports (its expose(name, handler)). This module is auto-injected as the global trusted by the prelude.

Usage: local trusted = require("@builtin/modules/trusted_client")

modules/ui/README

require("@builtin/modules/api/engine/ui") -- ui (also available as global 'ui')

UI system — screens, styles, themes, widget responses, focus, custom-widget builders, per-widget state. Public Luau surface over the __ui Internal FFI namespace.

Usage: local ui = require("@builtin/modules/api/engine/ui") Also available as global: ui

modules/ui/blur

blur()

Surrender keyboard focus from whichever widget currently holds it.

modules/ui/bringAreaToFront

bringAreaToFront(id: string)

Raise a movable area to the top of the window stacking order — the programmatic equivalent of clicking it. egui orders overlapping areas by interaction, so bumping a screen layer alone will NOT bring an unclicked window forward; call this when a taskbar button, focus change, or app launch should bring its window to the front.

modules/ui/captureWindow

captureWindow(screen: string, window: string, opts: CaptureOpts?): CaptureResult?

Render a single Window widget to its own offscreen texture and write the result as PNG at /runtime/render_surfaces/<rtHandle>.png. The screen does NOT need to be visible. Returns { rtHandle, texturePath } or nil on invalid inputs (width/height clamped to [1, 8192], defaults 600x400).

modules/ui/click

click(callbackId: string, value: any?)

Simulate a widget click / interaction by its callback id.

modules/ui/defineStyle

defineStyle(name: string, style: StyleProps)

Define a named style. Style keys follow <widgetType>.<className> (e.g. "label.h1", "button.primary") or bare <className> to apply across widget types. Widgets reference styles via the classes (or class) prop.

modules/ui/defineStyles

defineStyles(styles: { [string]: StyleProps })

Define multiple named styles at once.

modules/ui/defineWidget

defineWidget(name: string, builderFn: (WidgetTree, { WidgetTree }) -> WidgetTree)

Register a custom widget kind. When a tree contains { type = name, props = ..., children = ... }, the decoder calls builderFn(props, children) at register / update time and substitutes the returned widget table in place. Errors surface through ui.lastValidation() with codes widget-builder-error / widget-builder-bad-return / decode-recursion-depth-exceeded.

modules/ui/focus

focus(widgetId: string)

Programmatically request keyboard focus on a widget. Queued as a one-shot; the next render of the matching widget calls response.request_focus().

modules/ui/focusedWidget

focusedWidget(): string?

Return the widget id of whichever widget currently holds keyboard focus, or nil. Snapshotted post-render each frame.

modules/ui/getAreaPos

getAreaPos(id: string): AreaPos?

Read the current pivot position of an area widget, including any user drag deltas. Returns { x, y } or nil if the area didn't render this frame.

modules/ui/getAreaSize

getAreaSize(id: string): AreaSize?

Read the measured size of an area widget, including any user resize-grip drags if the area is resizable. Returns { w, h } or nil if the area didn't render this frame.

modules/ui/getDockLayout

getDockLayout(id: string): string?

Read the current serialized layout (split/tab arrangement) of a dockArea widget as a JSON string. Returns nil if the dockArea didn't render this frame. Persist the string and pass it back via the dockArea's layout prop to restore the arrangement.

modules/ui/getLayoutInfo

getLayoutInfo(widgetId: string?): LayoutInfo?

Get layout info (position, size, content bounds) for UI containers. If widgetId is given, returns info for that widget only; otherwise returns all.

modules/ui/getScreenTree

getScreenTree(screenName: string): WidgetTree?

Return the last widget tree table passed to registerScreen / updateScreen for screenName.

modules/ui/getTheme

getTheme(): string

Get the name of the currently active theme.

modules/ui/getToken

getToken(name: string): string?

Look up a single design token value from the active theme.

modules/ui/getTokens

getTokens(): { [string]: string }

Get all design tokens from the active theme as a key-value map.

modules/ui/getWidgetProps

getWidgetProps(typeName: string): { WidgetPropDescriptor }?

Get the property definitions for a widget type.

modules/ui/getWidgetTypes

getWidgetTypes(): { string }

Get all available widget type names that can be used in widget trees.

modules/ui/hideScreen

hideScreen(name: string)

Hide a registered screen.

modules/ui/lastRegistration

lastRegistration(): { name: string, layer: number? }?

The name and layer passed to the most recent ui.registerScreen call, recorded synchronously at call time. A host that mounts a nested app reads this immediately after the mount to learn which screen the nested code registered, without intercepting the ui table.

modules/ui/lastValidation

lastValidation(screenName: string?): any

Validation diagnostics produced at the most recent registerScreen / updateScreen. With no args returns a { [screen] = entry } map; with a name returns that screen's entry or nil. Validation gated by world setting ui.validation = "off" | "warn" | "strict" (default "warn").

modules/ui/listScreens

listScreens(): { ScreenSummary }

List every registered screen with its current visibility, layer, and whether the screen has a populated root widget tree. Sorted by layer ascending, then name.

modules/ui/listThemes

listThemes(): { string }

List all registered theme names.

modules/ui/registerBackgroundShader

registerBackgroundShader(shaderHandle: any, width: number?, height: number?)

Register a screen-domain .shader as a UI background, drawn via the backgroundShader style. Takes the shader's asset handle from asset.resolve.

modules/ui/registerCallbackEnv

registerCallbackEnv(key: string, env: { [string]: any })

Register an environment table to receive widget-callback broadcasts: its global onCallback(id, value) fires for any widget callback not owned by a specific component instance — the same broadcast a component's onCallback receives. Keyed by key; re-registering the same key replaces the previous env. A component instance is folded into the callback dispatch automatically, so reach for this from a non-component context that hosts a UI surface (a scene entrypoint registering its own screen). Pair with ui.unregisterCallbackEnv(key) so the ref is released.

modules/ui/registerScreen

registerScreen(name: string, widgetTree: WidgetTree, layer: number?)

Register a named UI screen with a widget tree. Optional layer controls z-ordering (higher = on top). Tag-based grouping lives in Z.tags (Z.tags.set(name, { "editor" }) after register).

modules/ui/registerTheme

registerTheme(name: string, theme: ThemeDefinition)

Register a theme from a flat Luau table. Most callers should use Z.theme.register(name, table) which runs the cascade for them.

modules/ui/removeScreen

removeScreen(name: string)

Alias for ui.unregisterScreen.

modules/ui/resetAreaSize

resetAreaSize(id: string)

Clear a resizable area's remembered size (from a grip drag or ui.setAreaSize) so its declared — or content — size takes over again.

modules/ui/response

response(widgetId: string): WidgetResponse?

Per-widget interaction snapshot for the most recent frame. Returns { clicked, hovered, focused, changed, value } where clicked / changed mark transitions and hovered / focused mark current state.

modules/ui/screen

screen(name: string): { [string]: any }?

Get a screen proxy with methods like setResolution and rasterize.

modules/ui/screenSize

screenSize(): { width: number, height: number }

The UI coordinate space as { width, height } (logical points). This is the space area pos, anchors, and getLayoutInfo rects use — and it is NOT the pixel size of a capture screenshot, which may be downscaled. Use this for absolute area positioning (e.g. pinning a menu above a bottom taskbar) instead of guessing the size from a capture image.

modules/ui/scroll

scroll(deltaX: number, deltaY: number)

Simulate a mouse-wheel scroll event on the UI.

modules/ui/setAreaPos

setAreaPos(id: string, x: number, y: number)

Programmatically move a movable area widget to (x, y). Applied for one frame; subsequent frames let drag tracking take over.

modules/ui/setAreaSize

setAreaSize(id: string, w: number, h: number)

Programmatically set a resizable area's size (the user-size override) — for maximize / restore / tile. Persists until the area's declared width/height changes or ui.resetAreaSize(id) clears it.

modules/ui/setScreenRenderLayer

setScreenRenderLayer(name: string, mask: number)

Set a screen's render-layer membership bitmask. A screen draws into a camera or capture only when this mask intersects the camera's include mask — the same rule geometry follows. Content UI defaults to the ui bit; the editor places its chrome on EditorUI so agent captures can drop it. Masks come from __renderLayers.bit(name).

modules/ui/setScrollPosition

setScrollPosition(widgetId: string, offsetY: number)

Set the scroll offset of a scrollArea widget.

modules/ui/setShaderUniforms

setShaderUniforms(name: string, uniforms: { [string]: number })

Set uniform values on a registered background shader.

modules/ui/setTheme

setTheme(name: string)

Switch the active global theme by name.

modules/ui/showScreen

showScreen(name: string)

Make a registered screen visible.

modules/ui/unregisterCallbackEnv

unregisterCallbackEnv(key: string)

Remove an environment registered with ui.registerCallbackEnv. Its onCallback stops receiving broadcasts. No-op if key isn't registered.

modules/ui/unregisterScreen

unregisterScreen(name: string)

Remove a screen from the registry entirely. Unlike hideScreen, this deletes the entry so it no longer appears in listScreens or render iteration.

modules/ui/unregisterWidget

unregisterWidget(name: string)

Drop a registered custom widget kind. Subsequent references produce an unknown-widget-type diagnostic.

modules/ui/updateScreen

updateScreen(name: string, widgetTree: WidgetTree)

Replace the widget tree of an already-registered screen.

modules/ui/useStyles

useStyles(themeName: string)

Apply a registered style file's classes additively without changing the active theme.

modules/ui/widgetState

widgetState(widgetId: string, key: string, default: any?): any

Read per-widget cross-frame state. Returns the value previously written via widgetStateSet, or default (or nil). State is keyed by widget id and persists across re-renders within a screen's lifetime; cleared automatically when the owning screen is unregistered.

modules/ui/widgetStateClear

widgetStateClear(widgetId: string, key: string)

Remove a per-widget state entry.

modules/ui/widgetStateSet

widgetStateSet(widgetId: string, key: string, value: any)

Write per-widget cross-frame state. Replaces any existing value under (widgetId, key). Tables are stored by reference.

modules/unwatch

modules.unwatch(watcherId) -> boolean

Remove a previously registered module source watcher by its ID.

modules/userfile/README

require("@builtin/modules/api/engine/userfile") -- userfile (also available as global 'userfile')

User-system → engine file upload. Opens the user's own file picker (native OS dialog / Android / web browser) and brings the chosen file(s) into the engine. Public Luau surface over the __userfile Internal FFI namespace.

Usage: local userfile = require("@builtin/modules/api/engine/userfile") Also available as global: userfile

modules/userfile/pick

pick(opts: PickOpts?): PickResult

Open the user's system file picker and bring the chosen file(s) into the engine. Yields until the user finishes (call from a coroutine / task, like any task.await) and returns { cancelled, files = {{ name, mime, size, bytes?, vfsPath? }} }. Without writeTo each file carries bytes (a binary-safe string); with writeTo each carries vfsPath (read it with vfs.read). Cancelling returns { cancelled = true, files = {} }; a genuine failure (e.g. a lost browser user-activation gesture) raises an error.

modules/userfile/pickFolder

pickFolder(opts: PickOpts?): PickResult

Convenience for userfile.pick({ folder = true }) — pick a whole directory tree. Yields until the user finishes and returns the same result table as pick. On the web this degrades to a multi-file selection.

modules/vfs/README

require("@builtin/modules/api/engine/vfs") -- vfs (also available as global 'vfs')

Virtual filesystem — read, write, list, watch. Public Luau surface over the __vfs Internal FFI namespace.

Usage: local vfs = require("@builtin/modules/api/engine/vfs") Also available as global: vfs

modules/vfs/clearPlayShadow

clearPlayShadow(): boolean

Forget the entire play-shadow set after a bulk promote or discard. Tracking only — never touches the bytes.

modules/vfs/evict

evict(path: string, opts: VfsOpts?): boolean

Drop the in-memory bytes for path from the writable MemFs layer without removing the asset. Use after processing large binaries to reclaim RAM.

modules/vfs/exists

exists(path: string, opts: VfsOpts?): boolean

Is the path known to the VFS? Checks the Stage-1 metadata (.meta sidecar / ManifestView) — NOT "are the bytes locally cached?". Use vfs.read(path) ~= nil to confirm bytes are reachable.

modules/vfs/list

list(path: string?): { VfsListEntry }

List entries in a VFS directory.

modules/vfs/mkdir

mkdir(path: string, opts: VfsOpts?): boolean

Create a directory. mkdir -p semantics — idempotent. Errors if a file already exists at the same path.

modules/vfs/move

move(src: string, dst: string, opts: { quiet: boolean? }?): (boolean, string?)

Move a file from src to dst. By default fires the destination's write side effects; pass opts.quiet = true to suppress them.

modules/vfs/pendingWrites

pendingWrites(): { string }

List the /source paths with an in-flight local write the synced manifest has not reflected yet — the read-your-writes frontier. A just-written file appears here until its upload round-trips and the synced dirty state catches up; world.vcsStatus unions these so a fresh edit reads back as dirty immediately. Empty when fully synced.

modules/vfs/playShadowPaths

playShadowPaths(): { string }

List the /source paths edited during running play that are currently held as copy-on-write SHADOWS (MemFs-only, on-disk original untouched) — the universal play shadow-copy set. These are the in-play edits persist promotes over the originals on confirm, or drops on a guarded discard. Empty outside play or when nothing was edited.

modules/vfs/promotePlayShadow

promotePlayShadow(path: string): (boolean, string?)

Promote a single play-shadow edit into a canonical write. Re-asserts the live overlay bytes through the full write pipeline with the play write lock released, then unmarks the path. The bytes stay in the engine end to end, so binary content promotes exactly. Requires play to be paused.

modules/vfs/read

read(path: string, opts: VfsOpts?): string?

Read a file from the virtual filesystem. Binary-safe. Returns file contents as a string, or nil if the file is not known. Relative paths resolve under opts.root (default /source/). When called from a coroutine and the bytes aren't locally cached, transparently yields the coroutine while the lazy fetch runs.

modules/vfs/readAsync

readAsync(path: string, opts: VfsOpts?): string

Asynchronous binary-safe read. Returns a promise ID that resolves to the file contents. Useful for reading render textures from the main thread without blocking.

modules/vfs/reload

reload(modulePath: string?): boolean

Clear entries from the require() cache so the next require(name) re-runs the module's source. Pass a single module identity to drop only that entry; call with no arguments to drop every cached module.

modules/vfs/remove

remove(path: string, opts: VfsOpts?): (boolean, string?)

Remove a file. Refuses to remove directories unless opts.recursive = true. Refuses protected system roots.

modules/vfs/revertPlayShadow

revertPlayShadow(path: string): (boolean, string?)

Revert a single play-shadow edit: restore the pre-play copy captured at the first play-mode write (the last edit-mode state, unstaged edits included) into the live slot — or remove the file when it did not exist at that moment — then unmark the path. Hot-reload picks the original back up, so the running session actually reverts. Requires play to be paused.

modules/vfs/unmarkPlayShadow

unmarkPlayShadow(path: string): boolean

Forget a single play-shadow path after it has been promoted (saved over source) or discarded. Tracking only — never touches the bytes.

modules/vfs/unwatch

unwatch(watcherId: number): boolean

TRUSTED ONLY. Remove a previously registered VFS watcher.

modules/vfs/watch

watch(path: string, callback: (string, string) -> ()): number

TRUSTED ONLY. Register a callback that fires when a VFS path is written or removed. Two match modes: exact, or folder/prefix (key ends with /). Callback signature: function(path: string, kind: "write" | "remove"). Returns a watcher id for vfs.unwatch.

modules/vfs/write

write(path: string, content: string, opts: VfsOpts?): (boolean, string?)

Write content to a file. Binary-safe. Overwrites existing files by default — pass opts.overwrite = false to refuse to clobber. Locked in play mode (returns (false, errmsg)).

modules/video/README

require("@builtin/modules/api/engine/video") -- video (also available as global 'video')

Video playback — create / play / pause / seek / setRate / setLoop / destroy on render-target-backed video players. Public Luau surface over the __video Internal FFI namespace.

Usage: local video = require("@builtin/modules/api/engine/video") Also available as global: video

modules/video/create

create(url: string, options: VideoOptions?): string

Create a video player. Returns a texture handle (e.g. "video_0") usable directly in material.setTexture() — its frames sample like any other texture.

modules/video/destroy

destroy(handle: string): boolean

Destroy a video player and free the render target and all resources.

modules/video/getInfo

getInfo(handle: string): VideoInfo?

Get video information and current playback state.

modules/video/pause

pause(handle: string): boolean

Pause video playback. Can be resumed with video.play.

modules/video/play

play(handle: string): boolean

Start or resume video playback.

modules/video/seek

seek(handle: string, time: number): boolean

Seek to a specific time (seconds) in the video.

modules/video/setLoop

setLoop(handle: string, loop: boolean): boolean

Enable or disable looping.

modules/video/setRate

setRate(handle: string, rate: number): boolean

Set the playback speed multiplier. 1.0 = normal, 2.0 = double speed, 0.5 = half speed.

modules/video/stop

stop(handle: string): boolean

Stop video playback and reset to the beginning.

modules/watch

modules.watch(path, callback) -> number

Register a callback that fires when a module's source code changes via VFS write. Use for live-reload: re-require the module inside the callback. Returns a watcher ID.

modules/world_defaults/README

world_defaults

Grafts the per-world defaults + args + lifecycle-callback registry onto the world global. Companion to world_vcs.module. Backed by .world_settings for the persisted fields; callbacks live in in-process registries. Slot fields exposed on world.* (4 per-mode + 1 mode-agnostic): - avatar_default_edit AssetRef (tag: playerAvatar) - avatar_default_play AssetRef (tag: playerAvatar) - camera_default_edit AssetRef (tag: cameraBehavior) - camera_default_play AssetRef (tag: cameraBehavior) - startup_scene AssetRef — engine auto-loads this scene at boot when set Per-mode slots are selected by wld.mode() at spawner-resolution time (player_spawner / camera_spawner). Worlds that don't care about the split set both halves to the same value. All setters are play-mode-gated: writes target .world_settings (a source-VFS file) which is locked in play mode. The gate raises a typed error at the API boundary instead of letting the call hit a downstream "VFS write refused" error. Set engine.mode = "edit" first. All bundle-typed setters enforce the slot's required tag (§ 7 of the player-camera-unification plan). Writing a wrongly-tagged ref is refused with a clear message naming the missing tag.

modules/world_status/README

world_status

Composes world.status() + world.status_text() — a snapshot of world identity, mode, running flag, primary scene, all loaded scenes, and players across all scenes. Pure-Luau (no Rust); uses existing globals: world.guid / world.branch / world.title, engine.mode, layers.active, layers.list, scene proxy's players / camera registries, world.connectedUsers.localUser. Installed onto the world global by world_defaults.module/init.luau via the world-namespace metatable machinery (see § 4b of the player-camera-unification integration design).

  • api
  • reference