---
title: "tools"
description: "The tools namespace — the engine's Luau API reference for tools."
section: "API Reference"
slug: "api-tools"
canonical: "https://origozero.ai/docs/api-tools"
updated: "2026-09-07T03:15:14.026337611+00:00"
tags: ["api", "reference"]
---

# tools

The `tools` namespace — 422 functions.

## globals/tools/bind {#globals-tools-bind}

```lua
tools.bind(identity: string, positional: { any }?, named: { [string]: any }?, opts: BindOpts?) -> BindResult
```

Resolve a call's arguments against a tool's declared parameters,
turning named arguments into the positional call the tool actually
takes. This is the binder behind `zero <toolbox> <tool> --name value`
and the `use_tool` MCP tool's named `args`, so a name resolves the
same way whichever surface the caller reached for. Reads the schema
from `tools.get`, matches each name to a parameter (exactly first,
then case-insensitively), and reports the first unresolvable name,
a name that a positional argument already filled, a call longer than
the signature, and a required parameter skipped over while a later
one is filled. Resolves the call only — running it is `tools.use`.

**Parameters**

- `identity` `string` — Tool identity (`"<toolbox>.<name>"`, with or without the
leading `tools.`).
- `positional` `{ any }` _(optional)_ — Arguments already given by position, filling slots from 1.
- `named` `{ [string]: any }` _(optional)_ — Arguments given by name, `{ [parameterName]: value }`.
- `opts` `BindOpts` _(optional)_ — `order` — the order to visit `named` in, so the first problem
reported is the caller's first (defaults to sorted, for a stable
answer). `prefix` — written in front of every argument name in the
failure message, `"--"` for a shell flag. `whole` — read `named` as ONE
table argument when not one of its keys names a parameter, and as the
first argument written inline when only some of them do, for a surface
whose payload is ambiguous between the two readings.
`positionalCount` — how many slots `positional` fills, for a caller
that passed an explicit `nil` and so cannot be measured by length.

**Returns** `BindResult` — `{ ok, call, count, failure? }`. Call the tool with `table.unpack(call, 1, count)`; on failure `call` is empty and `failure` carries the reason.

```lua
bind("camera.lookAt", {}, { target = { 0, 5, 0 } })
bind("MaterialAuthor.fromColor", {}, { color = { 1, 0, 0 } }, { whole = true })
```

## globals/tools/create {#globals-tools-create}

```lua
tools.create(args: { [string]: any }) -> { ok: boolean, path: string?, identity: string?, signature: string?, error: string? }
```

Create a new tool on disk inside an EXISTING toolbox.
Scaffolds the `.tool/` folder via `asset.create("tool", …)`,
then writes the supplied code wrapped in a documented typed
function into `init.luau` (the `--!desc`/`--!arg`/`--!return`/
`--!example` doc block + signature built from the structured
metadata), the tags into `.metadata`, and a brief `README.md`
— so the authored tool is indistinguishable from a builtin.
Errors cleanly if the parent toolbox doesn't exist — call
`tools.createToolbox` first so the toolbox starts out with a
real description instead of the placeholder template body.
The engine's normal hot-reload pipeline picks up the new
files and binds the tool's global on the next pass.
Every toolbox created via `tools.createToolbox` ships a
`shared.module/` (the template default has `ok`/`fail`
result-envelope constructors; users can extend or replace it
via `sharedCode` at create time or by editing the module
later). The generated `init.luau` automatically declares
`local shared = require(".shared")` before your function
body, so the `code` you pass can reference `shared.ok(...)`
/ `shared.fail(...)` (or whatever the toolbox's custom
helpers expose) directly. If a toolbox doesn't have a
`shared.module/` for some reason, the require is skipped so
there's no dangling import to fail.

**Parameters**

- `args` `{ [string]: any }` — Structured tool definition.
Required: `name` (leaf, e.g. `"hello"`), `toolbox` (parent
toolbox, e.g. `"mytools"`), `code` (the Luau function body —
not a full module, just the statements that will become the
function's body), `description` (one or more sentences
describing what the tool does).
Optional but recommended: `args` (array of `{name, type,
description}` argument records), `returns` (`{type,
description}` for the return value), `examples` (array of
call-site code strings), `tags` (array of strings for search).

**Returns** `{ ok: boolean, path: string?, identity: string?, signature: string?, error: string? }` — `{ ok, path?, identity?, signature?, error? }`. `path` — the new `.tool/` folder's VFS path. `identity` — the tool's `<toolbox>.<name>` identity. `signature` — the derived `name(args) -> ret` signature.

```lua
tools.create({ name = "hello", toolbox = "mytools", description = "Greet.", code = "return 'hi'" })
```

## globals/tools/createToolbox {#globals-tools-createtoolbox}

```lua
tools.createToolbox(args: { [string]: any }) -> { ok: boolean, path: string?, identity: string?, error: string? }
```

Create a new toolbox folder. A toolbox is the namespace
container for tools — `.toolbox/` on disk; once tools are authored
inside it they are invoked as `tools.use("<name>", "<toolName>", …)`.
This call scaffolds the `.toolbox/` folder via
`asset.create("toolbox", …)` and overwrites the placeholder
`README.md` with a real description so the toolbox doesn't ship
the template stub. Optionally seeds `shared.module/` if the
caller supplies cross-tool helper code. Authoring a tool inside
this toolbox is the separate `tools.create` call.

**Parameters**

- `args` `{ [string]: any }` — Structured toolbox definition.
Required: `name` (the toolbox's leaf name, e.g. `"mytools"` —
becomes `<name>.toolbox/` on disk and the namespace for tool
identities), `description` (one or more sentences describing
the surface this toolbox exposes — what problem space the
tools cover and who calls them).
Optional: `sharedCode` — replacement body for the toolbox's
`shared.module/init.luau`. The toolbox template ALWAYS ships
a `shared.module/` with default `ok`/`fail` result-envelope
constructors so tools in this toolbox can `require(".shared")`
from day one. Supply `sharedCode` only when you want to
override that default with custom cross-tool helpers
(parsers, registries, …); the module body is left untouched
if you omit it. `path` — explicit VFS destination
(`/zero/source/...` form). Defaults to
`/zero/source/tools/<name>` if omitted; pass an explicit path
to author a library-scoped or package-internal toolbox.

**Returns** `{ ok: boolean, path: string?, identity: string?, error: string? }` — `{ ok, path?, identity?, error? }`. `path` — the new `.toolbox/` folder's VFS path. `identity` — the toolbox's registered identity (`<name>`).

```lua
tools.createToolbox({ name = "mytools", description = "Custom tooling for my workflow." })
```

## globals/tools/delete {#globals-tools-delete}

```lua
tools.delete(identity: string) -> { ok: boolean, path: string?, error: string? }
```

Remove a previously-authored tool by identity. Deletes the
`.tool/` folder and its contents from the VFS via
`vfs.remove(..., { recursive = true })`. The engine drops the
tool's global on the next hot-reload pass. Refuses to operate
on a path that doesn't exist (returns `{ ok = false }` with
an explanatory error).

**Parameters**

- `identity` `string` — Tool identity (`"<toolbox>.<name>"`).

**Returns** `{ ok: boolean, path: string?, error: string? }` — Table shaped `{ ok, path?, error? }`.

```lua
tools.delete("mytools.hello")
```

## globals/tools/get {#globals-tools-get}

```lua
tools.get(identity: string) -> ToolMeta?
```

Read back a tool's assembled metadata — description, typed
signature, per-argument docs, return, examples, and tags — gathered
from the four canonical sources in its `.tool/` folder. Individual
tools aren't entries in the asset index (only their parent toolboxes
are), so this resolves the toolbox via `asset.resolve(toolbox,
"toolbox")` and reads the tool relative to it. Returns `nil` when the
toolbox or tool is missing.

**Parameters**

- `identity` `string` — Tool identity (`"<toolbox>.<name>"`, with or without the
leading `tools.`).

**Returns** `ToolMeta?` — The assembled metadata `{ name, signature?, description, args, varargs, returns?, examples, typeDefs, tags }`, or nil if not found. Each entry in `args` is `{ name, type, description, optional }` in signature order, so a caller can tell a required parameter from an optional one without parsing the rendered signature. `varargs` is true when the tool accepts trailing arguments beyond the named parameters. `typeDefs` carries `{ name, definition }` for every named type the signature refers to, resolved transitively, so a signature reading `spawn(opts: SpawnOpts)` can be called without opening the tool's source.

```lua
local meta = tools.get("entityOps.modify"); print(meta.signature)
```

## globals/tools/list {#globals-tools-list}

```lua
tools.list(tier: (number | string)?, toolbox: string?) -> { stdout: string, value: any }
```

Discover registered code-mode tools, grouped by toolbox.
Default tier returns just `{ <toolbox> = { name, name, … } }`
plus a formatted `stdout` listing every toolbox on one line
with its tools — compact enough that listing the whole
catalogue doesn't flood agent context. Higher tiers enrich
each entry with its one-line description (tier 2) or its full
assembled metadata — signature, args, returns, examples (tier 3).
Pass a toolbox name to restrict the output to one toolbox.

**Parameters**

- `tier` `(number | string)` _(optional)_ — Verbosity level: `1` (default) toolbox summary,
`2` adds one-line descriptions, `3` adds the full assembled
metadata. Pass a string instead to restrict to that toolbox at tier 1.
- `toolbox` `string` _(optional)_ — Optional toolbox name to restrict the output to
(e.g. `"entityOps"`). Passing a string as the first arg also works.

**Returns** `{ stdout: string, value: any }` — Table shaped `{ stdout: string, value: <grouped> }`. For tier 1, `value` is `{ [toolbox] = { toolName, … } }`. For tier ≥ 2, `value` is `{ [toolbox] = { { name, identity, signature?, description, … }, … } }`.

```lua
tools.list()              -- default: every toolbox, names only
tools.list(2)             -- include first-line descriptions
tools.list("entityOps")     -- only the `entityOps` toolbox at tier 1
tools.list("entityOps", 2)  -- only `entityOps`, with descriptions
```

## globals/tools/search {#globals-tools-search}

```lua
tools.search(query: string?, opts: { toolbox: string?, limit: number? }?) -> { stdout: string, value: any }
```

Search registered code-mode tools by relevance, the canonical
in-engine tool-discovery entry point — callable from `execute`
Luau so an agent can find the tool it needs without leaving the
engine. Enumerates every tool across every toolbox (reusing the
same filesystem discovery `tools.list` uses), then scores each
against the query: a token hit in the tool NAME weighs most,
then its DESCRIPTION, then its TAGS. Tools scoring above zero are
returned best-first. With an empty/omitted `query` and no
`toolbox` filter, returns the whole catalogue (name + signature
+ toolbox) so the agent can browse. Each returned `name` is the
tool's `<toolbox>.<tool>` identity; invoke it with the `use_tool`
MCP tool (`toolbox` + `tool` + `args`, an array in signature order
or an object naming the parameters), the form each entry's
`examples` are rendered in.

**Parameters**

- `query` `string` _(optional)_ — Free-text search string. Tokenized lowercase on
non-alphanumeric boundaries; each token is matched against tool
name, description, and tags. Empty or nil with no `toolbox`
filter lists every tool.
- `opts` `{ toolbox: string?, limit: number? }` _(optional)_ — Optional filters table.
`toolbox` — keep only tools whose owning toolbox exactly matches.
`limit` — maximum entries to return (default `10`; pass a larger value to
return more, up to every match — there is no upper cap).

**Returns** `{ stdout: string, value: any }` — Table shaped `{ stdout: string, value: { SearchEntry } }`. `value` is the ranked array; each entry is `{ name = "<toolbox>.<tool>", signature?, description, toolbox?, tags?, examples? }`. `stdout` is a one-line human summary of the match count.

```lua
tools.search("spawn camera")
tools.search("material", { limit = 5 })
tools.search("", { toolbox = "physics" })   -- list a toolbox
tools.search("")                             -- list everything
```

## globals/tools/toolboxes {#globals-tools-toolboxes}

```lua
tools.toolboxes() -> { stdout: string, value: { { toolbox: string, purpose: string, toolCount: number } } }
```

Discover the registered toolboxes as a grouped overview — one
row per toolbox with its one-line purpose and tool count. The
toolbox-first entry point to tool discovery: an agent navigates by
DOMAIN (which toolbox), then drills into a toolbox's tools with
`tools.search("", { toolbox = "<name>" })`. `purpose` is sourced from
the toolbox's `README.md` (its first descriptive line), falling back
to the `.metadata` `description`.

**Returns** `{ stdout: string, value: { { toolbox: string, purpose: string, toolCount: number } } }` — Table shaped `{ stdout: string, value: { { toolbox, purpose, toolCount } } }`. `value` is toolbox-name-sorted; each entry is `{ toolbox = "<name>", purpose = "<one line>", toolCount = <n> }`.

```lua
tools.toolboxes()
```

## globals/tools/tryUse {#globals-tools-tryuse}

```lua
tools.tryUse(toolbox: string, tool: string, ...: any?) -> ToolCall
```

Invoke one code-mode tool and report the outcome as a value. Takes the
same arguments as `tools.use` and resolves the toolbox the same way, and
returns `{ ok, value, error, toolbox, tool }` for every outcome — an
unknown toolbox, an unknown tool, a tool that reported failure, and a tool
that raised all arrive as `ok = false` with the reason in `error`. The
fields are the ones the `use_tool` MCP surface reports, so a script
comparing several calls in one pass reads the same names it would over
MCP, and reads them without wrapping each call in `pcall`.

**Parameters**

- `toolbox` `string` — The owning toolbox — an ambient toolbox by its name (`"entityOps"`)
or a library toolbox by its scoped identity (`"@lib::ns"`).
- `tool` `string` — The tool's leaf name within that toolbox (`"spawn"`).
- `...` `any` _(optional)_

**Returns** `ToolCall` — `{ ok, value, error, toolbox, tool }`. `value` carries the tool's own value when `ok` is true; `error` carries the reason when it is false.

```lua
tryUse("entityOps", "spawn", { Model = { model = "cube" } })
```

## globals/tools/use {#globals-tools-use}

```lua
tools.use(toolbox: string, tool: string, ...: any?) -> ...any
```

Invoke one code-mode tool by naming its toolbox and tool explicitly —
the Luau-code counterpart to the `use_tool` MCP tool. The normal path is
the `use_tool` MCP tool; this is the escape hatch for editor panels and
shipped modules that script a tool from engine Luau. Resolves the toolbox
from the runtime store (builtin, library, and user-authored runtime
toolboxes all work), calls the named tool with the remaining args, and
returns the tool's value directly — unwrapping the `ZmToolResult`
envelope and RAISING a Luau error when the tool fails. Because every call
names both toolbox and tool, it can never read as a `tools.<box>`
namespace. `tools.tryUse` takes the same arguments and reports the
outcome as a value instead of raising.

**Parameters**

- `toolbox` `string` — The owning toolbox — an ambient toolbox by its name (`"entityOps"`)
or a library toolbox by its scoped identity (`"@lib::ns"`).
- `tool` `string` — The tool's leaf name within that toolbox (`"spawn"`).
- `...` `any` _(optional)_

**Returns** `...any` — Everything the tool returned, in the order it returned it — a tool declaring `(path, reason, frame)` hands back all three, so a second and third value the tool states are read the way the tool's own signature says. Raises on an unknown toolbox / tool or a tool-reported failure. These are the tool's OWN values, not the `{ ok, value }` envelope the `use_tool` MCP surface reports — reach for `tools.tryUse` when the call's outcome is what you want.

```lua
use("entityOps", "spawn", { Model = { model = "cube" } }, { position = {0, 2, 0} })
```

## tools/MaterialAuthor/composeFolder {#tools-materialauthor-composefolder}

```lua
MaterialAuthor.composeFolder(folder: string, opts?: ComposeFolderOpts) -> AssetRef<material>
```

Scan `folder` for textures (loose images and `.texture` assets), infer each one's PBR slot from its name, and compose them into a single new Material. Errors when no slot-mappable texture is found. Returns the new `AssetRef<material>`.

**Parameters**

- `folder` `string`
- `opts` `ComposeFolderOpts` _(optional)_

**Returns** `AssetRef<material>`

```lua
"/zero/source/textures/brick"
"/zero/source/pbr/metal", { name = "Metal", metallic = 1 }
"/zero/source/pbr/tiles", { name = "Tiles", tiling = 8 }
```

## tools/MaterialAuthor/duplicate {#tools-materialauthor-duplicate}

```lua
MaterialAuthor.duplicate(source: string | AssetRef<material>, newName: string, overrides?: MaterialOverrides) -> AssetRef<material>
```

Duplicate a material under `newName`, applying `overrides`. The source is resolved by AssetRef / identity / name; its `mat.yaml` definition is cloned (shader + every property + every texture binding carry over) under the new identity with `overrides` merged in, then written as the new material. Returns the new `AssetRef<material>`.

**Parameters**

- `source` `string | AssetRef<material>`
- `newName` `string`
- `overrides` `MaterialOverrides` _(optional)_

**Returns** `AssetRef<material>`

```lua
"Gold", "WetGold", { roughness = 0.05 }
goldRef, "GoldEmissive", { emissive = { 1, 0.8, 0, 1 } }
```

## tools/MaterialAuthor/fromColor {#tools-materialauthor-fromcolor}

```lua
MaterialAuthor.fromColor(color: Color, opts?: FromColorOpts) -> AssetRef<material>
```

Create a flat-colour material in one call. `color` is `{r,g,b}` / `{r,g,b,a}` (array) or `{ r=, g=, b=, a= }` (keyed), channels in [0,1]. Returns the new `AssetRef<material>`.

**Parameters**

- `color` `Color`
- `opts` `FromColorOpts` _(optional)_

**Returns** `AssetRef<material>`

```lua
{ 1, 0, 0 }, { name = "Red" }
{ 1, 0.84, 0 }, { name = "Gold", metallic = 1, roughness = 0.2 }
```

## tools/MaterialAuthor/fromTexture {#tools-materialauthor-fromtexture}

```lua
MaterialAuthor.fromTexture(texture: string | table, opts?: FromTextureOpts) -> AssetRef<material>
```

Create a new material that binds `texture` to one slot. Accepts a `renderer.texture` handle, an `AssetRef`/`.texture` identity, or a VFS image path. The slot is taken from `opts.slot`, else inferred from the texture name (`*_normal` → `normal_texture`, …), else `base_color_texture`. Returns the new `AssetRef<material>`.

**Parameters**

- `texture` `string | table`
- `opts` `FromTextureOpts` _(optional)_

**Returns** `AssetRef<material>`

```lua
"logo.texture"
tex, { name = "Brick", slot = "base_color_texture", roughness = 0.6 }
tex, { name = "Ground", tiling = 300 }
```

## tools/animation/observe {#tools-animation-observe}

```lua
animation.observe(target?: (string | EntityRef)) -> AnimObserveResult
```

What the engine is posing right now, one line per body: whether its pose is actually changing, the clip driving it with its playhead, how many of its bones that clip reaches, and — when it is not moving — the one reason why. Call it with no arguments for every body in the scene, or pass an entity to narrow it to that one (a character root resolves down to the skinned body under it).

**Parameters**

- `target` `(string | EntityRef)` _(optional)_

**Returns** `AnimObserveResult`

```lua
"hero"
```

## tools/animation/play {#tools-animation-play}

```lua
animation.play(animation: string | AssetRef<animation>, entityArg: string | EntityRef, opts?: AnimPlayOpts) -> AnimPlayResult
```

Play an animation clip on an entity's skinned body for a test window, then clean up. Resolves `animation` by identity or name and `entity` by id or name, finds the SkinnedModel to drive, and plays it through the canonical AnimGraph — the same path ClipPlayer uses. Returns `{ ok = false, error }` when the clip, the entity, or a SkinnedModel under it cannot be found. When the body already runs an animation the clip overrides it for the window and the original resumes untouched on cleanup.

**Parameters**

- `animation` `string | AssetRef<animation>`
- `entityArg` `string | EntityRef`
- `opts` `AnimPlayOpts` _(optional)_

**Returns** `AnimPlayResult`

```lua
"A_Walk_F_Masc", "hero"
"Idle", "hero", { offset = "5s", loop = true, duration = 8 }
```

## tools/api/docs {#tools-api-docs}

```lua
api.docs(...) -> string
```

Browse engine VFS documentation under `/zero/docs`. No args lists top-level categories. Partial paths list a directory's entries (one entry per line, trailing `/` on directories). Full paths return the document content as a string. Each argument is a single path segment — they are joined with `/` to form the lookup path.

**Returns** `string`

```lua
-- list categories
"api"                     -- list namespaces
"api", "physics"          -- list physics methods
"api", "physics", "raycast" -- full raycast docs
```

## tools/api/searchDocs {#tools-api-searchdocs}

```lua
api.searchDocs(query: string) -> string
```

Search the engine's documentation: the generated reference pages under `/zero/docs` AND the prose guides that explain the concepts behind them. The query is split on whitespace; a doc matches when EVERY word appears (case-insensitive substring) in its PATH or its BODY text. Path matches rank first, then body-only matches. Searches content, so a concept like "global illumination" or "indirect light" surfaces the tools/components/guides that describe it — not just docs named that. Every returned line is a path `vfs.read` accepts.

**Parameters**

- `query` `string`

**Returns** `string`

```lua
"velocity"
"global illumination"
"indirect light"
"voxel pivot"
```

## tools/appearance/replaceWithContent {#tools-appearance-replacewithcontent}

```lua
appearance.replaceWithContent(targets: eo.Targets, source: string, opts?: ReplaceOpts) -> ReplaceReport
```

Replace one or many entities with a content asset, resolution-first: pass a name / path / identity STRING (limit the search with `type`), and the tool resolves it and reports the canonical asset it hit. Each target entity (and its WHOLE subtree) is removed, and a fresh spawn of the content takes its slot — same parent, local position, rotation, and name; scale kept unless `keepScale = false`. Components on the removed entities do not carry over. Targets are entity names or ids, one or a list, or a `scene.find` QUERY resolved for you — `{ name = { "tree" } }`, `{ component = { "Light" } }`, `{ under = { "forest" } }`. `dryRun = true` resolves everything and returns the plan (which entities would be removed, how big each subtree is, what would spawn) without mutating.

**Parameters**

- `targets` `eo.Targets`
- `source` `string`
- `opts` `ReplaceOpts` _(optional)_

**Returns** `ReplaceReport`

```lua
"rider_blockout", "a_snowboarder"
{ "tree_1", "tree_2", "tree_3" }, "pine", { type = "bundle" }
{ "tree_1", "tree_2" }, "pine", { dryRun = true }
```

## tools/appearance/setModel {#tools-appearance-setmodel}

```lua
appearance.setModel(root: string, model: string | table, opts?: AppearanceOpts) -> AssignSummary
```

Assign a model (mesh) to the Model / SkinnedModel-bearing entities found by searching an entity hierarchy from `root`. Sets the `model` field on each match's Model / SkinnedModel. You don't have to locate the exact entity; give the root and the model, and this walks the subtree. By default it assigns to EVERY match under the root; pass `{ first = true }` for only the first match.

**Parameters**

- `root` `string`
- `model` `string | table`
- `opts` `AppearanceOpts` _(optional)_

**Returns** `AssignSummary`

```lua
"Rocks", "sphere"
"Rocks", boulderMeshRef, { match = "rock_%d+" }
```

## tools/appearance/swapMaterials {#tools-appearance-swapmaterials}

```lua
appearance.swapMaterials(material: string | AssetRef<material>, opts?: SwapMaterialsOpts) -> AssignSummary
```

Batch-assign one material across entity hierarchies in a single call. `material` is a string — combed against EVERY material in the project (exact identity wins, then exact leaf name, then the first case-insensitive substring match) — or an AssetRef<material> envelope, used directly. Targets come from component schemas: every targeted entity's components are enumerated, and every public field declared `Field.resource("material", …)` / `Field.assetRef("material", …)` (on Model, SkinnedModel, and any other component that declares one) is set to the resolved material. `opts.roots` selects the hierarchies — one entity id/name, an array of ids/names/entity proxies, or absent for the ENTIRE scene; each is scanned recursively, and entities reached through more than one root are assigned once. A blue outline flashes on each affected entity for visual confirmation.

**Parameters**

- `material` `string | AssetRef<material>`
- `opts` `SwapMaterialsOpts` _(optional)_

**Returns** `AssignSummary`

```lua
"weathered_stone"  -- whole scene
goldRef, { roots = "Castle", first = true }
"brick", { roots = { "wall_a", "wall_b" }, match = "wall" }
```

## tools/appearance/swapShader {#tools-appearance-swapshader}

```lua
appearance.swapShader(roots?: (string | { any }), shader: string | AssetRef<shader>, opts?: AppearanceOpts) -> ShaderSwapResult
```

Convert every material in use across entity hierarchies to a new shader, in one call — "convert every single material in this scene to the ps1 shader". Scans the targeted hierarchies recursively, reads every component public field declared `Field.resource("material", …)` / `Field.assetRef("material", …)` (discovered from the component schemas, so any component carrying a materialRef field participates), collects the DISTINCT materials those fields hold, and routes each through the material asset's `setShader` — which preserves texture links and property values by canonical role (`MAIN_TEX`→`albedo`→`base_color_texture` and friends), rewrites the material asset's `mat.yaml`, and marks every instance dirty so the change persists and propagates. For a material you already hold, call `matRef:setShader(shader)` directly — this tool is for discovering the materials you'd otherwise have to enumerate yourself.

**Parameters**

- `roots` `(string | { any })` _(optional)_
- `shader` `string | AssetRef<shader>`
- `opts` `AppearanceOpts` _(optional)_

**Returns** `ShaderSwapResult`

```lua
nil, "@builtin::shaders.ps1"  -- whole scene
"Castle", unlitShaderRef
{ "wall_a", "wall_b" }, "pbr"
```

## tools/assets/backfillSearchMeta {#tools-assets-backfillsearchmeta}

```lua
assets.backfillSearchMeta(opts?: BackfillOpts) -> BackfillResult
```

Re-derive the search `.metadata` (component set, humanoid flag) for every existing asset of the given types by invoking each asset's `reindexSearchMeta` ref method. Assets minted before the deriver existed carry no `.metadata.components` / `.metadata.humanoid`, so their search facets read false until this runs. Idempotent — re-running recomputes the same values. Only assets whose type exposes `reindexSearchMeta` are touched; the rest are counted as scanned and left alone.

**Parameters**

- `opts` `BackfillOpts` _(optional)_

**Returns** `BackfillResult`

```lua
{ types = { "bundle" } }
```

## tools/assets/describe {#tools-assets-describe}

```lua
assets.describe(assetName: string, opts?: DescribeOpts) -> string
```

Describe an asset in agent-readable markdown: identity, type, scope, origin, description (README lead), tags, a **Behavior** section listing the operations any asset of this type answers to (its assetType's `ref:method(...)` surface, with signatures and descriptions), and a type-specific detail section (fields, events, and the component's own instance methods for a component; exports for a module; the call contract for a tool; ...). Every method, event, and field is rendered as a full signature so nothing needs the source read. Resolves `assetName` the same way `assets.find` results resolve. Pairs with `assets.find` (find -> describe).

**Parameters**

- `assetName` `string`
- `opts` `DescribeOpts` _(optional)_

**Returns** `string`

```lua
"@builtin::components.Camera"
```

## tools/assets/fetchInUse {#tools-assets-fetchinuse}

```lua
assets.fetchInUse(targets?: (string | EntityRef | { string | EntityRef })) -> FetchSummary
```

Fetch the unique set of asset references in use on the targeted entities — materials, meshes, textures, bundles, and every other category a component field can hold. `targets` is one entity id/name, an array of ids/names/entity proxies, or nil for EVERY entity in the scene; root targets are scanned recursively through their children. For each entity, every component's public data is scanned (nested tables to depth 8) for AssetRef envelopes; refs are deduped by guid and returned with the entities that use them — the agent's one-call inventory of what the targeted content is made of.

**Parameters**

- `targets` `(string | EntityRef | { string | EntityRef })` _(optional)_

**Returns** `FetchSummary`

```lua
-- everything the scene references
"Castle"     -- everything the Castle hierarchy uses
{ "wall_a", "wall_b" }
```

## tools/assets/find {#tools-assets-find}

```lua
assets.find(substring: string, category?: string, opts?: { subassets: boolean? }) -> FindSummary
```

Find every asset in the entire project whose identity contains `substring` (case-insensitive). Searches the full asset index — every category, every library — and returns the matches grouped by category. Pass `category` to restrict the search to one kind (discover the available kinds via `asset.categories()`).

**Parameters**

- `substring` `string`
- `category` `string` _(optional)_
- `opts` `{ subassets: boolean? }` _(optional)_

**Returns** `FindSummary`

```lua
"car"
"stone", "material"
"wheel", nil, { subassets = true }
```

## tools/assets/fromEntity {#tools-assets-fromentity}

```lua
assets.fromEntity(targets?: (string | EntityRef | { string | EntityRef }), opts?: FromEntityOpts) -> FromEntitySummary
```

Capture everything an entity is carrying at RUNTIME — every live procedural mesh / texture handle held in a component's resource field, across the whole hierarchy — as durable assets, and repoint each field at the new persistent asset. A runtime mesh (`renderer.mesh.create`) has no asset behind it, so anything keyed to it (a GI bake, a saved scene) is session-only; this makes it permanent in one call. Every public resource field of every component participates (discovered by field reflection), so it reifies ALL of an entity's runtime content, not a single hard-coded type. Persistent references (already assets) are left untouched. In edit mode the new assets persist to the world's source; in play mode they land in the ephemeral runtime store, so run this in EDIT to make content durable. Runtime resources shared across several fields are reified once and every field is repointed at the one asset.

**Parameters**

- `targets` `(string | EntityRef | { string | EntityRef })` _(optional)_
- `opts` `FromEntityOpts` _(optional)_

**Returns** `FromEntitySummary`

```lua
-- freeze every runtime resource in the scene
"cornellRoom"             -- freeze the cornellRoom hierarchy's runtime meshes
nil, { dryRun = true }    -- report what would be reified, write nothing
```

## tools/assets/generatePreviews {#tools-assets-generatepreviews}

```lua
assets.generatePreviews(opts?: GeneratePreviewsOpts) -> GenerateSummary
```

Queue a render of the missing `preview.png` for every world material, texture, and bundle under `root` (default: the whole world source), and remove empty leftover `README.md` files inside the visited assets. Skips assets whose preview already exists unless `force` is set. Returns as soon as the scan finishes; the queued renders drain serially in the background — poll `assets.previewStatus` until `pending` is 0.

**Parameters**

- `opts` `GeneratePreviewsOpts` _(optional)_

**Returns** `GenerateSummary`

```lua
{ root = "/source/projects/mygame" }
```

## tools/assets/new {#tools-assets-new}

```lua
assets.new(source: string | RuntimeHandle, name?: string, opts?: NewOpts) -> AssetRef<mesh | texture>
```

Persist a LIVE runtime GPU resource into a durable asset. `source` is a resource handle from `renderer.mesh.create` / `renderer.texture.create`, or a bare guid string naming a GPU-resident resource (paired with `opts.category`). The resource is read back from the GPU (yields a frame or two), encoded, and written as a new asset via `asset.create`, so anything keyed to it — a GI bake, a saved scene, a material slot — survives a reload instead of regenerating with a fresh identity. In edit mode the asset persists to the world's source; in play mode it lands in the ephemeral runtime store, so reify in EDIT to make content durable. Raw in-hand geometry/pixels with no GPU resource go through `asset.create(<type>, name, data)` directly — this tool freezes a live handle.

**Parameters**

- `source` `string | RuntimeHandle`
- `name` `string` _(optional)_
- `opts` `NewOpts` _(optional)_

**Returns** `AssetRef<mesh | texture>`

```lua
renderer.mesh.create({ positions = p, indices = i }), "tree"
meshGuid, "terrain", { category = "mesh" }
```

## tools/assets/previewStatus {#tools-assets-previewstatus}

```lua
assets.previewStatus() -> PreviewStatus
```

Report the preview work queue: renders still pending, the asset currently rendering, session totals, and the most recent failures. Poll after `generatePreviews` until `pending` reaches 0 and `active` is empty.

**Returns** `PreviewStatus`

## tools/assets/typeBehavior {#tools-assets-typebehavior}

```lua
assets.typeBehavior(name: string) -> BehaviorResult
```

What an asset of this TYPE can do — the operations every ref of it answers to (`ref:method(...)`), the lifecycle hooks it runs, and whether it can become an entity in a scene. This is the difference between knowing a type exists and knowing what it gives you: a type that already evaluates, instantiates, validates and recompiles itself is scaffolding to build inside, not a name in a list. Each operation carries the type author's own three answers: what it does (`description`), what to pass it and what it hands back (`signature`, the arguments and the declared return), and what that return means (`returns`). Read it with `assets.typeDoc` for the prose, and `assets.types` to find the name.

**Parameters**

- `name` `string`

**Returns** `BehaviorResult`

```lua
"procGraph"
```

## tools/assets/typeDoc {#tools-assets-typedoc}

```lua
assets.typeDoc(name: string) -> { [string]: any }
```

Read one asset type's own documentation — what the type IS, the shape of its folder, how it is authored, and what it is for. This is the type's README as its author wrote it, so it says things no signature can: which file is the source, what regenerates when, what the type does for you. Read this before building anything that resembles what the type describes. `assets.types` finds the name to pass here.

**Parameters**

- `name` `string`

**Returns** `{ [string]: any }`

```lua
"procGraph"
"terrain"
```

## tools/assets/types {#tools-assets-types}

```lua
assets.types(query?: string) -> TypesResult
```

Every KIND of asset this engine can make — the scaffolding available to build inside, as opposed to `assets.find` (content that exists) or `search_tools` (operations you can call). Each row is a registered assetType: what it is, the `asset.create` call that makes one, and whether it can become an entity in a scene. Reach for this BEFORE writing a system of your own. A job that looks like "no tool does this, so I will write it" is usually a job with a type already shaped for it — a graph that regenerates from parameters, a terrain, a voxel template, a population — and building beside one of those is a few lines where building from scratch is a project.

**Parameters**

- `query` `string` _(optional)_

**Returns** `TypesResult`

```lua
"procedural"
"terrain"
```

## tools/assets/users {#tools-assets-users}

```lua
assets.users(assetArg: string | AssetRef) -> UsersSummary
```

Find every entity in the active layer that uses an asset. Give the asset as a word — the entire project is combed case-insensitively and the first matching asset is used — or as an AssetRef you already hold. Every entity's component data is scanned recursively for references to that asset, and each use site is reported with the entity, component, and field it lives in.

**Parameters**

- `assetArg` `string | AssetRef`

**Returns** `UsersSummary`

```lua
"pickup_car"
goldMaterialRef
```

## tools/baking/__all {#tools-baking-all}

```lua
baking.__all(opts: AllOpts, scope?: (string | EntityRef | { string | EntityRef })) -> AllResult
```

**Parameters**

- `opts` `AllOpts`
- `scope` `(string | EntityRef | { string | EntityRef })` _(optional)_

**Returns** `AllResult`

## tools/baking/all {#tools-baking-all}

```lua
baking.all(opts?: AllOpts) -> AllResult
```

Run the entire GI bake in one call — static-surface lightmaps, an auto-placed irradiance probe volume for moving entities, and auto-placed reflection probes — then report what remains. This is the finalize step for a static scene: it takes the scene from "everything recomputed every frame" to baked. Each stage can be skipped (`skipLightmaps` / `skipProbes` / `skipReflections`); `resolution` / `samples` tune quality. Returns each stage's result plus a fresh `remaining` coverage report. This runs for many seconds on a real scene and is handed back as a background task: check how far it has got with this toolbox's `status` tool, and a `lightmap bake finished` notice reports the end.

**Parameters**

- `opts` `AllOpts` _(optional)_

**Returns** `AllResult`

```lua
-- bake everything in the scene
{ resolution = 128, samples = 128 }
{ scope = "arena", skipReflections = true }
```

## tools/baking/clear {#tools-baking-clear}

```lua
baking.clear(opts?: ClearOpts) -> ClearResult
```

Clear baked GI across a scope — restores each static surface's original material (removing its lightmap), despawns irradiance-probe volumes, unregisters reflection probes, and deletes the cubemap assets their bake wrote. A probe attached to a scene object loses its `ReflectionProbe` component and the object stays; an entity the bake placed to hold a probe is despawned. Use it to re-bake from a clean slate or strip GI from a region. Each category can be kept (`keepLightmaps` / `keepProbes` / `keepReflections`). Returns how many of each were cleared.

**Parameters**

- `opts` `ClearOpts` _(optional)_

**Returns** `ClearResult`

```lua
-- clear everything in the scene
{ scope = "arena" }
{ keepReflections = true }
```

## tools/baking/detect {#tools-baking-detect}

```lua
baking.detect(scope?: (string | EntityRef | { string | EntityRef })) -> DetectReport
```

Report what in the scene needs baking / re-baking and why — the bake-coverage check. Scans the scene (or a `scope`) for the state that quietly wrecks performance and leaves indirect light missing: static geometry with no bake (no lightmap and no probe-field coverage), shadow-casting point/spot lights re-rendering their shadow every frame, movers outside every probe volume's field, and reflective scenes with no reflection probe. Each finding names the one call that fixes it. Returns `clean = true` with an empty findings list when the scene is already covered.

**Parameters**

- `scope` `(string | EntityRef | { string | EntityRef })` _(optional)_

**Returns** `DetectReport`

```lua
-- whole scene
"interior"
```

## tools/baking/lightmaps {#tools-baking-lightmaps}

```lua
baking.lightmaps(opts?: LightmapOpts) -> LightmapResult
```

Bake static-geometry lightmaps across a scope in one call — resolves the static surfaces under `scope` (every visible `Model` whose `resolveMobility()` is `"static"`; movable geometry, physics bodies, and players sample probe volumes instead) and bakes full irradiance into a per-surface lightmap, no per-entity setup. Surfaces whose UVs tile or overlap are bound to the covering probe volume instead (counted in `probeLit`); set `Model.mobility` to override a wrong mobility derivation. `match` narrows to surfaces whose name contains a substring; `resolution` / `samples` / `intensity` set quality. Returns how many baked, how many went to the probe field, how many were skipped, and a per-surface report. This runs for many seconds on a real scene and is handed back as a background task: check how far it has got with this toolbox's `status` tool, and a `lightmap bake finished` notice reports the end.

**Parameters**

- `opts` `LightmapOpts` _(optional)_

**Returns** `LightmapResult`

```lua
-- whole scene
{ scope = "level_geo", resolution = 128, samples = 32 }
{ match = "floor" }
```

## tools/baking/mobility {#tools-baking-mobility}

```lua
baking.mobility(opts?: MobilityOpts) -> MobilityResult
```

Report (and optionally batch-set) geometry mobility across a scope. Every `Model` in `scope` is listed with its authored `mobility` field and what it resolves to — static geometry receives lightmaps and occludes baked light; movable geometry samples probe volumes. Pass `set` to override the field on every matched Model in one call (the exclusion switch when the automatic derivation gets an entity wrong); omit it to just inspect. `match` narrows by name substring.

**Parameters**

- `opts` `MobilityOpts` _(optional)_

**Returns** `MobilityResult`

```lua
-- report the whole scene
{ match = "crate", set = "static" }
{ scope = "props", set = "movable" }
```

## tools/baking/probes {#tools-baking-probes}

```lua
baking.probes(opts?: ProbeOpts) -> ProbeResult
```

Place irradiance light-probe volumes automatically and bake them — the whole light-probe pass in one call, no hand placement. Gathers the scene's point/spot lights (limited by `scope`), CLUSTERS them by influence overlap, and drops one right-sized volume per cluster (so separated rooms get separate volumes, not one box over the dead space between them). Probe density comes from `spacing` (world units between probes) with a hard `maxProbes` cap per volume, so a big volume gets coarser probes rather than a runaway grid; `maxVolumes` bounds the volume count by merging the nearest clusters. When a scene has no point/spot lights (sun/ambient only), it falls back to one volume over the scene's geometry bounds. Each baked field publishes into the renderer's irradiance-volume set and persists into the scene's baked-lighting container — every standard-PBR fragment inside a volume (movers, avatars, freshly spawned props) samples it with no per-entity setup, and a fresh boot restores it with no re-bake. Returns each placed volume (id, bounds, grid resolution, probe count) plus totals. The same placement is available to component/`execute` code as `require("@builtin::systems.globalIllumination.volumeProbe").autoPlace(opts)`.

**Parameters**

- `opts` `ProbeOpts` _(optional)_

**Returns** `ProbeResult`

```lua
-- whole scene, defaults
{ spacing = 2, samples = 128 }
{ scope = "interior", maxProbes = 4096 }
```

## tools/baking/reflections {#tools-baking-reflections}

```lua
baking.reflections(opts?: ReflectionOpts) -> ReflectionResult
```

Place reflection probes across the scene automatically and bake them — the whole reflection pass in one call, no hand placement. The tool bounds the scene's geometry (limited by `scope`), spreads a small set of probes evenly through that volume, and bakes each into its cubemap; reflective surfaces then sample the nearest probe(s), proximity-blended by the renderer. `count` defaults to a value derived from the scene size and is clamped to the renderer's 8-probe budget; `radius` defaults to a size that overlaps neighbours for seamless coverage; `bake = false` places without baking. Returns the placed probes (id, position, radius) and how many baked.

**Parameters**

- `opts` `ReflectionOpts` _(optional)_

**Returns** `ReflectionResult`

```lua
-- whole scene, auto count
{ count = 4 }
{ scope = "lobby", count = 1, radius = 20 }
```

## tools/baking/showProbes {#tools-baking-showprobes}

```lua
baking.showProbes(opts?: ShowProbesOpts) -> ShowProbesResult
```

Visualize the baked irradiance field — drop an unlit, color-coded marker at every probe position so an invisible SH bake becomes something you can see and judge. Each marker is tinted by the light that probe captured (brighter where the bake gathered more light, dark in occluded corners), so you can read the falloff through the volume and catch a bake that came back empty. Reads the volume's baked probe buffer (bake first). `volume` targets one volume (default: every VolumeProbe in the scene); `gain` scales brightness (default auto-normalizes to the brightest probe); `stride` thins a dense grid; `scale` sizes the markers. Re-run to refresh; `baking.clear` (or deleting the `gi_probe_viz` entity) removes it.

**Parameters**

- `opts` `ShowProbesOpts` _(optional)_

**Returns** `ShowProbesResult`

```lua
-- every baked volume
{ gain = 4, scale = 0.2 }
{ volume = "probe_volume_1", stride = 2 }
```

## tools/baking/status {#tools-baking-status}

```lua
baking.status() -> BakeStatus
```

Report the running bake's progress: whether one is going, how many receivers it has FINISHED (`done`, which `percent` is derived from) and how many it has REACHED (`started`) out of the total it resolved, which one it is on, and how long it has been running. A bake is handed off as a background task, so this is how you check on it while it works; the `lightmap bake finished` notice reports the end.

**Returns** `BakeStatus`

```lua
-- while a bake is running
-- poll until `running` is false
```

## tools/cam/frame {#tools-cam-frame}

```lua
cam.frame(entityId: string | EntityRef, opts?: FrameOpts)
```

Position a camera to frame a target entity from a distance + angle, then point it at the entity in one call. `opts.angle` accepts three forms (matches `capture.fromEntity`): a number (elevation pitch in degrees — legacy scalar), `{yaw, pitch}` (array form), or `{yaw=, pitch=}` (named form). `opts.yaw` is still respected as a fallback when `opts.angle` is a number or doesn't carry a yaw entry.

**Parameters**

- `entityId` `string | EntityRef`
- `opts` `FrameOpts` _(optional)_

```lua
'my_cube'
'my_cube', { distance = 20, angle = 45, yaw = 90 }
'my_cube', { angle = {90, 60} }
```

## tools/cam/get {#tools-cam-get}

```lua
cam.get(camId?: (string | EntityRef)) -> CamGetResult
```

Read camera info — transform + fov. Defaults to the active camera (`camera.active()`) when `camId` is omitted. Returns `{ error = ... }` when no camera is active, and when the reference names no entity.

**Parameters**

- `camId` `(string | EntityRef)` _(optional)_

**Returns** `CamGetResult`

```lua
'cam2'
```

## tools/cam/lookAt {#tools-cam-lookat}

```lua
cam.lookAt(camIdOrX: string | EntityRef | number, xOrY: number, yOrZ: number, zOrNil?: number)
```

Point a camera at a world position. Computes a look-at rotation (yaw + pitch) and writes it to the camera's `localRotation`. Two call shapes: `cam.lookAt(x, y, z)` (uses active camera) and `cam.lookAt(camId, x, y, z)` (specific camera). Logs and returns when no camera / target is available, when the camera has no position, or when the camera is already at the target (length < 0.001).

**Parameters**

- `camIdOrX` `string | EntityRef | number`
- `xOrY` `number`
- `yOrZ` `number`
- `zOrNil` `number` _(optional)_

```lua
0, 0, 0
'cam2', 5, 3, 0
```

## tools/cam/spawn {#tools-cam-spawn}

```lua
cam.spawn(name: string, ...) -> string
```

Spawn a camera entity with a `Camera` component at a position. Supports four call shapes for the position: no args (origin), `({x,y,z}, opts?)`, `(opts)` (opts only), or `(x, y, z, opts?)`. When `opts.renderTarget` is set, creates a GPU texture and points the camera at it via `Camera:setTargetTexture`; sample it by `entity(id).component.get("Camera").textureHandle` and free it with `renderer.destroy` when done.

**Parameters**

- `name` `string`

**Returns** `string`

```lua
"main", 5, 5, 5
"rt_cam", { renderTarget = { name = "feed", width = 256, height = 256 } }
```

## tools/camera/bindToPlayer {#tools-camera-bindtoplayer}

```lua
camera.bindToPlayer(camera?: (EntityRef | string)) -> BindToPlayerResult
```

Make the joining player use this camera in play — bind it to the active scene's PlayerPrototype. The prototype's `camera` role must be a descendant of its subtree (it is cloned with the player on spawn), so this re-parents the camera into the prototype (keeping its world pose) and sets the prototype's `camera` ref to it. This is how you choose the player's camera: pair it with a static behavior (`camera.set { behavior = "@builtin::controller.menu" }`) for a fixed PS1-style camera the player moves within, or a follow behavior (`orbital_follow`, `third_person_follow`, `first_person`, …) for a tracking one. On spawn the player's body is bound as the camera's follow target; a static behavior ignores that and holds its pose. Errors when the scene has no PlayerPrototype (a player-less scene has no player camera to bind). Returns `{ prototype, camera }`.

**Parameters**

- `camera` `(EntityRef | string)` _(optional)_

**Returns** `BindToPlayerResult`

```lua
"fixedCam"
```

## tools/camera/create {#tools-camera-create}

```lua
camera.create(name: string, position?: (vec3 | number), opts?: CameraCreateOpts) -> shared.CameraInfo
```

Spawn a new camera entity at a position, with any starting parameters. `opts` accepts `fov`, `near`, `far`, `priority`, `renderLayers` (a layer-name spec like `"all !ui"`), `debugChannel`, `follow` (an entity the camera behavior tracks), `behavior` (a `cameraBehavior`-tagged component), `lookAt` (a world position or entity to aim at), and `target` (render-to-texture: `true` or `{ width, height, name }` renders offscreen; omit for the viewport). The scene's primary camera is spawned for you from the PlayerPrototype — use this for ADDITIONAL cameras (a security view, a cutscene angle, a render-texture feed). Returns the new camera's full parameter record.

**Parameters**

- `name` `string`
- `position` `(vec3 | number)` _(optional)_
- `opts` `CameraCreateOpts` _(optional)_

**Returns** `shared.CameraInfo`

```lua
"securityCam", { 0, 6, 12 }, { fov = 50, renderLayers = "all !ui" }
"feedCam", { x = 2, y = 3, z = 2 }, { target = { width = 512, height = 512 }, lookAt = "player" }
```

## tools/camera/frame {#tools-camera-frame}

```lua
camera.frame(camera?: (EntityRef | string), target: EntityRef | string, opts?: CameraFrameOpts) -> shared.CameraInfo
```

Position and aim a camera to frame a target entity in one step: place the camera at `distance` from the target on the `yaw`/`pitch` angle, then look at it. `opts` accepts `distance` (default 10), `yaw` (default 0), and `pitch` (default 20), all degrees. Pass `nil` for the first argument to frame with the active camera. Returns the camera's updated parameter record.

**Parameters**

- `camera` `(EntityRef | string)` _(optional)_
- `target` `EntityRef | string`
- `opts` `CameraFrameOpts` _(optional)_

**Returns** `shared.CameraInfo`

```lua
"securityCam", "player", { distance = 8, yaw = 30, pitch = 15 }
nil, "statue"
```

## tools/camera/get {#tools-camera-get}

```lua
camera.get(camera?: (EntityRef | string)) -> shared.CameraInfo
```

Read the full parameter record for one camera — position, rotation, fov, near, far, priority, render-layer spec, debug channel, render-target guid, behavior, and follow target — beside whether it is `enabled` (the switch that decides whether it renders at all), whether it is `rendering`, the `reason` it is not, the projection the renderer built for it in `frame`, and `mismatch` naming every field where that frame disagrees with the record above. Pass a camera name/id/proxy, or nothing to read the active (on-screen) camera, falling back to the scene's main camera.

**Parameters**

- `camera` `(EntityRef | string)` _(optional)_

**Returns** `shared.CameraInfo`

```lua
"securityCam"
```

## tools/camera/list {#tools-camera-list}

```lua
camera.list() -> { shared.CameraRow }
```

List every camera in the scene, enumerated from the live ECS so a just-spawned, hidden, or runtime camera is never missed, sorted by descending priority (the order the renderer resolves the on-screen camera). Each row marks whether it is the `active` camera (drawn this frame), whether it is `enabled` — the per-camera switch that decides whether it competes for the viewport or renders into a target at all — whether it is `rendering` and the `reason` it is not, whether it is the `player` camera (the one the PlayerPrototype uses — the joining player's view), and its `role` — `"main"` (highest-priority active non-editor), `"editor"` (the editor fly-camera), or `""` — plus its priority, fov, render-layer spec, and render-target guid. For the projection the renderer actually built for each camera, and what each cost, read `camera.observe()`.

**Returns** `{ shared.CameraRow }`

## tools/camera/lookAt {#tools-camera-lookat}

```lua
camera.lookAt(camera?: LookAtRef, target?: LookAtRef) -> shared.CameraInfo
```

Aim a camera at a world position or at another entity. The target is an entity name/id or a position `{ x, y, z }` / `{ x =, y =, z = }`. Pass a single target to aim the active camera, or a camera plus a target to aim a specific one. Returns the camera's updated parameter record.

**Parameters**

- `camera` `LookAtRef` _(optional)_
- `target` `LookAtRef` _(optional)_

**Returns** `shared.CameraInfo`

```lua
"securityCam", "player"
{ 10, 0, 5 }
```

## tools/camera/set {#tools-camera-set}

```lua
camera.set(camera?: (EntityRef | string | CameraSetParams), params?: CameraSetParams) -> shared.CameraInfo
```

Change one or more parameters on a camera in a single call. Accepts `fov`, `near`, `far`, `priority`, `debugPass` (the diagnostic view this camera renders, BY NAME — `"final"` (lit), `"normal"`, `"depth"`, a content view like `"lightmap"`, …; `renderer.debugPass.list()` enumerates them; the live viewport draws the active camera's pass), `renderLayers` (a layer-name spec like `"all !ui"` — a bare name includes a layer, `!name` excludes it), `behavior` (a `cameraBehavior`-tagged component ref), and `follow` (the entity the behavior tracks). `debugChannel` accepts the raw numeric channel for the same effect. Pass no camera (or `nil`) to change the active camera; pass `{ ... }` as the only argument for the same. Returns the camera's updated parameter record.

**Parameters**

- `camera` `(EntityRef | string | CameraSetParams)` _(optional)_
- `params` `CameraSetParams` _(optional)_

**Returns** `shared.CameraInfo`

```lua
"securityCam", { fov = 50, priority = 5 }
{ debugPass = "normal" }
{ debugPass = "final" }   -- back to the lit image
```

## tools/capture/cleanup {#tools-capture-cleanup}

```lua
capture.cleanup() -> number
```

Clear every file in `/source/tmp/capture/`. Walks the directory with `vfs.list` and removes each non-directory entry via `vfs.remove`. Safe to call on a missing / empty directory.

**Returns** `number`

## tools/capture/collage {#tools-capture-collage}

```lua
capture.collage(opts: CaptureCollageOpts) -> (shared.CaptureResult?, string?)
```

Render a GRID of frames into one image and write it before returning. What varies from cell to cell is what you pass, and a collage says so rather than assuming: `setups` gives a cell per whole camera set-up — the contact sheet of a shot list, each entry written the way a single capture is aimed — `viewpoints` gives a cell per named view of one subject (front, back, left, right, top, bottom, iso — or "sides" for all six), `passes` gives a cell per render pass (final, albedo, normal, depth, motion_vectors, and any content-registered capture view), and `duration` gives a cell per sample across that many seconds of gameplay. Naming a station axis (`setups` or `viewpoints`) AND `passes` lays out a 2D grid: a row per station, a column per pass, so reading across a row is one set-up under every pass and reading down a column is one pass from every set-up. A collage with no axis is refused naming all of them; `setups` and `viewpoints` both say where the camera stands and cannot combine; and `duration` combines with none of them — a cell differing both in what it looks at and in when it was taken answers neither question. The grid shape is worked out from the number of cells; pass `grid` ("3x2") to state it. `width` and `height` are the whole SHEET, and a cell is rendered at the box the grid divides out of it — so a cell holds the same picture a single capture at that cell's own `width`/`height` holds, whatever the grid's shape, and a round subject reads round in every cell of a `4x1`. Every cell comes back labelled on the result with the station it was rendered from and the pixels it was rendered at, so a grid is readable from the response instead of guessable from the pictures.

**Parameters**

- `opts` `CaptureCollageOpts`

**Returns** `(shared.CaptureResult?, string?)`

```lua
{ setups = { { position = { -14.6, 1.35, 4.5 }, lookAt = { -16.3, 1.1, 1.6 }, fov = 28, label = "shot 1" }, { position = { 8, 3, 12 }, lookAt = { 0, 1, 0 }, fov = 50, label = "shot 2" } }, grid = "2x1" }
{ entity = "crate", viewpoints = "sides", basis = "local", projection = "orthographic", isolate = true }
{ entity = "crate", basis = "local", viewpoints = { "front", "iso" }, passes = { "final", "normal" } }
{ passes = { "final", "albedo", "normal", "depth" } }
{ entity = "salt_pan", viewpoints = "sides", basis = "local", clearAir = true }
{ duration = 3, grid = "3x3", path = "/source/tmp/capture/motion.jpg" }
```

## tools/capture/fromCamera {#tools-capture-fromcamera}

```lua
capture.fromCamera(camera: string | EntityRef, opts?: (string | FromCameraOpts)) -> (string?, string?, shared.CaptureFrame?)
```

Render the scene from a specific NAMED camera, writing the PNG before returning — the returned path exists on success. By DEFAULT it uses the camera's exact authored settings — its world-space pose plus its own `fov`, `near`, `far`, and the scene layers it draws: the camera's own `renderLayers` with the editor's chrome (`EditorUI`) and the authoring overlays (`debug`) taken off, since those are drawn for whoever is editing rather than by that camera into the world. So the frame is precisely what that camera draws of the scene, without changing what is on screen. Pass `opts` to OVERRIDE any of those for this one capture WITHOUT touching the camera: `pass` (what the frame HOLDS, by name — `"final"`, a built-in diagnostic pass like `albedo` / `normal` / `depth` / `roughness`, or a capture view a render feature published; it picks the channel rendered, the layers that pass is read under, and whether the post-process chain runs over it — a diagnostic buffer is read flat), `view` (a capture view named directly, by the name a render feature published it under — `renderer.captureView.list()` enumerates them, and a name no feature published is refused against that list), `renderLayers` (a layer-name spec like `"all !ui"`), `debugChannel` (the same channel selection by number), `fov`, `near`, `far`, `width`, `height`, and `path`. Use it to check any specific camera — a security view, a cutscene angle, a render-texture feed — and to preview it under different layers or a diagnostic pass.

**Parameters**

- `camera` `string | EntityRef`
- `opts` `(string | FromCameraOpts)` _(optional)_

**Returns** `(string?, string?, shared.CaptureFrame?)`

```lua
"securityCam"
"securityCam", { pass = "normal" }
"securityCam", { view = "overdraw" }
"securityCam", { renderLayers = "all !ui", debugChannel = 0 }
"securityCam", "/source/tmp/capture/sec.png"
```

## tools/capture/fromEntity {#tools-capture-fromentity}

```lua
capture.fromEntity(opts: CaptureEntityOpts) -> (string?, string?, shared.CaptureFrame?)
```

Render the scene framed on an entity (or several) and write the PNG before returning — the returned path exists on success. The camera auto-fits the union world-space bounds of the target(s), so the subject fills the frame at any scale; pass `distance` to orbit at a fixed radius instead.

**Parameters**

- `opts` `CaptureEntityOpts`

**Returns** `(string?, string?, shared.CaptureFrame?)`

```lua
{ entity = "player" }
{ entity = "player", pass = "normal" }
{ entity = "crate", viewpoint = "front", basis = "local" }
{ entity = "crate", viewpoint = "top", basis = "local", projection = "orthographic", isolate = true }
{ entity = { "house", "tree" }, angle = {90, 15}, path = "/source/tmp/capture/yard.png" }
{ entity = "player", deterministic = true }
{ entity = "salt_pan", clearAir = true }
```

## tools/capture/fromPosition {#tools-capture-fromposition}

```lua
capture.fromPosition(opts: CapturePositionOpts) -> (string?, string?, shared.CaptureFrame?)
```

Render the scene from a world-space position and write the PNG before returning — the returned path exists on success. Spawns an ephemeral offscreen camera, renders once, reads the target back and writes it; all resources clean up before the call returns.

**Parameters**

- `opts` `CapturePositionOpts`

**Returns** `(string?, string?, shared.CaptureFrame?)`

```lua
{ position = {0, 5, 10}, lookAt = {0, 0, 0} }
{ position = {0, 5, 10}, lookAt = {0, 0, 0}, pass = "albedo" }
{ path = "/source/tmp/capture/front.png", position = {0, 5, 10}, lookAt = {0, 0, 0} }
{ position = {0, 5, 10}, lookAt = {0, 0, 0}, deterministic = true }
{ position = {0, 5, 10}, lookAt = {0, 0, 0}, clearAir = true }
```

## tools/capture/oneshot {#tools-capture-oneshot}

```lua
capture.oneshot(opts?: CaptureOneshotOpts) -> (CaptureHandle?, string?)
```

Spawn an ephemeral offscreen camera configured from `opts`, trigger one render, and return the handles so the caller can read the render target back synchronously and clean up. Unlike `fromPosition` / `fromEntity` / `viewport`, this does NOT spawn a task to handle readback — it returns immediately, making it usable from MCP in edit mode where the task scheduler does not tick.

**Parameters**

- `opts` `CaptureOneshotOpts` _(optional)_

**Returns** `(CaptureHandle?, string?)`

```lua
{ source = "entity", entity = "Player", distance = 8, angle = {45, 20}, pass = "normal" }
```

## tools/capture/viewport {#tools-capture-viewport}

```lua
capture.viewport(opts?: (string | ViewportOpts)) -> (string?, string?, shared.CaptureFrame?)
```

Capture the current viewport and write the PNG before returning — the returned path exists on success. Mirrors the on-screen camera through the shared capture core (`source = "screen"`), so the frame is taken from that camera's pose, through that camera's own lens, under the render layers it draws the screen with, the editor's own chrome left out: the chrome stands around the viewport's picture, and the frame holds that picture — the scene, its debug overlays and the content UI drawn into it. Pass `opts` to OVERRIDE any of that for this one capture: `width` / `height` for a raster other than the viewport's own size, `renderLayers` (a layer-name spec like `"all !EditorUI !debug"`) for the frame without the editor's chrome over it, `pass` for what the frame HOLDS (`"final"`, a built-in diagnostic pass like `albedo` / `normal` / `depth`, or a capture view a render feature published), `clearAir` for the frame with the air between the camera and a surface taken out, and `postProcessing` to take the frame with the grade off.

**Parameters**

- `opts` `(string | ViewportOpts)` _(optional)_

**Returns** `(string?, string?, shared.CaptureFrame?)`

```lua
"/source/tmp/capture/screenshot.png"
{ path = "/source/tmp/capture/clean.png", renderLayers = "all !EditorUI !debug" }
{ path = "/source/tmp/capture/half.png", width = 960, height = 540 }
{ path = "/source/tmp/capture/still.png", deterministic = true }
{ path = "/source/tmp/capture/normals.png", pass = "normal" }
{ path = "/source/tmp/capture/own_colour.png", clearAir = true }
```

## tools/characterController/install {#tools-charactercontroller-install}

```lua
characterController.install(entityId: string, opts?: InstallOpts) -> InstallResult
```

Install `CharacterController` + `Locomotion` + `MovementState` on an entity, with an optional third-person camera child. Applies an optional preset (e.g. `'synty'`) for default property values, then layers user overrides on top. Idempotent per component — running `install` twice on the same entity is a no-op for already-present components. The camera child carries a `Camera` component plus the `orbital_follow` controller targeting the owning entity; skip it with `camera = false`.

**Parameters**

- `entityId` `string`
- `opts` `InstallOpts` _(optional)_

**Returns** `InstallResult`

```lua
"Player", { preset = "synty" }
player.id(), { preset = "humanoid", overrides = { moveSpeed = 8.0 } }
entityId, { camera = false }  -- no camera child
```

## tools/characterController/uninstall {#tools-charactercontroller-uninstall}

```lua
characterController.uninstall(entityId: string) -> UninstallResult
```

Tear down everything `characterController.install()` added on an entity: `AnimDebug` (if present), `Locomotion`, `MovementState`, `CharacterController`, and any child entity carrying an `orbital_follow` controller targeting this entity. Idempotent — missing components are skipped silently. Returns the count of despawned camera children so callers can verify cleanup.

**Parameters**

- `entityId` `string`

**Returns** `UninstallResult`

```lua
"Player"
player.id()
```

## tools/cutscene/pause {#tools-cutscene-pause}

```lua
cutscene.pause() -> shared.ZmToolResult
```

Freeze the cutscene playing now. The clock stops and so does the scene under it — components stop updating, physics stops stepping, animation stops advancing — which is what keeps one shot on screen for as many captures and edits as the work needs. `cutscene.resume` starts both again.

**Returns** `shared.ZmToolResult`

## tools/cutscene/play {#tools-cutscene-play}

```lua
cutscene.play(source: string, paused?: boolean) -> shared.ZmToolResult
```

Play a cutscene now, from the module its definition is returned from — the same call a scene entrypoint or a `CutsceneTrigger` makes, so what you see here is what a player sees. Starting one while another is playing ends that one first.

**Parameters**

- `source` `string`
- `paused` `boolean` _(optional)_

**Returns** `shared.ZmToolResult`

```lua
"mygame.scenes.intro"
"mygame.scenes.intro", true
```

## tools/cutscene/rate {#tools-cutscene-rate}

```lua
cutscene.rate(r: number) -> shared.ZmToolResult
```

Set the playback rate of the cutscene playing now. 1 is real time, 0.25 slow motion, 2 double speed; a negative rate runs it backwards. The cutscene itself is untouched — only how fast its clock moves.

**Parameters**

- `r` `number`

**Returns** `shared.ZmToolResult`

```lua
0.25
-1
```

## tools/cutscene/resume {#tools-cutscene-resume}

```lua
cutscene.resume() -> shared.ZmToolResult
```

Start the cutscene playing now again after `cutscene.pause`.

**Returns** `shared.ZmToolResult`

## tools/cutscene/seek {#tools-cutscene-seek}

```lua
cutscene.seek(t: number) -> shared.ZmToolResult
```

Move the clock of the cutscene playing now to an exact moment. The frame it lands on is the frame that time would have produced — the camera, the fade and the subtitle are all re-derived — so a shot can be checked without watching the cutscene up to it.

**Parameters**

- `t` `number`

**Returns** `shared.ZmToolResult`

```lua
6.5
```

## tools/cutscene/skip {#tools-cutscene-skip}

```lua
cutscene.skip() -> shared.ZmToolResult
```

Skip the cutscene playing now to its closing fade — what a player pressing the skip key does. Cues between here and the end still fire, so a cutscene that opens a door on its way out opens it.

**Returns** `shared.ZmToolResult`

## tools/cutscene/status {#tools-cutscene-status}

```lua
cutscene.status() -> shared.ZmToolResult
```

Report the cutscene playing now — its name, where its clock is, which shot is on screen, and whether it is paused or running at an unusual rate. Reads the cutscene whatever started it, so this answers about the one a scene is playing, not a private copy.

**Returns** `shared.ZmToolResult`

## tools/cutscene/stop {#tools-cutscene-stop}

```lua
cutscene.stop() -> shared.ZmToolResult
```

End the cutscene playing now. The camera, the overlay and the player's controls all go back the way they were, the same as when a cutscene reaches its end on its own.

**Returns** `shared.ZmToolResult`

## tools/debug/inspect {#tools-debug-inspect}

```lua
debug.inspect(target: string | { any }, opts?: InspectOpts) -> InspectResult
```

Inspect entity component state for every entity matching the target. A name resolves to every matching entity; an id to itself; arrays and entity proxies work too. Each match reports its script components (serialized public data) and native ECS components, selected by `opts.include`.

**Parameters**

- `target` `string | { any }`
- `opts` `InspectOpts` _(optional)_

**Returns** `InspectResult`

```lua
'player'
'lamp', { include = "ecs" }
'enemy_*', { types = { "Health" }, limit = 5 }
```

## tools/debug/playSession {#tools-debug-playsession}

```lua
debug.playSession(enabled?: boolean) -> { playSession: boolean }
```

Show or hide the debug overlay in play mode. Edit mode always shows the overlay; play mode shows it only while this session is on — the way to watch bones, colliders, or bounds on a running, animating scene. The flag lives in memory only and is never saved, so debug visibility can never be permanently toggled on by accident; it resets on engine restart.

**Parameters**

- `enabled` `boolean` _(optional)_

**Returns** `{ playSession: boolean }`

```lua
true
false
```

## tools/debug/problems {#tools-debug-problems}

```lua
debug.problems(opts?: ProblemsOpts) -> ProblemsResult
```

Return the errors and warnings the engine has logged since your last call — the non-crashing failures (contract violations, component `awake`/`update` throws, auto-disabled components, unresolved requires, bridged renderer/asset errors) that otherwise never surface in `execute` results. Each call advances a read-cursor, so repeated calls report only what is new, oldest first (the root-cause error surfaces ahead of the cascade it triggered). **When something you built does not behave, call this FIRST** — the engine has usually already logged why.

**Parameters**

- `opts` `ProblemsOpts` _(optional)_

**Returns** `ProblemsResult`

```lua
{ level = "error", limit = 20 }
{ all = true }
```

## tools/debug/scope {#tools-debug-scope}

```lua
debug.scope(target?: (string | { any })) -> { scoped: { string }, count: number }
```

Limit every enabled debug category to a set of entities, so the overlay is focused on specific objects. Call with no argument (or an empty list) to clear the focus and draw for every entity again.

**Parameters**

- `target` `(string | { any })` _(optional)_

**Returns** `{ scoped: { string }, count: number }`

```lua
'player'
{ 'player', 'enemy' }
```

## tools/debug/set {#tools-debug-set}

```lua
debug.set(category: string, enabled?: boolean) -> { [string]: boolean }
```

Show or hide a debug-visualization overlay category. The overlay is ambient editor tooling drawn for the whole scene; this flips which categories draw. A category is settable before the package that draws it has registered — the state is kept and applies when it arrives — so a script that configures overlays does not depend on how far along the session is. `debug.state` lists what is set.

**Parameters**

- `category` `string`
- `enabled` `boolean` _(optional)_

**Returns** `{ [string]: boolean }`

```lua
'colliders'
'bones', true
'all', false
```

## tools/debug/state {#tools-debug-state}

```lua
debug.state() -> DebugState
```

Read the current debug-visualization state: which categories draw, the entity scope, whether the play-mode session is on, and the engine mode. `categories` covers every category the session knows — those a package has registered to draw, plus any set before its provider arrived — so what this lists is what `debug.set` accepts.

**Returns** `DebugState`

## tools/detail/resident {#tools-detail-resident}

```lua
detail.resident(axis?: string) -> { [string]: any }
```

Report what a world has resident of its detail right now. Covers terrain's LOD cut, a voxel world's chunk meshes, spatial streaming's cell store and mesh LOD's level selection, each read from the system's own state rather than from the component fields that asked for it. Every voxel chunk carries what became of its mesh build, so a world that stands with the right block count and draws nothing reads as what it is. The same reading is served at `/zero/runtime/observations/streaming`.

**Parameters**

- `axis` `string` _(optional)_

**Returns** `{ [string]: any }`

```lua
"voxel"
```

## tools/detail/whyNotDrawn {#tools-detail-whynotdrawn}

```lua
detail.whyNotDrawn(subject: any) -> { [string]: any }
```

Answer why a piece of a world's detail is not on screen. The reason is the nearest cause from a closed set — `drawn`, `notManaged`, `notBuilt`, `buildQueued`, `buildFailed`, `builtEmpty`, `emittedNothing`, `outsideRadius`, `evicted`, `coarserLevel`, `finerLevel`, `noSuchLevel`, `hidden` — so it names the thing to change rather than a consequence of it, and `detail` carries the engine's own message when a mesh build was dropped. Reads the four detail systems' state rather than the components' fields.

**Parameters**

- `subject` `any` _(optional)_

**Returns** `{ [string]: any }`

```lua
"Vox"
```

## tools/diagnostics/errors {#tools-diagnostics-errors}

```lua
diagnostics.errors(entityFilter?: (string | EntityFilterTable)) -> { EntityErrors }
```

Get all component/entity errors in the scene. With no argument, returns errors for all entities (enumerated via `vfs.list("/runtime/layers/main/entities")`). With an entity name or ID, returns errors for that entity only. Each entity's errors are read from `/runtime/layers/main/entities/<name>/errors` — values of `"(none)"` or `""` are treated as no errors. Read-only — does not modify anything.

**Parameters**

- `entityFilter` `(string | EntityFilterTable)` _(optional)_

**Returns** `{ EntityErrors }`

```lua
"lamp"
```

## tools/diagnostics/summary {#tools-diagnostics-summary}

```lua
diagnostics.summary() -> SceneSummary
```

Get a summary of the current scene. Enumerates entities via `vfs.list("/runtime/layers/main/entities")`, counts each entity's components by listing `/runtime/layers/main/entities/<name>/components`, counts entities with non-empty error files, and reports `Camera` component instances separately. Also surfaces rendering culling mode + stats via `__rendering.getCullingMode` / `__rendering.getCullStats` when available — GPU culling has no readback path so its stats slot becomes a notice string instead. Read-only — does not modify anything.

**Returns** `SceneSummary`

## tools/diagnostics/validate {#tools-diagnostics-validate}

```lua
diagnostics.validate() -> ValidationReport
```

Validate scene integrity: check for broken materials, missing model assets, and entity errors. Enumerates entities via `vfs.list("/runtime/layers/main/entities")`, reads each entity's `errors` file, and parses messages of the form `material '...' does not exist` / `Material:...not found ...'...'` / `mesh '...' not found` / `Model:...not found ...'...'` to surface broken references. Read-only — does not modify anything.

**Returns** `ValidationReport`

## tools/editor/observe {#tools-editor-observe}

```lua
editor.observe() -> { [string]: any }
```

What each editor action committed, and why it committed less than it was asked for. `lastDrag` is the most recent gizmo drag: the handle it held, the pivot it reconstructed against, what the pointer asked for and what the drag applied after snapping, and — per entity — the transform before, the transform handed to the engine, and the transform the engine HOLDS, read back from the engine. `lastGrab` is a press that landed on a handle and began no drag, with the reason. `lastDelete` / `lastDuplicate` carry the ids removed and created and the ones that refused; `lastCommand` tells a missing command from a disabled one from a broken predicate from a body that raised; `lastSelect` carries the ids before and after with the difference taken both ways. Every record names an `outcome` and, when that is not a clean commit, one `reason` from the closed set `reasons` enumerates. Answers in edit mode as well as play mode.

**Returns** `{ [string]: any }`

## tools/entityOps/addComponents {#tools-entityops-addcomponents}

```lua
entityOps.addComponents(targets: Targets, components: Components, opts?: AddComponentsOpts) -> { AddResult }
```

Attach component(s) to one or many entities in a single call. Targets are entity NAMES or ids (arrays and scene.find records work too), or a `scene.find` QUERY resolved for you — `{ name = { "wheel" } }`, `{ component = { "Light" } }`, `{ under = { "car" } }` — so you operate on a selection without looking it up first. Components are given as a name, an array of names, or a `{ Name = dataTable }` map carrying each component's setup data — names accept the leaf (`"Physics"`), the full identity (`"@builtin::components.Physics"`), a VFS path (resolved to its identity), or a case-insensitive partial resolved to the one component it can mean, all before anything mutates; a name matching nothing, or several components, is an error naming what it could not settle rather than a half-applied call. `create = true` spawns a fresh root entity for any string target that matches nothing, so one call can both create the entity and give it its setup. Returns one `{ id, name, added, created? }` record per entity.

**Parameters**

- `targets` `Targets`
- `components` `Components`
- `opts` `AddComponentsOpts` _(optional)_

**Returns** `{ AddResult }`

```lua
"crate", { Physics = { kind = "dynamic" } }
{ "crate_1", "crate_2" }, { "Physics", "Audio" }
"spinner", { Rotator = { speed = 2 } }, { create = true }
```

## tools/entityOps/component {#tools-entityops-component}

```lua
entityOps.component(componentType: string, opts?: ComponentInspectOpts) -> ComponentInfo
```

Inspect a component TYPE so you set fields that exist instead of guessing. Resolves the component by name / identity / VFS path and reports its public FIELDS (each with name, type, and current value), its callable METHODS, its lifecycle HOOKS (awake / update / onDestroy / …), asset-ref fields with their AssetRef type, and a one-line description. Pass `on` (an entity name or id that carries the component) and every field's `value` is that entity's LIVE value, read off its component proxy — a vector / struct / list as its plain table, a reference by the identity of what it points at (an asset as a `{ __ref, identity, name, type }` envelope, an entityRef as the entity id). Omit `on` and each `value` is the DECLARED DEFAULT as written in the component's source. Use this before `entityOps.addComponents` / `entityOps.spawn` to learn the exact field names and value shapes.

**Parameters**

- `componentType` `string`
- `opts` `ComponentInspectOpts` _(optional)_

**Returns** `ComponentInfo`

```lua
"Camera"
"Model", { on = "my_prop" }
"@builtin::components.Light"
```

## tools/entityOps/duplicate {#tools-entityops-duplicate}

```lua
entityOps.duplicate(targets: Targets, opts?: DuplicateOpts) -> { shared.TransformState }
```

Copy entities that already exist — a duplicate of a prop, or a numbered run of them spread along an axis. Targets resolve by NAME or id, an array of them, `scene.find` records, or a QUERY resolved for you (`{ name = { "crate" } }`, `{ component = { "Light" } }`, `{ under = { "room" } }`), so a whole selection is copied without looking its ids up first. `count` makes that many copies of EACH source (default 1). `offset` is a `{x,y,z}` world-space step that ACCUMULATES: copy 1 sits one offset from the source, copy 2 sits two, and so on — every copy is placed relative to the SOURCE, not to the copy before it. `name` names each copy and a `%d` in it becomes the copy's 1-based index (`"crate_%d"` → `crate_1`, `crate_2`); without it copies are named after their source. Copies carry the source's render layer. An unrecognised option key is an error naming the accepted set, and a count below 1 is an error, so a mistyped call never reads back as a successful no-op. Returns the full `{ id, name, position, localPosition, rotation, localRotation, eulerAngles, scale, parent }` state of every copy, in creation order.

**Parameters**

- `targets` `Targets`
- `opts` `DuplicateOpts` _(optional)_

**Returns** `{ shared.TransformState }`

```lua
"crate"
"crate", { count = 5, offset = { 2, 0, 0 } }
"pillar", { count = 4, offset = { 0, 3, 0 }, name = "pillar_%d" }
{ name = { "tree" } }, { offset = { 10, 0, 0 } }
```

## tools/entityOps/fromAsset {#tools-entityops-fromasset}

```lua
entityOps.fromAsset(source: string, opts?: FromAssetOpts) -> FromAssetResult
```

Instantiate an existing asset into the scene. Resolution-first: pass a name / path / identity STRING and the tool resolves it (limit the search with `type` to avoid name clashes), dispatches by the resolved category, and REPORTS the canonical asset it hit so you learn what it resolved to. A `bundle` or `avatar` instantiates via its own `:instantiate()` — `link` (default true) keeps the live Asset-component link so the source drives the hierarchy, `link = false` bakes the hierarchy into the scene. A `mesh` becomes a renderable (Model + mesh Collider). A `material` becomes a sphere carrying it. A `texture` becomes a plane sampling it. Anything else goes through the uniform scene-instantiation contract — a `gaussianSplat` capture becomes an entity drawing that cloud, and every other type whose assetType defines the hook works the same way, else a legible error. `parent` (an entity ref, id, or name) spawns the instance under that entity — it lands on the parent, and `position` then places it in the parent's space; an unresolvable parent fails the call before anything spawns. `position` / `rotation` / `scale` place it; `synced` replicates gameplay edits to peers.

**Parameters**

- `source` `string`
- `opts` `FromAssetOpts` _(optional)_

**Returns** `FromAssetResult`

```lua
"@builtin::avatars.humanoid"
"oak_chest", { type = "bundle", position = {2, 0, 0} }
"truck_model", { type = "bundle", parent = "truck_rig" }
"gold", { type = "material" }
"/zero/source/generated/meshes/a_statue.bundle", { link = false }
```

## tools/entityOps/modify {#tools-entityops-modify}

```lua
entityOps.modify(targets: Targets, ops: ModifyOps) -> { ModifyResult }
```

Change entity state — one tool for delete / enable / disable / internal / temporary / reparent / rename, over one or many entities. Targets are entity NAMES or ids (arrays and scene.find records work too), or a `scene.find` QUERY resolved for you — `{ name = { "wheel" } }`, `{ component = { "Light" } }`, `{ under = { "car" } }` — so you operate on a selection without looking it up first. Boolean ops take true, false, or "toggle". `delete` removes the entity and its whole subtree and wins over every other op. `enabled` drives the entity's active state, `internal` marks it engine plumbing — taken out of the default entity listings and the inspector default view, while it keeps rendering, `temporary` marks it as not-persisted. `parent` (a name or id) reparents; `unparent = true` makes it a scene root; `keepWorldTransform = false` keeps the local transform instead of the world pose when reparenting. `rename` sets a new name (single target only). `components` sets field values on components the entity ALREADY carries — `{ Camera = { fov = 60 }, Light = { intensity = 2 } }`, the same shape `spawn` takes, so creating with a value and changing it later are written alike; component names resolve the same forgiving way `addComponents` resolves them, and an entity that does not carry one named is an error rather than a silent skip. An unrecognised key is an error naming the accepted set, so a typo fails the call rather than reporting an empty `applied`. Returns one `{ id, name, applied }` record per entity.

**Parameters**

- `targets` `Targets`
- `ops` `ModifyOps`

**Returns** `{ ModifyResult }`

```lua
"debug_probe", { internal = true }
{ "probe_a", "probe_b" }, { delete = true }
"old_character", { enabled = "toggle" }
"hat", { parent = "player_head" }
"lamp", { components = { Light = { intensity = 4 } } }
{ component = { "Light" } }, { components = { Light = { intensity = 0 } } }
```

## tools/entityOps/removeComponents {#tools-entityops-removecomponents}

```lua
entityOps.removeComponents(targets: Targets, components: Components) -> { RemoveResult }
```

Detach component(s) from one or many entities in a single call — the counterpart to `entityOps.addComponents`. Targets resolve by NAME or id, an array of them, `scene.find` records, or a QUERY resolved for you (`{ component = { "Light" } }`, `{ name = { "lamp" } }`, `{ under = { "room" } }`), so you can strip a component from everything carrying it without naming a single entity. Components are given as a name or an array of names, and each name matches loosely: the leaf (`"Light"`), the full identity (`"@builtin::components.Light"`), a VFS path, or a case-insensitive partial (`"light"`) resolved to the one component it can mean — the response reports the CANONICAL name it landed on, so a half-remembered name both works and teaches you the real one. A name matching several components is an error listing the candidates rather than a guess, and a name matching none is an error too, so nothing is silently skipped. Every entity comes back as `{ id, name, removed, absent }`: `removed` lists what it actually carried and lost, `absent` lists what it never had — so a call that changed nothing is visible instead of reading like a success.

**Parameters**

- `targets` `Targets`
- `components` `Components`

**Returns** `{ RemoveResult }`

```lua
"lamp", "Light"
{ "crate_1", "crate_2" }, { "Physics", "Audio" }
{ component = { "Light" } }, "Light"
"lamp", "light"
```

## tools/entityOps/roots {#tools-entityops-roots}

```lua
entityOps.roots(targets: Targets) -> { RootHit }
```

Find the root ancestor of one or many entities — walks each up its parent chain to the top-level entity. Targets are entity names or ids (a single one, an array, or `scene.find` records), or a `scene.find` QUERY resolved for you — `{ name = { "wheel" } }`, `{ component = { "Light" } }`, `{ under = { "car" } }`. Returns one record per target with its resolved root id + name and the hop distance; a target that is already a root reports itself with depth 0.

**Parameters**

- `targets` `Targets`

**Returns** `{ RootHit }`

```lua
"player_hand"
{ "wheel_fl", "wheel_fr" }
```

## tools/entityOps/spawn {#tools-entityops-spawn}

```lua
entityOps.spawn(components: SpawnComponents | SpawnBatch, opts?: SpawnOpts) -> SpawnResult | { SpawnResult }
```

Spawn entities from a template — you name the components they carry and their field values, plus a transform. Spawn MANY in ONE call by passing a `{ defaults, items }` list: `defaults` holds what every entity shares and each `items` entry overrides only what differs, so a six-walled room is one call rather than six. Per-item keys win over the matching `defaults` key, and `components` merges per COMPONENT — an item naming `Model` replaces only that entry and keeps the shared `Collider`. Every item is validated before any entity is created, so a typo fails the whole call instead of leaving a half-built scene. Or pass a single `{ [ComponentName] = { field = value } }` map plus opts to spawn ONE entity. Each component is an AUTHORED component (leaf name like `"Model"`, a full identity, or a VFS path) added with its data so it serializes — native ECS components are intentionally not exposed, so you never author something that renders at runtime but vanishes on save. `position` places it; `rotation` takes a `{x,y,z,w}` (or `{x=,y=,z=,w=}`) quaternion or `{pitch,yaw,roll}` (or `{pitch=,yaw=,roll=}`) euler degrees; `scale` a number or `{x,y,z}`; `name` defaults to the first component's name. A spawn is recorded into the scene as it is made, so it is there again the next time the scene loads; `temporary` leaves an entity out of that record — nothing is written for it as it spawns and no save carries it — which is the spelling for content a build reproduces for itself. `internal`, `synced` (replicate gameplay edits to peers), and `attributes` carry the rest of the lifecycle. An unrecognised key is an error naming the accepted set — in `opts`, at the top level, in `defaults`, and in an item alike. Pass an empty components map to spawn a bare entity. Returns the spawned id, name, and component leaf names — one record for the single form, an array in item order for the list form.

**Parameters**

- `components` `SpawnComponents | SpawnBatch`
- `opts` `SpawnOpts` _(optional)_

**Returns** `SpawnResult | { SpawnResult }`

```lua
{ Model = { model = "cube" }, BoxCollider = {} }, { position = {0, 1, 0} }
{ Light = { kind = "point", intensity = 5 } }, { name = "lamp", position = {0, 3, 0} }
{ Text3D = { content = "Hello", fontSize = 48 } }, { position = {0, 2, 0}, rotation = {0, 45, 0} }
{ defaults = { components = { Model = { model = "cube" }, BoxCollider = {} }, scale = { 4, 3, 0.2 } }, items = { { name = "wall_n", position = { 0, 1.5, -2 } }, { name = "wall_s", position = { 0, 1.5, 2 } }, { name = "wall_e", position = { 2, 1.5, 0 }, rotation = { 0, 90, 0 } }, { name = "wall_w", position = { -2, 1.5, 0 }, rotation = { 0, 90, 0 } } } }
{ defaults = { components = { Light = { kind = "point", intensity = 4 } } }, items = { { name = "lamp_a", position = { -3, 3, 0 } }, { name = "lamp_b", position = { 3, 3, 0 }, components = { Light = { kind = "point", intensity = 9 } } } } }
```

## tools/entityOps/swap {#tools-entityops-swap}

```lua
entityOps.swap(target: string | EntityRef, replacement: string | EntityRef, opts?: SwapOpts) -> SwapPlan
```

Replace one entity with another entity that already exists in the scene. The replacement is MOVED into the target's slot — same parent, local position, and rotation; the target's scale unless `keepScale = false`; the target's name unless `keepName = false` — and the target (with its whole subtree) is removed. Both arguments are entity names, ids, or EntityRef proxies. `dryRun = true` reports exactly what would move and what would be removed, without mutating.

**Parameters**

- `target` `string | EntityRef`
- `replacement` `string | EntityRef`
- `opts` `SwapOpts` _(optional)_

**Returns** `SwapPlan`

```lua
"DefaultBody", "my_knight"
"placeholder_car", "sports_car", { keepName = false }
"DefaultBody", "my_knight", { dryRun = true }
```

## tools/entityOps/transform {#tools-entityops-transform}

```lua
entityOps.transform(targets: Targets | { TransformTarget }, ops?: TransformOps) -> { shared.TransformState }
```

Move, rotate and scale entities that already exist — set an absolute position / rotation / scale, apply a relative offset / rotation / scale multiplier, or snap to a grid, over one or many entities. Targets resolve by NAME or id, an array of them, `scene.find` records, or a QUERY resolved for you (`{ name = { "wall" } }`, `{ component = { "Light" } }`, `{ under = { "building" } }`) so you can move things without looking their ids up first. Absolute ops: `position` and `rotation` are world-space, `localPosition` and `localRotation` are parent-relative, `scale` is a number or `{x,y,z}`. Relative ops: `offset` adds a position delta, `rotate` applies a rotation on top of the current one, `scaleBy` multiplies the current scale; `space` ("world" default, or "local") governs those relative ops. `snap` quantizes the resulting position to grid steps. Rotations take a `{x,y,z,w}` quaternion or `{pitch,yaw,roll}` euler degrees, the same forms `entityOps.spawn` accepts. Absolute and relative forms of one channel are exclusive and passing both is an error, and an unrecognised option key is an error naming the accepted set, so a mistyped key never reads back as a successful no-op. Pass a LIST of `{ target = ..., <ops> }` records to give each entity its own values in a single call; a bad entry there fails the whole call before any entity moves. Called with NO ops it is a pure read. Always returns the resulting `{ id, name, position, localPosition, rotation, localRotation, eulerAngles, scale, parent }` per entity, so the call is both the write and the read.

**Parameters**

- `targets` `Targets | { TransformTarget }`
- `ops` `TransformOps` _(optional)_

**Returns** `{ shared.TransformState }`

```lua
"lamp", { position = { 0, 3, 0 } }
{ "wall_n", "wall_s" }, { offset = { 0, 2, 0 } }
{ name = { "wall" } }, { offset = { 0, 2, 0 } }
"crate", { rotation = { 0, 45, 0 }, scale = 2 }
"prop", { offset = { 1, 0, 0 }, space = "local", snap = 0.5 }
"lamp"
```

## tools/gui/bounds {#tools-gui-bounds}

```lua
gui.bounds(id?: ElementId) -> (ElementRect | { [string]: ElementRect })?
```

Read a UI element's on-screen layout rect by id - `{ x, y, w, h }` plus content bounds/centres, in logical points. Omit the id for a map of every recorded element's rect. This is the space `gui.clickAt` and anchors use.

**Parameters**

- `id` `ElementId` _(optional)_

**Returns** `(ElementRect | { [string]: ElementRect })?`

```lua
"glass/vertical@8"
```

## tools/gui/captureElement {#tools-gui-captureelement}

```lua
gui.captureElement(screen?: (string | CaptureElementArgs), element?: (string | { string }), opts?: CaptureElementOpts) -> CaptureElementResult
```

Render one UI element — or several composited by their on-screen positions — to a texture and save it as a JPG under `/source/tmp`, so you can READ it to see exactly how those elements render. Each element (by its `elementTree` id) is rendered into a right-sized render texture the same way a camera renders into a render texture; the result is read back, encoded JPG, written, and the texture destroyed (nothing stays GPU-resident). Pass one id for a single element, or a list of ids to capture several LAYERS together — the image covers their union bounds and each element is drawn at its real relative offset, so a panel and an icon sitting on different screens composite into one picture. Ids may span different screens; each element's screen is taken from its id. Find ids with `gui.elementTree`. The screen must have rendered at least once (so the element has a measured size).

**Parameters**

- `screen` `(string | CaptureElementArgs)` _(optional)_
- `element` `(string | { string })` _(optional)_
- `opts` `CaptureElementOpts` _(optional)_

**Returns** `CaptureElementResult`

```lua
"glass", "glass/vertical@10"
```

## tools/gui/click {#tools-gui-click}

```lua
gui.click(callbackId: string, value?: any)
```

Simulate a widget click by callback ID. Forwards to `ui.click(callbackId, value)`, which dispatches the registered `onCallback` handler associated with `callbackId`.

**Parameters**

- `callbackId` `string`
- `value` `any` _(optional)_

```lua
"reset_btn"
"volume_slider", 0.5
```

## tools/gui/clickAt {#tools-gui-clickat}

```lua
gui.clickAt(x: (number | ClickAtArgs), y?: number, opts?: { button: (number | string)? }) -> ClickAtResult
```

Click at a logical screen position - a REAL pointer down+up. Coordinates are logical UI points (the space `gui.size` / `gui.bounds` / anchors use), not capture pixels.

**Parameters**

- `x` `(number | ClickAtArgs)`
- `y` `number` _(optional)_
- `opts` `{ button: (number | string)? }` _(optional)_

**Returns** `ClickAtResult`

```lua
640, 360
```

## tools/gui/clickElement {#tools-gui-clickelement}

```lua
gui.clickElement(id: (string | ClickElementArgs), opts?: { button: (number | string)?, duration: number? }) -> ClickElementResult
```

Click a UI element by its `elementTree` / layout id: a REAL pointer down+up at the element's centre. Works on any visible widget (no author-assigned callback needed) - the complement to `gui.click`, which needs a callback id. Find ids with `gui.elementTree`.

**Parameters**

- `id` `(string | ClickElementArgs)`
- `opts` `{ button: (number | string)?, duration: number? }` _(optional)_

**Returns** `ClickElementResult`

```lua
"glass/vertical@10"
```

## tools/gui/dragElement {#tools-gui-dragelement}

```lua
gui.dragElement(from: (string | DragElementArgs), to?: string, opts?: { button: (number | string)? }) -> DragElementResult
```

Drag between two UI elements by their `elementTree` ids: press at the first element's centre, move to the second, release.

**Parameters**

- `from` `(string | DragElementArgs)`
- `to` `string` _(optional)_
- `opts` `{ button: (number | string)? }` _(optional)_

**Returns** `DragElementResult`

```lua
"slider_nub", "slider_end"
```

## tools/gui/elementTree {#tools-gui-elementtree}

```lua
gui.elementTree(screen: string | { screen: string })
```

List a screen's rendered element hierarchy with each element's on-screen layout rect. Each node is `{ id, type, bounds, children }` where `bounds` is `{ x, y, w, h }` in logical points, present once the element has been measured. `id` is the `id` set on the node when the author gave it one, otherwise `<screen>/<type>@<path>`. Pass an id (or several) to `gui.captureElement` to screenshot that panel or widget.

**Parameters**

- `screen` `string | { screen: string }`

```lua
"glass"
```

## tools/gui/focus {#tools-gui-focus}

```lua
gui.focus(id?: ElementId) -> FocusResult
```

Focus a widget by id so it takes keyboard input; call with no id (or `""`) to blur the current focus. Forwards to `ui.focus` / `ui.blur`.

**Parameters**

- `id` `ElementId` _(optional)_

**Returns** `FocusResult`

```lua
"search_input"
```

## tools/gui/fonts {#tools-gui-fonts}

```lua
gui.fonts(system?: (boolean | { system: boolean })) -> { any }
```

List every font family a `style.fontFamily` can select, with the aliases that also select it, the concrete face in each weight/style slot, and whether it came from the host OS. Read from the registry the UI text renderer resolves a family through, so a family listed here is one a label renders in. A `fontFamily` naming something absent from this list paints in the default proportional face and is reported as `unknown-font-family` through `gui validate`. Forwards to `ui.listFonts()`.

**Parameters**

- `system` `(boolean | { system: boolean })` _(optional)_

**Returns** `{ any }`

```lua
true
```

## tools/gui/getTree {#tools-gui-gettree}

```lua
gui.getTree(name: string)
```

Get the widget tree of a screen (for reading state). Forwards to `ui.getScreenTree(name)`. The returned tree mirrors the registered widget list and is suitable for inspection / diff-based test assertions.

**Parameters**

- `name` `string`

```lua
"hud"
```

## tools/gui/hide {#tools-gui-hide}

```lua
gui.hide(name: string) -> shared.ScreenVisibility
```

Hide a screen and report the visibility it now carries. Forwards to `ui.hideScreen(name)` — the registered screen remains in the registry and can be re-shown via `gui.show`. Raises when no screen answers to `name`, naming the ones that do.

**Parameters**

- `name` `string`

**Returns** `shared.ScreenVisibility`

```lua
"hud"
```

## tools/gui/hoverElement {#tools-gui-hoverelement}

```lua
gui.hoverElement(id: ElementId) -> HoverElementResult
```

Move the pointer to a UI element's centre by its `elementTree` id, so the element's hover state paints. The cursor stays put - follow with `gui.captureElement` to see the hovered look.

**Parameters**

- `id` `ElementId`

**Returns** `HoverElementResult`

```lua
"glass/vertical@9"
```

## tools/gui/panel {#tools-gui-panel}

```lua
gui.panel(name: string, widgets: { any }, opts?: { layer: number?, visible: boolean? }) -> shared.ScreenVisibility
```

Create a UI screen from a widget list and report the visibility it now carries. Registers the screen via `ui.registerScreen` at `opts.layer`, then leaves it shown — or hidden when `opts.visible` is false, so a screen can be built ahead of the moment it appears. Each widget is `{ type, text?, id?, value?, color?, children?, onCallback?, ... }`.

**Parameters**

- `name` `string`
- `widgets` `{ any }`
- `opts` `{ layer: number?, visible: boolean? }` _(optional)_

**Returns** `shared.ScreenVisibility`

```lua
"hud", { { type = "label", text = "Score: 0", id = "score" }, { type = "button", text = "Reset", onCallback = "reset_btn" } }
```

## tools/gui/screens {#tools-gui-screens}

```lua
gui.screens()
```

List every registered screen with `{ name, visible, layer, hasRoot }` - the top-level map of what UI exists right now. Forwards to `ui.listScreens`.

## tools/gui/scroll {#tools-gui-scroll}

```lua
gui.scroll(dx: number, dy: number)
```

Simulate scroll input. Forwards to `ui.scroll(dx, dy)`.

**Parameters**

- `dx` `number`
- `dy` `number`

```lua
0, -10
```

## tools/gui/scrollElement {#tools-gui-scrollelement}

```lua
gui.scrollElement(id: (string | ScrollElementArgs), offsetY?: number) -> ScrollElementResult
```

Scroll a `scrollArea` to a vertical offset (pixels from the top) by its `elementTree` id — the reliable, element-targeted scroll. Prefer this over `scroll` (a mouse-wheel simulation) when you know which area to move.

**Parameters**

- `id` `(string | ScrollElementArgs)`
- `offsetY` `number` _(optional)_

**Returns** `ScrollElementResult`

```lua
"app/scrollArea@1.0.1", 240
```

## tools/gui/setTheme {#tools-gui-settheme}

```lua
gui.setTheme(name: string)
```

Set the active UI theme. Forwards to `ui.setTheme(name)` — subsequent screen draws use the new theme.

**Parameters**

- `name` `string`

```lua
"dark"
```

## tools/gui/show {#tools-gui-show}

```lua
gui.show(name: string) -> shared.ScreenVisibility
```

Show a screen and report the visibility it now carries. Forwards to `ui.showScreen(name)`. The screen must already be registered (via `gui.panel` or `ui.registerScreen`); a name no screen answers to raises, naming the ones that do.

**Parameters**

- `name` `string`

**Returns** `shared.ScreenVisibility`

```lua
"hud"
```

## tools/gui/size {#tools-gui-size}

```lua
gui.size()
```

Return the UI coordinate space `{ width, height }` in logical points - the space `gui.bounds`, anchors, and `gui.clickAt` use (not a capture's pixel size). Forwards to `ui.screenSize`.

## tools/gui/state {#tools-gui-state}

```lua
gui.state(id: ElementId) -> WidgetResponse?
```

Read a widget's live interaction snapshot by id: `{ clicked, hovered, focused, changed, value }`. Assert what a UI is doing after you drive it. Forwards to `ui.response`.

**Parameters**

- `id` `ElementId`

**Returns** `WidgetResponse?`

```lua
"volume_slider"
```

## tools/gui/themes {#tools-gui-themes}

```lua
gui.themes() -> { string }
```

List registered themes. Forwards to `ui.listThemes()`.

**Returns** `{ string }`

## tools/gui/toggle {#tools-gui-toggle}

```lua
gui.toggle(name: string) -> shared.ScreenVisibility
```

Flip a screen's visibility and report the visibility it now carries. Reads the screen's current state from `ui.listScreens()`, then calls `ui.hideScreen` or `ui.showScreen`. Raises when no screen answers to `name`, naming the ones that do.

**Parameters**

- `name` `string`

**Returns** `shared.ScreenVisibility`

```lua
"hud"
```

## tools/gui/update {#tools-gui-update}

```lua
gui.update(screenName: string, widgetId: string, props: { [string]: any })
```

Update a specific widget inside a screen by widget ID. Reads the current widget tree via `ui.getScreenTree(screenName)`, walks it to find the widget with the matching `id`, copies every key from `props` onto the matched node, and writes the modified tree back via `ui.updateScreen`. If the screen does not exist, prints a warning and returns without erroring.

**Parameters**

- `screenName` `string`
- `widgetId` `string`
- `props` `{ [string]: any }`

```lua
'hud', 'score', { text = 'Score: 500' }
```

## tools/gui/validate {#tools-gui-validate}

```lua
gui.validate(screen?: (string | { screen: string })) -> any
```

Return the diagnostics from the last render of a screen (or all screens when no name is given): unknown widget types, bad props, decode/build errors. First stop when a screen renders wrong. Forwards to `ui.lastValidation`.

**Parameters**

- `screen` `(string | { screen: string })` _(optional)_

**Returns** `any`

```lua
"glass"
```

## tools/gui/widgetProps {#tools-gui-widgetprops}

```lua
gui.widgetProps(typeName: string) -> table?
```

Get property definitions for a widget type. Forwards to `ui.getWidgetProps(typeName)` — the returned table describes every property a widget of this type accepts (name, type, default).

**Parameters**

- `typeName` `string`

**Returns** `table?`

```lua
"button"
```

## tools/gui/widgetTypes {#tools-gui-widgettypes}

```lua
gui.widgetTypes() -> { string }
```

List available widget types. Forwards to `ui.getWidgetTypes()`.

**Returns** `{ string }`

## tools/ik/bones {#tools-ik-bones}

```lua
ik.bones(of: string | EntityRef, filter?: string) -> BonesResult
```

List a rigged body's bones with their world positions and parents. Resolves the body itself or the first descendant carrying a rigged skeleton, so it can be called on a character root without knowing where inside the spawned hierarchy the skeleton lives. Also returns the canonical role map when the rig has one, which is what `IKLimb.role` and `IKRig` resolve through; a rig with no retarget profile returns an empty map, and its chains have to be named bone by bone.

**Parameters**

- `of` `string | EntityRef`
- `filter` `string` _(optional)_

**Returns** `BonesResult`

```lua
"hero"
"hero", "hand"
player.avatar.id()
```

## tools/ik/reach {#tools-ik-reach}

```lua
ik.reach(of: string | EntityRef, chain: string, target?: (string | EntityRef)) -> ReachResult
```

Report a chain's reachable band and, when a target is given, whether that target falls inside it. A limb reaches as far as its bones are long and no further: beyond that the chain extends straight toward the target and stops, which is correct and indistinguishable from a broken solver. Under `minReach` (the fold limit) it cannot fold tightly enough. Everything is reported in world units, so the rig's model space — where a 0.61 m arm reports a reach near 61 — does not have to be reasoned about.

**Parameters**

- `of` `string | EntityRef`
- `chain` `string`
- `target` `(string | EntityRef)` _(optional)_

**Returns** `ReachResult`

```lua
"hero", "rightarm"
"hero", "rightarm", "doorknob"
"crane", "boom_base..boom_tip", "hook"
```

## tools/ik/state {#tools-ik-state}

```lua
ik.state(of: string | EntityRef) -> StateResult
```

Gather every IK component on an entity and report each one's state, so "is anything solving at all" is a single call. Each entry carries a `status` in words: a body spawned from a bundle takes a few frames to arrive, and a paused engine never advances those frames, so "not ready" on its own does not distinguish a wait that clears from one that never will. Where a component reports a distance to its target, that number is the residual after solving — small means the solve arrived, and it is not the distance to compare against a limb's reach (use `ik.reach`).

**Parameters**

- `of` `string | EntityRef`

**Returns** `StateResult`

```lua
"hero"
player.avatar.id()
```

## tools/importers/await {#tools-importers-await}

```lua
importers.await(path: string, timeoutSecs?: number) -> AwaitResult
```

Wait until the import job for `path` reaches a terminal state — `imported`, `failed`, `skipped`, or `unclaimed` — or the timeout elapses. Returns the job record (still `running` on timeout). When no job exists for the path at all, returns the gate explanation instead, so the call always answers what happened.

**Parameters**

- `path` `string`
- `timeoutSecs` `number` _(optional)_

**Returns** `AwaitResult`

```lua
"/zero/source/models/gun.fbx"
"/zero/source/models/gun.fbx", 60
```

## tools/importers/cancel {#tools-importers-cancel}

```lua
importers.cancel(path?: string) -> CancelResult
```

Cancel a queued source before it imports, or every queued source at once. Pass a `path` to cancel just that one; omit it to cancel ALL queued sources (an accidental 1k-file drop). Sources already importing are left to finish. A cancelled source settles as a `cancelled` job, visible in `importers.status`.

**Parameters**

- `path` `string` _(optional)_

**Returns** `CancelResult`

```lua
"/zero/source/models/gun.fbx"
```

## tools/importers/explain {#tools-importers-explain}

```lua
importers.explain(path: string) -> ExplainResult
```

Explain what the importer system would do with `path` right now, without importing: whether the file exists and reads, which importers claim it, and which gate (if any) blocks an import. `verdict` is `would-import` or `blocked`; when blocked, `reason` names the gate — `missing` (no such file), `unreadable`, `no-claimant` (no importer claims the extension/content), `already-in-own-bundle` (the source already lives inside its produced asset), or `unchanged-content` (the bytes match the last import; `output` points at the existing asset).

**Parameters**

- `path` `string`

**Returns** `ExplainResult`

```lua
"/zero/source/models/gun.fbx"
```

## tools/importers/imported {#tools-importers-imported}

```lua
importers.imported() -> ImportedResult
```

List every imported asset in the world with its source and the importer that produced it. Each row: `{ asset, source = { guid, path }, importer (guid), importerName, iteration, at }`, newest import first. The stored importer link is the guid; `importerName` resolves it for display.

**Returns** `ImportedResult`

```lua
use_tool { toolbox = "importers", tool = "imported" }
```

## tools/importers/list {#tools-importers-list}

```lua
importers.list() -> { count: number, importers: { { name: string, identity: string } } }
```

List every registered importer with its name and identity. Importers are `.importer/` assets, each a `canImport(path, bytes)` + `import(ctx)` pair; a written source file is offered to each claimant in registration order and the first to produce output wins.

**Returns** `{ count: number, importers: { { name: string, identity: string } } }`

## tools/importers/queue {#tools-importers-queue}

```lua
importers.queue() -> QueueResult
```

The live import backlog: queued sources first (still waiting behind the bounded concurrency, in FIFO order) then the ones importing right now. A bulk drop of N files imports one at a time, so this is where you watch the queue drain. Cancel queued items with `importers.cancel`.

**Returns** `QueueResult`

## tools/importers/run {#tools-importers-run}

```lua
importers.run(target: string | { string }, opts?: RunOpts) -> RunResult
```

Import/reimport one target, many targets, or a folder. A produced asset reimports in place (source resolved from its provenance); a loose source imports; a folder is scanned (recursive by default), its imported assets reimported and loose sources imported, filtered by `opts.mode` ("all" | "new" | "existing"). `mode` governs folder scanning; explicit path/array targets are always processed.

**Parameters**

- `target` `string | { string }`
- `opts` `RunOpts` _(optional)_

**Returns** `RunResult`

```lua
"/zero/source/guns"
```

## tools/importers/status {#tools-importers-status}

```lua
importers.status(opts?: { state: ImportJobState?, path: string?, limit: number? }) -> StatusSummary
```

List recent import jobs, newest first, with the live queue depth. Every dispatch is recorded: `queued` (waiting behind the bounded concurrency), `running` (import in flight), `imported` (done — `output` is the produced asset path), `failed` (the importer threw — `error` carries the message), `unclaimed` (every importer deferred), `skipped` (gated — `reason` is `already-in-own-bundle`, `unchanged-content`, `no-claimant`, or `unreadable`), and `cancelled` (a queued source cancelled before it ran). `backlog` is the exact queue depth (`{ queued, running }`), `active` lists the jobs importing right now, and `totals` tallies every recorded job by state — poll those three to watch a bulk drop drain. See the full backlog list with `importers.queue`; drop queued items with `importers.cancel`. Filter the `jobs` window with `opts.state` (one state), `opts.path` (substring match on the source path), and `opts.limit` (default 25).

**Parameters**

- `opts` `{ state: ImportJobState?, path: string?, limit: number? }` _(optional)_

**Returns** `StatusSummary`

```lua
{ state = "failed" }
{ path = "weapons", limit = 5 }
```

## tools/inputAuthor/catalog {#tools-inputauthor-catalog}

```lua
inputAuthor.catalog() -> { any }
```

Every control the catalog knows, with what each is bound to on keyboard, gamepad and touch. Naming one of these in `map` or `control` gets all three filled in.

**Returns** `{ any }`

## tools/inputAuthor/check {#tools-inputauthor-check}

```lua
inputAuthor.check(map: string) -> any
```

Read a `.inputMap` and report every problem across its controls — a missing device class, a malformed record, a button whose class is not an array. Reports all of them at once rather than stopping at the first, and activates nothing, so a scheme can be checked before a world runs it.

**Parameters**

- `map` `string`

**Returns** `any`

```lua
"racer"
```

## tools/inputAuthor/control {#tools-inputauthor-control}

```lua
inputAuthor.control(mapPath: string, control: any) -> string
```

Add a control to an existing `.inputMap`, carrying keyboard, gamepad and touch. A catalog name arrives pre-filled; anything else needs all three classes from you.

**Parameters**

- `mapPath` `string`
- `control` `any` _(optional)_

**Returns** `string`

```lua
"/zero/source/inputMaps/racer.inputMap", "jump"
mapPath, { name = "horn", label = "Horn", kind = "button", kbm = '{ B.key("KeyH") }', gamepad = '{ B.padButton("north") }', touch = '{ B.touchButton({ zone = "right-lower" }) }' }
```

## tools/inputAuthor/map {#tools-inputauthor-map}

```lua
inputAuthor.map(name: string, controls: { any }, opts?: MapOpts) -> any
```

Write a `<name>.inputMap/` and every control it declares, each carrying keyboard, gamepad and touch.

**Parameters**

- `name` `string`
- `controls` `{ any }`
- `opts` `MapOpts` _(optional)_

**Returns** `any`

```lua
"racer", { "move", "brake", { name = "boost", from = "sprint", label = "Nitro" } }
"shooter", { "move", "look", "attack", "aim", "reload", "jump" }
"driving", { "move", "brake" }, { group = "vehicle", suppresses = { "player" } }
```

## tools/inputSim/bindings {#tools-inputsim-bindings}

```lua
inputSim.bindings() -> { any }
```

Every control that is live right now: its name, the label a player sees, its kind, and which activated map contributed it.

**Returns** `{ any }`

## tools/inputSim/click {#tools-inputsim-click}

```lua
inputSim.click(opts?: ClickOpts)
```

Click a mouse button — press, optional hold, release — optionally moving the cursor to `(x, y)` first.

**Parameters**

- `opts` `ClickOpts` _(optional)_

```lua
{ x = 400, y = 300 }
{ button = 1 }
```

## tools/inputSim/device {#tools-inputsim-device}

```lua
inputSim.device(class?: string) -> string
```

Read the active device class, or force one. With no argument this reports what the session currently looks like: "kbm", "touch" or "gamepad".

**Parameters**

- `class` `string` _(optional)_

**Returns** `string`

```lua
"touch"
"auto"
```

## tools/inputSim/drag {#tools-inputsim-drag}

```lua
inputSim.drag(dx: number, dy: number, steps?: number)
```

Drag across the on-screen look zone by `(dx, dy)` pixels, in `steps` increments so the per-frame deltas a look binding reads are real rather than one impossible jump.

**Parameters**

- `dx` `number`
- `dy` `number`
- `steps` `number` _(optional)_

```lua
200, 0
```

## tools/inputSim/fired {#tools-inputsim-fired}

```lua
inputSim.fired(peek?: boolean) -> { any }
```

Every control that fired since this tool last ran, with how many times and on which device class. Reading CLEARS the record, so two calls around an action answer "did that input reach the game" without the previous test's results bleeding in.

**Parameters**

- `peek` `boolean` _(optional)_

**Returns** `{ any }`

```lua
true
```

## tools/inputSim/held {#tools-inputsim-held}

```lua
inputSim.held() -> any
```

Everything the session is holding down right now: keys, mouse buttons, touch contacts, the on-screen stick, and connected pads with the buttons they hold. `atRest` is true when it holds nothing.

**Returns** `any`

## tools/inputSim/key {#tools-inputsim-key}

```lua
inputSim.key(key: string, duration?: number)
```

Tap a key — down, optionally held for `duration` seconds, then up. Queues the simulated events, yields a frame so they drain, and ticks so subscribers fire before the call returns.

**Parameters**

- `key` `string`
- `duration` `number` _(optional)_

```lua
"Space"
"KeyW", 2
```

## tools/inputSim/keyDown {#tools-inputsim-keydown}

```lua
inputSim.keyDown(key: string)
```

Press a key and leave it HELD until `keyUp` releases it. A second `keyDown` on a held key produces no new press edge — pair every `keyDown` with a `keyUp`, or use `key` for a whole tap.

**Parameters**

- `key` `string`

```lua
"ShiftLeft"
```

## tools/inputSim/keyUp {#tools-inputsim-keyup}

```lua
inputSim.keyUp(key: string)
```

Release a key. Only the release re-arms the next press edge.

**Parameters**

- `key` `string`

```lua
"ShiftLeft"
```

## tools/inputSim/layout {#tools-inputsim-layout}

```lua
inputSim.layout() -> any
```

What the touch overlay is drawing right now: each visible button with its label and centre, the stick and look zones, and whatever overflowed behind the fan.

**Returns** `any`

## tools/inputSim/listMacros {#tools-inputsim-listmacros}

```lua
inputSim.listMacros() -> { AssetRef<inputMacro> }
```

Enumerate every registered `.inputMacro` asset in the project. Thin wrapper over `asset.list("inputMacro")`. Returns an array of `AssetRef` handles suitable for direct passing to `sim.macro`.

**Returns** `{ AssetRef<inputMacro> }`

## tools/inputSim/lockPointer {#tools-inputsim-lockpointer}

```lua
inputSim.lockPointer(locked: boolean) -> boolean
```

Lock the pointer (cursor captured and hidden) or release it. A mouse-look binding is usually gated on the lock, so a look test that moves the cursor without locking first reads as no movement at all.

**Parameters**

- `locked` `boolean`

**Returns** `boolean`

```lua
true
```

## tools/inputSim/macro {#tools-inputsim-macro}

```lua
inputSim.macro(refOrEvents: MacroEventList | MacroEventsWrapper | AssetRef<inputMacro> | string)
```

Play a macro of timed input events. Accepts: - inline event list: `{ { t = 0, op = "keyDown", arg = "KeyW" }, ... }` - `AssetRef<inputMacro>` — loaded via `asset.resolve` + `vfs.read` - identity / path string of an `.inputMacro` asset. Each event: `{ t, op, arg }`. `t` is seconds since macro start (events are sorted on play). `op` is one of: `keyDown`, `keyUp`, `mouseDown`, `mouseUp`, `mouseMove`, `scroll`, `lockPointer`, `tap`, `click`. `arg` shape matches the op. No game-specific recipes — drive a player, drive a car, or drive a UI test through the same primitive. Author macros by hand or record them via `sim.recordMacro` and persist via `sim.saveMacro`.

**Parameters**

- `refOrEvents` `MacroEventList | MacroEventsWrapper | AssetRef<inputMacro> | string`

```lua
{ { t = 0.0, op = "mouseDown", arg = 1 }, { t = 0.0, op = "keyDown", arg = "KeyW" }, { t = 0.5, op = "mouseMove", arg = { dx = 200, dy = 0 } }, { t = 1.5, op = "keyUp", arg = "KeyW" }, { t = 1.5, op = "mouseUp", arg = 1 } }
asset.ref("my_walkthrough", "inputMacro")
```

## tools/inputSim/mouseDown {#tools-inputsim-mousedown}

```lua
inputSim.mouseDown(button?: number)
```

Press a mouse button and leave it HELD until `mouseUp`.

**Parameters**

- `button` `number` _(optional)_

```lua
1
```

## tools/inputSim/mouseMove {#tools-inputsim-mousemove}

```lua
inputSim.mouseMove(x: number, y: number)
```

Move the cursor to `(x, y)` in screen coordinates. The delta from the previous position is what a mouse-look binding reads, so two calls in a row produce a look movement.

**Parameters**

- `x` `number`
- `y` `number`

```lua
640, 360
```

## tools/inputSim/mouseMoveBy {#tools-inputsim-mousemoveby}

```lua
inputSim.mouseMoveBy(dx: number, dy: number)
```

Move the pointer by `(dx, dy)` pixels, the motion a mouse device reports. The same motion repeated keeps producing look movement, so a look axis can be held, or steered by a controller issuing a correction each tick.

**Parameters**

- `dx` `number`
- `dy` `number`

```lua
0, 140
```

## tools/inputSim/mouseUp {#tools-inputsim-mouseup}

```lua
inputSim.mouseUp(button?: number)
```

Release a mouse button.

**Parameters**

- `button` `number` _(optional)_

```lua
1
```

## tools/inputSim/pad {#tools-inputsim-pad}

```lua
inputSim.pad(name?: string) -> number
```

Connect a simulated pad and return the slot it took. `name` is the device name the pad reports, which is what decides the button legends a prompt draws — pass an Xbox, PlayStation or Nintendo name to check a scheme's prompts on that family.

**Parameters**

- `name` `string` _(optional)_

**Returns** `number`

```lua
"Xbox Wireless Controller"
"Sony DualSense Wireless Controller"
```

## tools/inputSim/padDown {#tools-inputsim-paddown}

```lua
inputSim.padDown(button: string, slot?: number)
```

Push a canonical pad button down and leave it held until `padUp`.

**Parameters**

- `button` `string`
- `slot` `number` _(optional)_

```lua
"right_trigger"
```

## tools/inputSim/padOff {#tools-inputsim-padoff}

```lua
inputSim.padOff(slot?: number)
```

Disconnect the pad in `slot`. Anything it still held is released first, so a held action ends rather than sticking.

**Parameters**

- `slot` `number` _(optional)_

```lua
0
```

## tools/inputSim/padPress {#tools-inputsim-padpress}

```lua
inputSim.padPress(button: string, duration?: number, slot?: number)
```

Press a canonical pad button — down, optionally held, then up. Buttons are named by POSITION, not by the letter printed on them, so the same call works whatever pad is connected: south, east, west, north, left_shoulder, right_shoulder, left_trigger, right_trigger, left_stick, right_stick, select, start, guide, dpad_up, dpad_down, dpad_left, dpad_right.

**Parameters**

- `button` `string`
- `duration` `number` _(optional)_
- `slot` `number` _(optional)_

```lua
"south"
"left_shoulder", 1.5
```

## tools/inputSim/padStick {#tools-inputsim-padstick}

```lua
inputSim.padStick(stick: string, x: number, y: number, duration?: number, slot?: number)
```

Push a thumbstick to `(x, y)`, each -1..1 with y screen-down positive — the same convention the on-screen stick reports, so a binding reads the same shape from either. Holds until pushed back to zero, or for `duration` seconds if you pass one.

**Parameters**

- `stick` `string`
- `x` `number`
- `y` `number`
- `duration` `number` _(optional)_
- `slot` `number` _(optional)_

```lua
"left", 0, -1
"right", 1, 0, 2
```

## tools/inputSim/padTrigger {#tools-inputsim-padtrigger}

```lua
inputSim.padTrigger(trigger: string, value: number, duration?: number, slot?: number)
```

Squeeze a trigger to `value`, 0..1. Past half throw the trigger's digital button latches too, so a scheme binding either the analog travel or the button sees this.

**Parameters**

- `trigger` `string`
- `value` `number`
- `duration` `number` _(optional)_
- `slot` `number` _(optional)_

```lua
"right", 1
```

## tools/inputSim/padUp {#tools-inputsim-padup}

```lua
inputSim.padUp(button: string, slot?: number)
```

Release a canonical pad button.

**Parameters**

- `button` `string`
- `slot` `number` _(optional)_

```lua
"right_trigger"
```

## tools/inputSim/pinch {#tools-inputsim-pinch}

```lua
inputSim.pinch(amount: number, steps?: number)
```

Pinch two fingers together (negative) or spread them apart (positive) by `amount` pixels, over `steps` frames. What a zoom binding reads on a phone.

**Parameters**

- `amount` `number`
- `steps` `number` _(optional)_

```lua
150
```

## tools/inputSim/recordMacro {#tools-inputsim-recordmacro}

```lua
inputSim.recordMacro(opts?: RecordMacroOpts) -> ({ MacroEvent }, number)
```

Capture real input over a window. Polls `Zin.state.*` every `sampleInterval` seconds (default `0.05`); emits a timed event whenever a key, mouse button, mouse delta, or scroll changes. Returns the events array and the spawned task handle. Caller decides whether to play immediately, save to disk via `sim.saveMacro`, or both. Generic — works for any input-driven scenario (player walk, car drive, UI flow, build sequence). Stops automatically after `duration` seconds, or earlier if `until_()` returns true.

**Parameters**

- `opts` `RecordMacroOpts` _(optional)_

**Returns** `({ MacroEvent }, number)`

```lua
{ duration = 3.0 }
```

## tools/inputSim/release {#tools-inputsim-release}

```lua
inputSim.release() -> any
```

Return the session to rest: lift every touch contact, drop the on-screen stick, release every key and mouse button that is down, and disconnect every connected pad.

**Returns** `any`

## tools/inputSim/saveMacro {#tools-inputsim-savemacro}

```lua
inputSim.saveMacro(name: string, events: { MacroEventRecord }) -> AssetRef<inputMacro>
```

Persist a recorded or hand-authored event list to a `.inputMacro` asset on disk at `/zero/source/<name>.inputMacro/events.json`. Returns the `AssetRef` of the saved macro for immediate playback.

**Parameters**

- `name` `string`
- `events` `{ MacroEventRecord }`

**Returns** `AssetRef<inputMacro>`

```lua
"walk_demo", { { t = 0.0, op = "keyDown", arg = "KeyW" }, { t = 1.0, op = "keyUp", arg = "KeyW" } }
```

## tools/inputSim/scroll {#tools-inputsim-scroll}

```lua
inputSim.scroll(dy: number)
```

Turn the mouse wheel by `dy`. Positive scrolls one way, negative the other; what that means is the binding's to decide.

**Parameters**

- `dy` `number`

```lua
-3
```

## tools/inputSim/stick {#tools-inputsim-stick}

```lua
inputSim.stick(x: number, y: number, duration?: number)
```

Push the on-screen movement stick to `(x, y)`, each -1..1, with y screen-down positive — the same convention a pad stick reports, so a binding reads the same shape from either.

**Parameters**

- `x` `number`
- `y` `number`
- `duration` `number` _(optional)_

```lua
0, -1        -- full forward, held
0, 0         -- let go
1, 0, 2      -- hold right for two seconds, then release
```

## tools/inputSim/tapButton {#tools-inputsim-tapbutton}

```lua
inputSim.tapButton(label: string, duration?: number)
```

Tap the on-screen button labelled `label` — the text a player reads on it. The tap lands at the button's real centre, so it proves the control is reachable, not just that the action exists.

**Parameters**

- `label` `string`
- `duration` `number` _(optional)_

```lua
"Jump"
```

## tools/inputSim/touch {#tools-inputsim-touch}

```lua
inputSim.touch(x: number, y: number, duration?: number)
```

Tap the screen at `(x, y)` — contact, optional hold, lift. The contact id is chosen for you; use `touchDown` / `touchMove` / `touchUp` when you need to drive several fingers at once.

**Parameters**

- `x` `number`
- `y` `number`
- `duration` `number` _(optional)_

```lua
200, 600
200, 600, 0.5
```

## tools/inputSim/touchDown {#tools-inputsim-touchdown}

```lua
inputSim.touchDown(x: number, y: number, pressure?: number) -> number
```

Put a finger down at `(x, y)` and leave it there. Returns the contact id `touchMove` and `touchUp` take, so several fingers can be driven at once — a two-finger pinch is two of these.

**Parameters**

- `x` `number`
- `y` `number`
- `pressure` `number` _(optional)_

**Returns** `number`

```lua
200, 600
```

## tools/inputSim/touchMove {#tools-inputsim-touchmove}

```lua
inputSim.touchMove(id: number, x: number, y: number, pressure?: number)
```

Move the contact `id` to `(x, y)`. The per-frame delta is what a drag-look binding reads, so a look test is several of these in a row rather than one big jump.

**Parameters**

- `id` `number`
- `x` `number`
- `y` `number`
- `pressure` `number` _(optional)_

```lua
id, 260, 580
```

## tools/inputSim/touchUp {#tools-inputsim-touchup}

```lua
inputSim.touchUp(id: number)
```

Lift the contact `id`.

**Parameters**

- `id` `number`

```lua
id
```

## tools/lib/has {#tools-lib-has}

```lua
lib.has(path: string) -> boolean
```

Check if a library asset exists at a path. Thin wrapper over `library.has(path)`.

**Parameters**

- `path` `string`

**Returns** `boolean`

```lua
"@builtin/models/Sample/DamagedHelmet"
```

## tools/lib/list {#tools-lib-list}

```lua
lib.list(assetType?: string)
```

List available library content. No args returns every registered library asset; pass an asset-type string to filter (e.g. "model", "shader", "material", "component"). Thin wrapper over `library.list(assetType)`.

**Parameters**

- `assetType` `string` _(optional)_

```lua
-- all library assets
"model"  -- just models
```

## tools/lighting/addLight {#tools-lighting-addlight}

```lua
lighting.addLight(name: string, position: Vec3, opts?: AddLightOpts) -> AddLightResult
```

Add a light to the scene: spawns an entity at `position` carrying a point Light (default) or SpotLight component. One consistent shape — use `setLight` to tweak it afterwards and `removeLight` to delete it. Lights cast shadows by default (a light whose objects cast none reads as broken); pass `castsShadows = false` for a cheap fill light. `intensity` means the same thing to both kinds: the two are rows of one light buffer shaded by `color * intensity * falloff`, so the same number at the same radius puts the same light on a surface either kind faces from the same place. A spot delivers it to the cone it opens on rather than all around itself, so it lights a smaller part of the room at that number — aim it and read the surface it lands on, and reach for `radius` and `angle` before reaching for a bigger `intensity`.

**Parameters**

- `name` `string`
- `position` `Vec3`
- `opts` `AddLightOpts` _(optional)_

**Returns** `AddLightResult`

```lua
"torch1", { x = 5, y = 3, z = 0 }, { color = {1, 0.6, 0.2}, intensity = 1.5, radius = 8 }
"beam", { x = 0, y = 6, z = 0 }, { kind = "spot", intensity = 1.5, radius = 8, angle = 30 }
```

## tools/lighting/get {#tools-lighting-get}

```lua
lighting.get() -> GetLightingResult
```

Read the scene's lighting + sky state. Returns every light (entity name, id, kind, values), the sky entity and its component type + current values, and — when the sky is a material Skybox — the material's editable parameter names/kinds/values (what `setSky` `params` accepts). `sky.source = "fallback"` means the scene has NO explicit sky entity and renders the engine fallback (adds the missing-sky warning; `setSky` fixes it). Each light also reports `mobility` (how GI baking treats it), `enabled` (the component's own switch) and `contributing` — whether this light reaches the frame. Each kind answers it from what carries a light of that kind: a punctual light from the row the renderer holds for it, a directional light from whether it holds the sun, an ambient light from whether it holds the scene's ambient term. So it is false for a switched-off light of any kind, which holds neither a row nor a singleton, for a punctual light the renderer holds no row for, for a directional light that does not hold the sun, for an ambient light that does not hold the ambient term, and for a baked static light whose light comes from the bake rather than from the light itself. A light the renderer holds reports what it is doing with the light's shadow, beside the `castsShadows` flag the scene authored: `shadowing` is whether a shadow map is being drawn for it this frame, `shadowSlot` the point-shadow cube slot a point light was seated in, and `shadowLayer` the spot-shadow atlas layer a spot or rect filed into. Each is `-1` for a caster the pool had no room for, which renders lit and throws no shadow. The same row carries `layerMask`: the render layer mask of the entity carrying the light, which cameras whose include mask intersects it are lit by. A punctual, directional or ambient light answers `contributing`, `shadowing`, `shadowSlot`, `shadowLayer` and `holdsAmbient` from the light set the renderer resolved on its LAST frame, so one authored earlier in the same call reads `enabled = true` with `contributing = false` and no shadow fields; read it again a frame on and the renderer has an answer for it. A directional light reports `holdsSun`: the scene's sun is one field set with one holder, so `holdsSun = true` marks the light whose direction, colour, intensity and `castsShadows` the scene is lit and shadowed by. The values of a directional light that does not hold it are authored and unread until it takes the sun back. An ambient light reports `holdsAmbient` on the same rule: the scene's ambient term is one field set with one holder, so `holdsAmbient = true` marks the light whose colour and intensity the ambient term carries, and the values of the ambient lights beside it are authored and unread. A sky with `syncSunToLight` on drives the sun holder from its time of day, so that light's `direction`, `color` and `intensity` are what the sky put there rather than what the scene authored. When the two differ the record carries `resolved` — the values the geometry is lit by — and `drivenBy`, naming what is writing them. `directionals` and `ambients` are the scoped collections: one record per light entity, each carrying `entityId`, `layerMask` (the render layer mask of the entity carrying it — cameras whose include mask intersects it are lit by it), `color` and `intensity`, plus `direction` and `castsShadows` on a directional record. A light on an entity with no explicit render layer records the default layer.

**Returns** `GetLightingResult`

## tools/lighting/removeLight {#tools-lighting-removelight}

```lua
lighting.removeLight(name: string) -> RemoveLightResult
```

Remove a light entity by name (exact, then substring — ambiguity errors and lists the scene's lights). Despawns the entity.

**Parameters**

- `name` `string`

**Returns** `RemoveLightResult`

```lua
"torch1"
```

## tools/lighting/setAmbient {#tools-lighting-setambient}

```lua
lighting.setAmbient(opts: AmbientOpts) -> SetAmbientResult
```

Set the scene's ambient light. Modifies the entity carrying the scene's ambient light, spawning one when the scene has none, and reports the entity it wrote. Read-merge-write: ONLY the fields you pass change — `{ intensity = 0.5 }` keeps the authored color.

**Parameters**

- `opts` `AmbientOpts`

**Returns** `SetAmbientResult`

```lua
{ color = {0.4, 0.4, 0.6}, intensity = 0.3 }
{ intensity = 0.5 }
```

## tools/lighting/setLight {#tools-lighting-setlight}

```lua
lighting.setLight(name: string, opts: SetLightOpts) -> SetLightResult
```

Update a light by entity name — exact match first, then substring (ambiguity errors and lists the scene's lights). Only the fields you pass change. Works on point, spot, area and distant lights and the sun/ambient entities.

**Parameters**

- `name` `string`
- `opts` `SetLightOpts`

**Returns** `SetLightResult`

```lua
"torch1", { intensity = 3 }
"sun", { direction = {-0.2, -1, -0.1} }
```

## tools/lighting/setSky {#tools-lighting-setsky}

```lua
lighting.setSky(sky?: string, opts?: SkyOpts) -> SetSkyResult
```

Set or reconfigure the scene's sky in one call. `sky` is a word: a procedural preset (`clear_day`, `sunset`, `sunrise`, `overcast`, `night`) applies that look via a ProceduralSky component; `studio` is the solid gray sky (Skybox over `sky_solid`); `none` turns the sky off explicitly (Skybox kind="none"); any other word resolves against the project's materials FILTERED to sky-domain shaders (exact identity → leaf name → substring) and the sky renders that material via a Skybox. Omit `sky` to reconfigure the current sky with `opts` only. The tool finds the scene's sky entity (creating a "sky" entity when absent) and writes the component — changes persist, replicate, and survive reload.

**Parameters**

- `sky` `string` _(optional)_
- `opts` `SkyOpts` _(optional)_

**Returns** `SetSkyResult`

```lua
"sunset"
"aurora", { params = { exposure = 1.4 } }
nil, { timeOfDay = 3, autoCycle = true }
"none"
```

## tools/lighting/setSun {#tools-lighting-setsun}

```lua
lighting.setSun(opts: SunOpts) -> SetSunResult
```

Set the scene's directional sun light. Modifies the entity carrying the scene's directional light, spawning one when the scene has none, and reports the entity it wrote. Read-merge-write: ONLY the fields you pass change — `{ intensity = 2 }` keeps the authored direction and color. A sky with `syncSunToLight` on drives the sun from its time of day, so setting the sun here takes it back: that switch goes off and the result reports `releasedFromSky` with the sky entity's name. Move the sun by `timeOfDay` (`setSky`) to keep the sky driving it.

**Parameters**

- `opts` `SunOpts`

**Returns** `SetSunResult`

```lua
{ direction = {-0.5, -1, -0.3}, color = {1, 0.95, 0.8}, intensity = 1.2 }
{ intensity = 2 }
```

## tools/lighting/setup {#tools-lighting-setup}

```lua
lighting.setup(opts: SetupOpts) -> SetupResult
```

Set up the whole scene lighting rig in one call. Only provided sections change. `sun`/`ambient` are partial updates on the entities carrying those lights; `sky` + `skyOpts` follow `setSky` semantics (preset word, sky-material word, procedural controls / material params); `lights` is a map of light name → `addLight` opts with a `position` — each is created if missing, updated if present. Naming `sun` takes the sun back from a sky driving it from its time of day: that sky's `syncSunToLight` goes off and `releasedFromSky` reports its entity name.

**Parameters**

- `opts` `SetupOpts`

**Returns** `SetupResult`

```lua
{ sun = { intensity = 1.2 }, sky = "sunset", lights = { torch1 = { position = {x=5,y=3,z=0}, color = {1,0.6,0.2} } } }
```

## tools/localContent/forget {#tools-localcontent-forget}

```lua
localContent.forget(path: string) -> string
```

Stop holding a path under `/source/local/` on this machine. The file goes and the record of it goes too, so the next boot does not restore it. A folder takes its contents with it. Content already promoted with `localContent.persist` is untouched — that copy belongs to the world.

**Parameters**

- `path` `string`

**Returns** `string`

```lua
"gestures/wave.json"
```

## tools/localContent/list {#tools-localcontent-list}

```lua
localContent.list() -> HeldContent
```

List everything this machine holds under `/source/local/` — the content a session wrote that stays on the device instead of going to the world. Each entry reports its path and its size in bytes, in path order, with the total alongside. Pairs with `localContent.persist` (list -> persist).

**Returns** `HeldContent`

## tools/localContent/persist {#tools-localcontent-persist}

```lua
localContent.persist(path: string, destination?: string) -> { string }
```

Copy content this machine has been holding under `/source/local/` into the world, at the same relative path under `/source/` unless `destination` says otherwise. Takes a file or a whole folder. The local copy stays where it is — persisting is a promotion, not a move, so a failed publish never costs the only copy. Refused while play is running: a `/source` write during play lands on the play shadow and would not persist, which is the exact failure this tool exists to end. Pairs with `localContent.list` (list -> persist).

**Parameters**

- `path` `string`
- `destination` `string` _(optional)_

**Returns** `{ string }`

```lua
"gestures/wave.json"
```

## tools/logs/clear {#tools-logs-clear}

```lua
logs.clear() -> ClearResult
```

Drop the buffered log entries, so a following search sees only what happened after the clear. Lifetime per-level counts are preserved, so "were there ever any errors" stays answerable. Rarely the right move: `summary` returns a `cursor` which, passed back to `search` as `since`, isolates what an action logged without discarding lines anybody else may want.

**Returns** `ClearResult`

## tools/logs/errors {#tools-logs-errors}

```lua
logs.errors(opts?: (string | ErrorsOpts)) -> ErrorsPage
```

Recent errors and warnings, newest first. A snapshot — the same lines however many times it is called. That is the difference from the `problems` tool in the `debug` toolbox, which drains: it advances a read-cursor and reports only what has not been seen, so that one answers "what is new" while this answers "what is there". Accepts the same filters as `search`.

**Parameters**

- `opts` `(string | ErrorsOpts)` _(optional)_

**Returns** `ErrorsPage`

```lua
{ level = "error", limit = 10 }
{ within = 120 }
"shader"
```

## tools/logs/search {#tools-logs-search}

```lua
logs.search(opts?: (string | SearchQuery)) -> SearchResult
```

Search what the engine logged. Filter by severity, subsystem, substring or regex, the script or entity that logged it, and a time window — every filter is optional and they narrow together. This is the general view over the same ring the `problems` tool in the `debug` toolbox drains: reach for it when you need a specific message, a specific entity, a specific minute, or the lines that led up to a failure. The same ring reads as plain text at `/zero/runtime/logs/engine`, which is the shorter reach for a string you can already name; this is the view that filters it.

**Parameters**

- `opts` `(string | SearchQuery)` _(optional)_

**Returns** `SearchResult`

```lua
{ level = "error", within = 60 }
{ entity = "ent_9f3c", limit = 20 }
{ level = "error", context = 5 }
{ level = "warn", group = true }
{ contains = "shader", type = "RENDERER" }
"shader"
```

## tools/logs/summary {#tools-logs-summary}

```lua
logs.summary(opts?: (string | SummaryOpts)) -> SummaryReport
```

How many lines the engine logged, of what severity, from which subsystems and entities, and which messages repeat — the shape of the noise before you go looking inside it. Also the cheapest "did anything go wrong" check, and the source of the cursor for incremental polling: read `cursor` before an action, pass it back to `search` as `since` afterwards, and you see only what that action logged. Accepts the same filters as `search`, so it can describe a slice as well as the whole.

**Parameters**

- `opts` `(string | SummaryOpts)` _(optional)_

**Returns** `SummaryReport`

```lua
{ level = "error" }
{ within = 300 }
```

## tools/logs/tail {#tools-logs-tail}

```lua
logs.tail(opts?: (string | TailOpts)) -> TailPage
```

The most recent lines the engine logged, any level, oldest first — the last line is the newest. The view for when you do not yet know what you are looking for; once you do, `search` narrows it. Accepts the same filters as `search`.

**Parameters**

- `opts` `(string | TailOpts)` _(optional)_

**Returns** `TailPage`

```lua
{ limit = 40 }
{ type = "SCRIPT" }
"shader"
```

## tools/notices/drain {#tools-notices-drain}

```lua
notices.drain() -> Drained?
```

Take every pending notice and return the formatted delivery block — the same text that would otherwise be attached to your next tool result. DRAINING: what this returns is no longer queued, so read it. Returns nil when nothing is pending. For a caller that pulls its own context; if you read notices off your tool results, you do not need this. Overflow content is written to /source/tmp/notices/ and named in the block.

**Returns** `Drained?`

## tools/notices/list {#tools-notices-list}

```lua
notices.list() -> NoticesList
```

Show the currently pending notice keys and the suppressed set. Each pending row carries its key, template, severity, origin, count, and source; each suppressed row carries its key and the number of times it has fired while muted. Use a key from here with `suppress`.

**Returns** `NoticesList`

## tools/notices/suppress {#tools-notices-suppress}

```lua
notices.suppress(key: string) -> { suppressed: string }
```

Mute a notice key you have acknowledged. It keeps counting (visible in `list`) but never renders on a tool call again this session. Available to the operating agent; a loaded world component cannot mute its own notices.

**Parameters**

- `key` `string`

**Returns** `{ suppressed: string }`

```lua
"user:atlas:texture rebuilt"
```

## tools/notices/unsuppress {#tools-notices-unsuppress}

```lua
notices.unsuppress(key: string) -> { unsuppressed: string }
```

Un-mute a notice key you previously suppressed, so it renders on a tool call again. Available to the operating agent.

**Parameters**

- `key` `string`

**Returns** `{ unsuppressed: string }`

```lua
"user:atlas:texture rebuilt"
```

## tools/phys/addBody {#tools-phys-addbody}

```lua
phys.addBody(id: string, bodyType?: ("dynamic" | "static" | "kinematic"), opts?: BodyOpts) -> string
```

Add Physics + Collider components to an entity in one call. `bodyType` selects the rigid-body kind: * `"dynamic"` — falls / responds to forces and collisions * `"static"` — immovable, only collides * `"kinematic"` — script-driven, ignores forces Flashes a purple outline (0.5s) so the change is visible.

**Parameters**

- `id` `string`
- `bodyType` `("dynamic" | "static" | "kinematic")` _(optional)_
- `opts` `BodyOpts` _(optional)_

**Returns** `string`

```lua
'my_cube'                                    -- dynamic box
'floor', 'static', { shape = 'box' }        -- static floor
'ball', 'dynamic', { shape = 'sphere', radius = 0.5 }
```

## tools/phys/addConstraint {#tools-phys-addconstraint}

```lua
phys.addConstraint(entityId: string | EntityRef, constraintType: ConstraintKind, targetEntityId: string | EntityRef, opts?: ConstraintOpts) -> string | EntityRef
```

Add a transform constraint to an entity. Constraint types: * `"position"` — follow target's position * `"rotation"` — follow target's rotation * `"scale"` — follow target's scale * `"lookat"` — orient toward target * `"parent"` — full local-space parent relationship * `"aim"` — orient one axis toward target The constraint runs on the constraints system every frame, not the physics solver — so it works on entities without Physics bodies too.

**Parameters**

- `entityId` `string | EntityRef`
- `constraintType` `ConstraintKind`
- `targetEntityId` `string | EntityRef`
- `opts` `ConstraintOpts` _(optional)_

**Returns** `string | EntityRef`

```lua
"follower", "lookat", "player"
"camera", "position", "car", { offset = {0, 4, 8}, weight = 0.9 }
```

## tools/phys/addJoint {#tools-phys-addjoint}

```lua
phys.addJoint(entityIdA: string, entityIdB?: (string | AddJointOpts), opts?: AddJointOpts) -> string
```

Add a physics joint between two entities. Both must have Physics components. Two call shapes are accepted: * `phys.addJoint(entityA, entityB, opts?)` — explicit two-entity form. * `phys.addJoint(entityA, opts)` where `opts.target` is the second entity name or ID — matches the README's `phys.addJoint("crate", { target = "wall", ... })` shape and the Joint component's `target` alias. Validates joint kind against the supported set (`fixed`, `hinge`, `ball`, `prismatic`, `spring`, `rope`), resolves names to entity IDs, normalises vec3 args (`localAnchor`, `remoteAnchor`, `axis`), and rejects `stiffness` / `damping` on `kind = "fixed"` and `kind = "rope"` since neither has a motorable axis (Closes #1062 for the silently-discarded case; Closes #527 for the silent no-op when entities were missing). `kind = "rope"` requires `maxDistance > 0` — the maximum distance between the anchor points; slack is free, taut resists extension. `breakForce` (newtons of linear reaction) and `breakTorque` (the angular row of the same reaction) release the joint once the reaction it carries exceeds either; the break is reported through `Physics.onJointBreak` and the joint's own `onBreak`, and a joint given neither is unbreakable.

**Parameters**

- `entityIdA` `string`
- `entityIdB` `(string | AddJointOpts)` _(optional)_
- `opts` `AddJointOpts` _(optional)_

**Returns** `string`

```lua
"door", "frame", { kind = "hinge", axis = {0, 1, 0} }
"crate", { target = "wall", kind = "spring", stiffness = 200, damping = 10 }
"ball", { target = "hook", kind = "rope", maxDistance = 8 }
"crate", { target = "wall", kind = "fixed", breakForce = 1200, breakTorque = 800 }
```

## tools/phys/ignoreCollision {#tools-phys-ignorecollision}

```lua
phys.ignoreCollision(entityIdA: string, entityIdB: string, ignore?: boolean) -> string
```

Set whether two entities ignore collisions between each other. `ignore` defaults to `true`; pass `false` to re-enable collisions. Useful for "drop pickup through self" and similar paired-entity rules without touching collision groups.

**Parameters**

- `entityIdA` `string`
- `entityIdB` `string`
- `ignore` `boolean` _(optional)_

**Returns** `string`

```lua
"player", "pickup"
"player", "pickup", false  -- re-enable
```

## tools/phys/lockRotation {#tools-phys-lockrotation}

```lua
phys.lockRotation(entityId: string | EntityRef, lockX: boolean, lockY: boolean, lockZ: boolean) -> string | EntityRef
```

Lock rotation axes on an entity's rigid body. Each flag is independent — `(true, false, true)` lets the body spin only around Y. Useful for top-down characters, hinged doors, and constrained vehicles.

**Parameters**

- `entityId` `string | EntityRef`
- `lockX` `boolean`
- `lockY` `boolean`
- `lockZ` `boolean`

**Returns** `string | EntityRef`

```lua
"cube", true, false, true  -- only rotate around Y
```

## tools/phys/lockTranslation {#tools-phys-locktranslation}

```lua
phys.lockTranslation(entityId: string | EntityRef, lockX: boolean, lockY: boolean, lockZ: boolean) -> string | EntityRef
```

Lock translation axes on an entity's rigid body. Each flag is independent — `(true, true, false)` lets the body only slide along Z (e.g. a rail / track).

**Parameters**

- `entityId` `string | EntityRef`
- `lockX` `boolean`
- `lockY` `boolean`
- `lockZ` `boolean`

**Returns** `string | EntityRef`

```lua
"rail", true, true, false  -- only move along Z
```

## tools/phys/removeBody {#tools-phys-removebody}

```lua
phys.removeBody(id: string) -> string
```

Remove an entity's Physics component and whichever collider component it carries. Strips the collider before Physics so the engine never sees a collider without a body during the transition.

**Parameters**

- `id` `string`

**Returns** `string`

```lua
"my_cube"
```

## tools/phys/removeConstraint {#tools-phys-removeconstraint}

```lua
phys.removeConstraint(entityId: string | EntityRef, index?: number) -> string | EntityRef
```

Remove transform constraints from an entity. Pass an 0-based `index` to remove a specific constraint; omit it to remove all of them.

**Parameters**

- `entityId` `string | EntityRef`
- `index` `number` _(optional)_

**Returns** `string | EntityRef`

```lua
"follower"        -- remove all
"follower", 0     -- remove just the first
```

## tools/phys/removeJoint {#tools-phys-removejoint}

```lua
phys.removeJoint(entityId: string | EntityRef) -> string | EntityRef
```

Remove all joints from an entity. Thin wrapper over `Physics.removeJoint(entityId)` — included in the toolbox so add/remove joint flows live behind the same `phys.*` surface.

**Parameters**

- `entityId` `string | EntityRef`

**Returns** `string | EntityRef`

```lua
"crate"
```

## tools/phys/setBodyType {#tools-phys-setbodytype}

```lua
phys.setBodyType(entityId: string | EntityRef, bodyType: "dynamic" | "kinematic" | "static") -> string | EntityRef
```

Change a rigid body's type at runtime. Preserves mass, colliders, and joints — only the body's response to forces and position writes changes. Useful for ragdoll activation (freeze pose as kinematic until hit, then flip to dynamic), freeze / unfreeze mechanics, and switching platforms between kinematic (scripted motion) and static. An unknown `bodyType` string raises a Luau error with the valid set.

**Parameters**

- `entityId` `string | EntityRef`
- `bodyType` `"dynamic" | "kinematic" | "static"`

**Returns** `string | EntityRef`

```lua
"player", "kinematic"  -- freeze
"player", "dynamic"    -- unfreeze, respond to forces
```

## tools/phys/setCollisionGroups {#tools-phys-setcollisiongroups}

```lua
phys.setCollisionGroups(entityId: string | EntityRef, membership: number, filter: number) -> string | EntityRef
```

Set collision group bitmasks on an entity's colliders. The two bitmasks encode "what am I" (`membership`) and "what do I collide with" (`filter`) separately — two entities collide iff each entity's `filter` contains a bit from the other entity's `membership`.

**Parameters**

- `entityId` `string | EntityRef`
- `membership` `number`
- `filter` `number`

**Returns** `string | EntityRef`

```lua
"player", 0x1, 0x2  -- Player on group 1 collides only with terrain (group 2)
```

## tools/phys/spawnDynamic {#tools-phys-spawndynamic}

```lua
phys.spawnDynamic(name: string, url?: (string | AssetRef<mesh>), x?: number, y?: number, z?: number, opts?: SpawnOpts) -> string
```

Spawn an entity with a Model + dynamic Physics body in one call. The returned id is the unique `ent_...` handle from `entity.spawn` (not the display name), matching `sc.spawnModel`'s contract (#579). Pops the entity in with a purple viz pulse.

**Parameters**

- `name` `string`
- `url` `(string | AssetRef<mesh>)` _(optional)_
- `x` `number` _(optional)_
- `y` `number` _(optional)_
- `z` `number` _(optional)_
- `opts` `SpawnOpts` _(optional)_

**Returns** `string`

```lua
'my_box'
'ball', 'sphere', 0, 5, 0, { mass = 1 }
```

## tools/phys/spawnStatic {#tools-phys-spawnstatic}

```lua
phys.spawnStatic(name: string, url?: (string | AssetRef<mesh>), x?: number, y?: number, z?: number, opts?: SpawnOpts) -> string
```

Spawn an entity with a Model + static collider in one call. Static = immovable, only collides. The returned id is the unique `ent_...` handle from `entity.spawn`, matching `sc.spawnModel`'s contract (#579).

**Parameters**

- `name` `string`
- `url` `(string | AssetRef<mesh>)` _(optional)_
- `x` `number` _(optional)_
- `y` `number` _(optional)_
- `z` `number` _(optional)_
- `opts` `SpawnOpts` _(optional)_

**Returns** `string`

```lua
'floor', 'cube', 0, 0, 0, { scale = { 10, 1, 10 } }
```

## tools/phys/whyStill {#tools-phys-whystill}

```lua
phys.whyStill(entityId: string | EntityRef) -> { [string]: any }
```

Answer why the solver is not moving an entity's rigid body. The reason is the nearest cause from a closed set — `noBody`, `simulationNotStepping`, `disabled`, `static`, `kinematic`, `infiniteMass`, `translationLocked`, `gravityDisabled`, `asleep`, `outsideIsland`, `resting`, `aboutToMove` — so it names the thing to change rather than a consequence of it. A body the solver IS advancing reports `moving = true` and no reason. Every value comes off the simulation, so a write the solver refused or clamped reads back as what it kept. Answers in edit mode as well as play mode.

**Parameters**

- `entityId` `string | EntityRef`

**Returns** `{ [string]: any }`

```lua
"crate"
```

## tools/phys/worldState {#tools-phys-worldstate}

```lua
phys.worldState() -> { [string]: any }
```

Report what the physics simulation is actually holding: bodies by type, how many are awake and asleep, colliders, joints, contact pairs / touching pairs / contact points, the bodies the last step integrated, world gravity, the timestep, whether the pipeline is stepping at all, and what the last step cost. Counted off the solver, so a body that failed to build is missing here while its `Physics` component still exists. Answers in edit mode as well as play mode.

**Returns** `{ [string]: any }`

```lua
-- no arguments
```

## tools/pixelArt/add {#tools-pixelart-add}

```lua
pixelArt.add(opts: PaletteAdd)
```

**Parameters**

- `opts` `PaletteAdd`

## tools/pixelArt/addFrame {#tools-pixelart-addframe}

```lua
pixelArt.addFrame(opts: AnimAddFrame) -> number?
```

**Parameters**

- `opts` `AnimAddFrame`

**Returns** `number?`

## tools/pixelArt/anim {#tools-pixelart-anim}

```lua
pixelArt.anim(opts: AnimOpts) -> number?
```

Multi-frame animation control. Pick the action via `opts.op`. All ops take `target` (handle/id/name). Frames share the canvas palette + size; draw into a frame by making it active (`setActiveFrame`) then using `Pixel.draw`.

**Parameters**

- `opts` `AnimOpts`

**Returns** `number?`

```lua
{ op = 'duplicateFrame', target = s }
{ op = 'setFps', target = s, fps = 8 }
```

## tools/pixelArt/bounds {#tools-pixelart-bounds}

```lua
pixelArt.bounds(opts: QueryBounds) -> any
```

**Parameters**

- `opts` `QueryBounds`

**Returns** `any`

## tools/pixelArt/circle {#tools-pixelart-circle}

```lua
pixelArt.circle(opts: DrawCircle)
```

**Parameters**

- `opts` `DrawCircle`

## tools/pixelArt/clear {#tools-pixelart-clear}

```lua
pixelArt.clear(opts: DrawClear)
```

**Parameters**

- `opts` `DrawClear`

## tools/pixelArt/clone {#tools-pixelart-clone}

```lua
pixelArt.clone(opts: SpawnClone) -> Handle
```

**Parameters**

- `opts` `SpawnClone`

**Returns** `Handle`

## tools/pixelArt/countByName {#tools-pixelart-countbyname}

```lua
pixelArt.countByName(opts: QueryCountByName) -> any
```

**Parameters**

- `opts` `QueryCountByName`

**Returns** `any`

## tools/pixelArt/create {#tools-pixelart-create}

```lua
pixelArt.create(opts: SpawnCreate) -> Handle
```

**Parameters**

- `opts` `SpawnCreate`

**Returns** `Handle`

## tools/pixelArt/deleteTemplate {#tools-pixelart-deletetemplate}

```lua
pixelArt.deleteTemplate(opts: PersistDeleteTemplate) -> boolean
```

**Parameters**

- `opts` `PersistDeleteTemplate`

**Returns** `boolean`

## tools/pixelArt/destroy {#tools-pixelart-destroy}

```lua
pixelArt.destroy(target: Handle | string | number)
```

Despawn a pixel canvas. `target` is a handle, entity id, or name.

**Parameters**

- `target` `Handle | string | number`

```lua
"hero"
```

## tools/pixelArt/draw {#tools-pixelart-draw}

```lua
pixelArt.draw(opts: DrawOpts)
```

Raster drawing onto a canvas's ACTIVE frame. Pick the primitive via `opts.op`. All ops take `target` (handle/id/name) + `color` (palette name; nil clears) plus op-specific coords.

**Parameters**

- `opts` `DrawOpts`

```lua
{ op = 'fillCircle', target = s, cx = 8, cy = 8, r = 6, color = 'red' }
{ op = 'rows', target = s, rows = {'rr','rr'}, legend = { r = 'red' } }
```

## tools/pixelArt/duplicateFrame {#tools-pixelart-duplicateframe}

```lua
pixelArt.duplicateFrame(opts: AnimDuplicateFrame) -> number?
```

**Parameters**

- `opts` `AnimDuplicateFrame`

**Returns** `number?`

## tools/pixelArt/ellipse {#tools-pixelart-ellipse}

```lua
pixelArt.ellipse(opts: DrawEllipse)
```

**Parameters**

- `opts` `DrawEllipse`

## tools/pixelArt/fillCircle {#tools-pixelart-fillcircle}

```lua
pixelArt.fillCircle(opts: DrawFillCircle)
```

**Parameters**

- `opts` `DrawFillCircle`

## tools/pixelArt/fillRect {#tools-pixelart-fillrect}

```lua
pixelArt.fillRect(opts: DrawFillRect)
```

**Parameters**

- `opts` `DrawFillRect`

## tools/pixelArt/frameCount {#tools-pixelart-framecount}

```lua
pixelArt.frameCount(opts: QueryFrameCount) -> number
```

**Parameters**

- `opts` `QueryFrameCount`

**Returns** `number`

## tools/pixelArt/getPixel {#tools-pixelart-getpixel}

```lua
pixelArt.getPixel(opts: QueryGetPixel) -> string?
```

**Parameters**

- `opts` `QueryGetPixel`

**Returns** `string?`

## tools/pixelArt/line {#tools-pixelart-line}

```lua
pixelArt.line(opts: DrawLine)
```

**Parameters**

- `opts` `DrawLine`

## tools/pixelArt/listTemplates {#tools-pixelart-listtemplates}

```lua
pixelArt.listTemplates(_opts: QueryListTemplates) -> { string }
```

**Parameters**

- `_opts` `QueryListTemplates`

**Returns** `{ string }`

## tools/pixelArt/palette {#tools-pixelart-palette}

```lua
pixelArt.palette(opts: PaletteOpts)
```

Palette editing. `op='set'` replaces the whole palette (cell indices preserved, so it recolors every frame in place); `op='add'` adds/updates one entry.

**Parameters**

- `opts` `PaletteOpts`

```lua
{ op = 'add', target = s, name = 'blue', color = {0,0,1} }
```

## tools/pixelArt/persist {#tools-pixelart-persist}

```lua
pixelArt.persist(opts: PersistOpts) -> string | boolean
```

Template persistence. `op='saveAsTemplate'` exports a canvas (all frames) as a reusable `pixelTemplate` asset; `op='deleteTemplate'` removes one. Instantiate saved templates with `Pixel.spawn(op='template')`.

**Parameters**

- `opts` `PersistOpts`

**Returns** `string | boolean`

```lua
{ op = 'saveAsTemplate', target = s, name = 'hero' }
{ op = 'deleteTemplate', name = 'hero' }
```

## tools/pixelArt/pixel {#tools-pixelart-pixel}

```lua
pixelArt.pixel(opts: DrawPixel)
```

**Parameters**

- `opts` `DrawPixel`

## tools/pixelArt/play {#tools-pixelart-play}

```lua
pixelArt.play(opts: AnimPlay)
```

**Parameters**

- `opts` `AnimPlay`

## tools/pixelArt/query {#tools-pixelart-query}

```lua
pixelArt.query(opts: QueryOpts) -> any
```

Read-only queries against a canvas (or the template library). Pick via `opts.op`. Per-canvas ops take `target` (handle/id/name).

**Parameters**

- `opts` `QueryOpts`

**Returns** `any`

```lua
{ op = 'getPixel', target = s, x = 0, y = 0 }
{ op = 'listTemplates' }
```

## tools/pixelArt/rect {#tools-pixelart-rect}

```lua
pixelArt.rect(opts: DrawRect)
```

**Parameters**

- `opts` `DrawRect`

## tools/pixelArt/removeFrame {#tools-pixelart-removeframe}

```lua
pixelArt.removeFrame(opts: AnimRemoveFrame)
```

**Parameters**

- `opts` `AnimRemoveFrame`

## tools/pixelArt/rows {#tools-pixelart-rows}

```lua
pixelArt.rows(opts: SpawnRows) -> Handle
```

**Parameters**

- `opts` `SpawnRows`

**Returns** `Handle`

## tools/pixelArt/saveAsTemplate {#tools-pixelart-saveastemplate}

```lua
pixelArt.saveAsTemplate(opts: PersistSaveAsTemplate) -> string
```

**Parameters**

- `opts` `PersistSaveAsTemplate`

**Returns** `string`

## tools/pixelArt/set {#tools-pixelart-set}

```lua
pixelArt.set(opts: PaletteSet)
```

**Parameters**

- `opts` `PaletteSet`

## tools/pixelArt/setActiveFrame {#tools-pixelart-setactiveframe}

```lua
pixelArt.setActiveFrame(opts: AnimSetActiveFrame)
```

**Parameters**

- `opts` `AnimSetActiveFrame`

## tools/pixelArt/setFps {#tools-pixelart-setfps}

```lua
pixelArt.setFps(opts: AnimSetFps)
```

**Parameters**

- `opts` `AnimSetFps`

## tools/pixelArt/spawn {#tools-pixelart-spawn}

```lua
pixelArt.spawn(opts: SpawnOpts) -> Handle
```

Generic pixel-canvas creation + placement. Pick behavior via `opts.op`: 'create' (blank/seeded sheet), 'rows' (paint from text + a legend), 'template' (instantiate a saved pixelTemplate), or 'clone' (duplicate an existing canvas). Returns a handle.

**Parameters**

- `opts` `SpawnOpts`

**Returns** `Handle`

```lua
{ op = 'create', width = 16, height = 16, palette = { red = {1,0,0} } }
{ op = 'rows', rows = {" r ","rrr"," r "}, legend = { r = "red" }, palette = { red = {1,0,0} } }
{ op = 'template', name = 'hero', position = { 0, 1, 0 } }
```

## tools/pixelArt/stop {#tools-pixelart-stop}

```lua
pixelArt.stop(opts: AnimStop)
```

**Parameters**

- `opts` `AnimStop`

## tools/pixelArt/template {#tools-pixelart-template}

```lua
pixelArt.template(opts: SpawnTemplate) -> Handle
```

**Parameters**

- `opts` `SpawnTemplate`

**Returns** `Handle`

## tools/pp/add {#tools-pp-add}

```lua
pp.add(name: string, opts?: PostEffectOpts) -> string
```

Add a post-processing effect. Use a built-in preset name (`bloom`, `color_correction`, `colorGrade`, `fog`, `grayscale`, `invert`, `lut`, `sepia`, `tonemap`, `vignette`) or provide custom WGSL via `opts.source` (author only `fn fragment(in: PostInput) -> vec4<f32>`, declare named material properties via `opts.properties`). Forwards to `postprocess.add(name, source, { properties, priority, enabled, layer })`, applies each property value via `postprocess.setProperty(name, prop, value)`, and binds each texture via `postprocess.setTexture(name, prop, path)`. Every property the effect's schema declares is settable by its own name in this call — the schema is what decides which keys `opts` takes, so a key it does not declare is refused against that set rather than dropped. `postprocess.describe(name)` lists the schema of a registered effect and the value each property holds; `asset.resolve("@builtin::shaders.post.<preset>", "shader"):getProperties()` lists a preset's before it is added. `opts.layer` picks which composited image the effect grades: `"scene"` runs it before the UI is drawn, so it grades the rendered picture and leaves every widget on screen as authored, and `"all"` (the default) runs it after the UI has landed, so the interface is graded along with the picture. A custom `opts.source` reads the scene's depth at a pixel with `zero_scene_depth(in.uv)`, which a defocus, a distance haze or a depth-keyed grade drives from.

**Parameters**

- `name` `string`
- `opts` `PostEffectOpts` _(optional)_

**Returns** `string`

```lua
'vignette', { intensity = 0.42, radius = 0.8, softness = 0.55 }
'bloom', { threshold = 1.0, intensity = 1.5, radius = 12 }
'color_correction', { brightness = 0.1, saturation = 0.3 }
'tonemap', { mode = 'ACES', exposure = 1.2 }
'lut', { texture = lut.install('dusk', lut.fromGrade({ temperature = -0.2 })).guid }
'myeffect', { source = 'fn fragment(in: PostInput) -> vec4<f32> {...}', properties = {{name='k', type='float', default={1}}}, k = 0.25 }
```

## tools/pp/list {#tools-pp-list}

```lua
pp.list() -> { string }
```

List the currently-active post-processing effects. Forwards to `postprocess.list()`.

**Returns** `{ string }`

## tools/pp/presets {#tools-pp-presets}

```lua
pp.presets() -> {string}
```

List the built-in preset effect names sorted alphabetically. The same names are accepted by `pp.add`.

**Returns** `{string}`

## tools/pp/remove {#tools-pp-remove}

```lua
pp.remove(name: string)
```

Remove a post-processing effect by name. Forwards to `postprocess.remove(name)`. Removing an effect that does not exist follows the underlying API's contract.

**Parameters**

- `name` `string`

```lua
'vignette'
```

## tools/pp/setEnabled {#tools-pp-setenabled}

```lua
pp.setEnabled(name: string, enabled: boolean)
```

Enable or disable a post-processing effect without removing it. Forwards to `postprocess.setEnabled(name, enabled)`.

**Parameters**

- `name` `string`
- `enabled` `boolean`

```lua
'bloom', false
```

## tools/primitives/cube {#tools-primitives-cube}

```lua
primitives.cube(name: SpawnTarget, x?: number, y?: number, z?: number, opts?: CubeOpts) -> string
```

Spawn a visual cube at a position. No physics by default — add `Physics` and `Collider` components explicitly if collision is wanted. Set `opts.scale` to size the cube — a number for a uniform cube, or three components as `{x, y, z}` or `{ x = , y = , z = }` for a box. Set `opts.animate=true` to play a viz.popIn entrance (default off — at bulk-spawn rates the per-call string.format + tween-source compile dominates spawn time).

**Parameters**

- `name` `SpawnTarget`
- `x` `number` _(optional)_
- `y` `number` _(optional)_
- `z` `number` _(optional)_
- `opts` `CubeOpts` _(optional)_

**Returns** `string`

```lua
"box1", 0, 5, 0
{ name = "crate", position = { 4, 0, 2 } }
"backdrop", 0, 0, -10, { scale = 5 }
"beam", 0, 1, 0, { scale = { 4, 0.4, 0.4 } }
"falling", 0, 5, 0
```

## tools/primitives/ground {#tools-primitives-ground}

```lua
primitives.ground(name: SpawnTarget, x?: number, y?: number, z?: number, color?: shared.ColorInput, size?: number) -> string
```

Spawn a flat ground plane with optional color tint and size. The base mesh is 1x0.1x1 unit; `size` scales it uniformly in XZ. Always has static physics (it's a ground plane).

**Parameters**

- `name` `SpawnTarget`
- `x` `number` _(optional)_
- `y` `number` _(optional)_
- `z` `number` _(optional)_
- `color` `shared.ColorInput` _(optional)_
- `size` `number` _(optional)_

**Returns** `string`

```lua
"floor", 0, 0, 0
{ name = "floor", position = { 0, -1, 0 } }
"red_floor", 0, 0, 0, {r=200, g=50, b=50}
"blue_floor", 0, 0, 0, {50, 90, 200}
"big_floor", 0, 0, 0, nil, 100
```

## tools/primitives/groundHex {#tools-primitives-groundhex}

```lua
primitives.groundHex(name: SpawnTarget, x?: number, y?: number, z?: number, color?: shared.ColorInput) -> string
```

Spawn a hexagonal ground tile with optional color tint. Includes static physics so objects rest on it.

**Parameters**

- `name` `SpawnTarget`
- `x` `number` _(optional)_
- `y` `number` _(optional)_
- `z` `number` _(optional)_
- `color` `shared.ColorInput` _(optional)_

**Returns** `string`

```lua
"hex_tile", 0, 0, 0
{ name = "hex_tile", position = { 3, 0, 3 } }
"grass_hex", 0, 0, 0, {r=80, g=160, b=60}
"sand_hex", 0, 0, 0, {200, 180, 120}
```

## tools/primitives/tree {#tools-primitives-tree}

```lua
primitives.tree(name: SpawnTarget, x?: number, y?: number, z?: number, preset?: TreePreset, seed?: number) -> string
```

Spawn a procedural tree from a preset. Includes static physics collider so objects collide with it.

**Parameters**

- `name` `SpawnTarget`
- `x` `number` _(optional)_
- `y` `number` _(optional)_
- `z` `number` _(optional)_
- `preset` `TreePreset` _(optional)_
- `seed` `number` _(optional)_

**Returns** `string`

```lua
"oak1", 5, 0, 3, "oak"
{ name = "oak1", position = { 5, 0, 3 } }, nil, nil, nil, "oak"
"tree_" .. i, math.random(-20, 20), 0, math.random(-20, 20), "pine", i * 42
```

## tools/procgen/adjust {#tools-procgen-adjust}

```lua
procgen.adjust(args: AdjustArgs) -> AdjustResult
```

Adjust a live generator in place: merge values into its param overrides (each key overrides the graph input of the same name), switch which output it realizes, or toggle autoBake. The change queues a cook on the generator's async runner and returns immediately — a heavy graph keeps cooking across frames while this call reports back. Watch it with `procgen.cooks`, cancel it with `procgen.cancel`.

**Parameters**

- `args` `AdjustArgs`

**Returns** `AdjustResult`

```lua
{ entity = "LegoSculpture", params = { model = "@builtin::meshes.torus" } }
```

## tools/procgen/apply {#tools-procgen-apply}

```lua
procgen.apply(args: ApplyArgs) -> ApplyResult
```

Flatten a live generator and detach it: the current output is written as durable content (real mesh assets), spawned as permanent children, and the Generator component is removed — the result stands on its own with no graph and no generator at runtime, like applying a modifier.

**Parameters**

- `args` `ApplyArgs`

**Returns** `ApplyResult`

```lua
{ entity = "LegoSculpture" }
```

## tools/procgen/bake {#tools-procgen-bake}

```lua
procgen.bake(args: BakeArgs) -> BakeResult
```

Evaluate a `.procGraph` graph target and realize it into the live scene as durable content, then verify it rendered. Geometry becomes a `.mesh` asset + a Model entity; an InstanceSet becomes live per-instance entities; a Bundle/Prefab becomes durable live entities that survive the edit->play reload. Pass `replace` to swap an existing entity in place (inheriting its position); `position` to place it; `material` to tint a Geometry bake.

**Parameters**

- `args` `BakeArgs`

**Returns** `BakeResult`

```lua
{ graph = "/source/proc/tower.procGraph", name = "tower", position = { 0, 0, 0 } }
```

## tools/procgen/cancel {#tools-procgen-cancel}

```lua
procgen.cancel(args: CancelArgs) -> CancelResult
```

Cancel the in-flight cook on an entity's Generator. The previous output stays; the next param change cooks fresh.

**Parameters**

- `args` `CancelArgs`

**Returns** `CancelResult`

```lua
{ entity = "LegoSculpture" }
```

## tools/procgen/collapse {#tools-procgen-collapse}

```lua
procgen.collapse(args: CollapseArgs) -> CollapseResult
```

Extract `nodes` out of a `.procGraph` into a new `.procGraph` named `name`, written as its own `init.luau` — a graph to keep editing. Every ref crossing the extracted cluster's boundary becomes a declared child input (outside producer -> extracted node) or child output (extracted node -> outside consumer); identical boundary sources/sinks share one socket. The host graph is left as it was; `procgen.reference` is the call that rewrites its source to use the child, and this returns the exact one to make.

**Parameters**

- `args` `CollapseArgs`

**Returns** `CollapseResult`

```lua
{ graph = "/source/proc/scene.procGraph", nodes = { "pts", "inst" }, name = "Scatter" }
```

## tools/procgen/cooks {#tools-procgen-cooks}

```lua
procgen.cooks() -> CooksResult
```

List the active cooks and the recent cook history — progress, status, and failure detail (which node failed). A finished record's `durationMs` spans the whole regen: evaluation plus realizing the output and refreshing the bake snapshot. The scene-wide cook monitor.

**Returns** `CooksResult`

## tools/procgen/diff {#tools-procgen-diff}

```lua
procgen.diff(args: DiffArgs) -> DiffResult
```

Compare two `.procGraph` graphs and report changed/added/removed nodes plus per-node op/param/input deltas (structural, via the content hash).

**Parameters**

- `args` `DiffArgs`

**Returns** `DiffResult`

```lua
{ a = "/source/proc/box.procGraph", b = updatedGraph }
```

## tools/procgen/explain {#tools-procgen-explain}

```lua
procgen.explain(args: ExplainArgs) -> ExplainResult
```

Agent-oriented natural-language summary of a `.procGraph` graph: its size, declared inputs (with types), the op of each node, the composite references, and where each output comes from.

**Parameters**

- `args` `ExplainArgs`

**Returns** `ExplainResult`

```lua
{ graph = "/source/proc/box.procGraph" }
```

## tools/procgen/find {#tools-procgen-find}

```lua
procgen.find(args?: FindArgs) -> FindResult
```

Discover procedural graphs by what they produce. Lists every `.procGraph` asset, opens each, reads its declared outputs' types, and filters on `name` (substring over name/id), `tag`, `category`, and/or `produces` (an output type or producing-op substring, e.g. "Bundle", "Geometry", "mesh."). Returns each match with its resolved outputs — so you can find a graph by its result, not just its description.

**Parameters**

- `args` `FindArgs` _(optional)_

**Returns** `FindResult`

```lua
{ produces = "Bundle" }
```

## tools/procgen/flatten {#tools-procgen-flatten}

```lua
procgen.flatten(args: FlattenArgs) -> FlattenResult
```

Inline every composite of a `.procGraph` graph into a single composite-free graph (the opt-in bake). Child nodes are namespaced under the composite id, inputs rewired to the bound values, composite outputs repointed at the inlined child outputs. Nested composites flatten recursively; cross-graph cycles error with the full path chain.

**Parameters**

- `args` `FlattenArgs`

**Returns** `FlattenResult`

```lua
{ graph = "/source/proc/bank_vault.procGraph", out = "/source/proc/bank_vault_flat.procGraph" }
```

## tools/procgen/generators {#tools-procgen-generators}

```lua
procgen.generators() -> GeneratorsResult
```

List every live procedural generator in the scene — the entities carrying a Generator component — with each one's bound graph, selected output, current param overrides, realized child count, and cook state (whether it's cooking right now, its progress, and its last finished cook's outcome). Use it to find what's generating — or why a generator's output looks stale — before adjusting (`procgen.adjust`) or flattening (`procgen.apply`).

**Returns** `GeneratorsResult`

## tools/procgen/inspect {#tools-procgen-inspect}

```lua
procgen.inspect(args: InspectArgs) -> InspectResult
```

Inspect one node of a `.procGraph` graph: its op, declared input/param/output sockets (from the registry), the InputRefs feeding it (producers), and the nodes that consume its outputs (consumers).

**Parameters**

- `args` `InspectArgs`

**Returns** `InspectResult`

```lua
{ graph = g, node = "box" }
```

## tools/procgen/modify {#tools-procgen-modify}

```lua
procgen.modify(args: ModifyArgs) -> ModifyResult
```

Apply structural edits to a `.procGraph` graph, type-check the result, and persist it — a step above `procgen.patch`. Each edit is a `{ op }` table: `{ op = "add_node", id, node }` (node = the op id, e.g. "mesh.box"); `{ op = "set_param", node, name, value }`; `{ op = "connect", node, socket, from }` (from = `{ node, output? }` for a   node output, else a constant); `{ op = "set_output", name, from }` (declare/rebind a graph output); `{ op = "rename_node", from, to }` (updates every reference); `{ op = "remove_node", node }`.

**Parameters**

- `args` `ModifyArgs`

**Returns** `ModifyResult`

```lua
{ graph = "/source/proc/tower.procGraph", edits = { { op = "add_node", id = "n", node = "mesh.sphere" }, { op = "set_output", name = "mesh", from = { node = "n" } } } }
```

## tools/procgen/ops {#tools-procgen-ops}

```lua
procgen.ops(query?: string, pack?: string, kind?: string, limit?: number) -> OpsResult
```

Search the procedural OP REGISTRY — every operation a graph node can name. Every op belongs to a pack: the families this package ships, the `terrain.*` ops, and any `.procPack` authored in a world, all listed the same way; a `.procNode` asset contributes one op addressed by its guid. Filter by free-text `query` over id and description, by the `pack` an op came from, or by `kind`. With no arguments it answers for the whole vocabulary. Every hit's `id` is what `procgen.schema` describes and what a node's `op` field takes, so this is the first call when composing a graph and the one that settles whether an op you remember actually exists. The answer comes in two halves: `catalogue` names EVERY matching op grouped by family, complete however many there are and small enough to read whole, and `results` describes the first `limit` of them. So one call maps the vocabulary and a second, narrowed by `query`, describes the corner you want.

**Parameters**

- `query` `string` _(optional)_
- `pack` `string` _(optional)_
- `kind` `string` _(optional)_
- `limit` `number` _(optional)_

**Returns** `OpsResult`

```lua
"scatter"
nil, nil, nil, 60
```

## tools/procgen/patch {#tools-procgen-patch}

```lua
procgen.patch(args: PatchArgs) -> PatchResult
```

Apply a list of structured edits to a `.procGraph` graph, then optionally save it. Supported edits (each a table with `op`): `{ op = "set_param", node, name, value }` — set a node param; `{ op = "set_input", node, socket, value }` — rewire an input (value =    `{ node = "<id>", output? }` for a node output, else a constant); `{ op = "remove_node", node }` — delete a node.

**Parameters**

- `args` `PatchArgs`

**Returns** `PatchResult`

```lua
{ graph = "/source/proc/box.procGraph", edits = { { op = "set_param", node = "box", name = "size", value = 3 } } }
```

## tools/procgen/preview {#tools-procgen-preview}

```lua
procgen.preview(args: PreviewArgs) -> PreviewResult
```

Evaluate a `.procGraph` graph target, visualize it (any output type), render it, and write image + stats artifacts under /source/tmp/proc-preview/<graph>/latest/.

**Parameters**

- `args` `PreviewArgs`

**Returns** `PreviewResult`

```lua
{ graph = "/source/proc/box.procGraph", target = "output:mesh" }
{ graph = "/source/proc/valley.procGraph", target = "node:snowBand", layer = "snow" }
```

## tools/procgen/promote {#tools-procgen-promote}

```lua
procgen.promote(args: PromoteArgs) -> PromoteResult
```

Publish the `.procGraph` at `graph` as an OPERATION — a `.procNode` bound to it, which puts the graph in the op registry where `ops` searches and any graph can reach it by name. Its params are the graph's declared inputs and its outputs are the graph's declared outputs, and it stays bound: editing the graph changes the op, and every graph using it re-cooks. A graph referenced directly as a node is private to whatever names it; an op is part of the shared vocabulary. Promote the graphs worth reusing.

**Parameters**

- `args` `PromoteArgs`

**Returns** `PromoteResult`

```lua
{ graph = "/source/proc/Scatter.procGraph" }
```

## tools/procgen/reference {#tools-procgen-reference}

```lua
procgen.reference(args: ReferenceArgs) -> ReferenceResult
```

Replace `nodes` in the `.procGraph` at `graph` with a single node that references the `.procGraph` at `child`, and write the host back out as source. The host's boundary is derived exactly as `collapse` derives it, so the graph evaluates to the same result through the child that it did through the nodes — and because the host's `init.luau` is what the type compiles, this is the step that makes the change the host's actual definition. The host's source is REGENERATED from its graph: every node, wire and declared input survives, and comments in the file do not.

**Parameters**

- `args` `ReferenceArgs`

**Returns** `ReferenceResult`

```lua
{ graph = "/source/proc/scene.procGraph", child = "/source/proc/Scatter.procGraph", nodes = { "pts", "inst" } }
```

## tools/procgen/schema {#tools-procgen-schema}

```lua
procgen.schema(id: string) -> any
```

Describe ONE procedural op: the input sockets it consumes and their types, the params it takes with their declared defaults and units, and the outputs it produces (with the one a wire picks by default). Accepts a builtin op id or a content op's guid, and describes a `.procNode` exactly as it describes a builtin. This is the call that settles what a node needs before it is wired — an op named from memory, or wired with a param it does not declare, is the usual reason a graph refuses to cook. Find the id with `procgen.ops`.

**Parameters**

- `id` `string`

**Returns** `any`

```lua
"terrain.erode"
```

## tools/procgen/search {#tools-procgen-search}

```lua
procgen.search(args?: SearchArgs) -> SearchResult
```

Search the procedural-graph library — every `.procGraph` this engine can see, the ones the world authored and the ones it ships. A free-text `query` matches the graph's name, its identity, the description its `.metadata` carries and its tags, so the word you would use for the thing finds it even when the graph is named something else. `tag` and `category` narrow it. Ask this before describing something from nothing: a graph that already makes what you need is two calls away, and rewriting one costs the work twice over.

**Parameters**

- `args` `SearchArgs` _(optional)_

**Returns** `SearchResult`

```lua
{ query = "scatter" }
{ tag = "terrain" }
```

## tools/procgen/spawn {#tools-procgen-spawn}

```lua
procgen.spawn(args: SpawnArgs) -> SpawnResult
```

Spawn a live procedural generator from a `.procGraph` graph: creates an entity with a Generator component bound to the graph, which immediately realizes the graph's output as the entity's managed children and keeps regenerating live as params change. This is the entry point for putting a graph in the scene — no manual entity.spawn / component.add needed.

**Parameters**

- `args` `SpawnArgs`

**Returns** `SpawnResult`

```lua
{ graph = "/source/legoify.procGraph", name = "LegoSculpture", position = { 0, 3, 0 } }
```

## tools/procgen/stats {#tools-procgen-stats}

```lua
procgen.stats(args: StatsArgs) -> StatsResult
```

Structural counts of a `.procGraph` graph (nodes / composites / inputs / outputs). When `target` is given, also evaluates it and reports the output type, elapsed duration, and (for geometry) vertex/triangle counts. Timing is reported only — it never feeds graph evaluation (determinism).

**Parameters**

- `args` `StatsArgs`

**Returns** `StatsResult`

```lua
{ graph = g, target = "output:mesh" }
```

## tools/procgen/trace {#tools-procgen-trace}

```lua
procgen.trace(args: TraceArgs) -> TraceResult
```

Dependency chain for a target: the node ids reachable from the target, in evaluation order (each node's producers appear before it — a post-order walk of the `node_output` edges). Composite/const/graph_input leaves stop the walk.

**Parameters**

- `args` `TraceArgs`

**Returns** `TraceResult`

```lua
{ graph = g, target = "output:mesh" }
```

## tools/procgen/tweak {#tools-procgen-tweak}

```lua
procgen.tweak(args: TweakArgs) -> TweakResult
```

Set one or more node params on a `.procGraph` graph, then re-evaluate + preview in a single step — the parameter-tuning loop. Each entry of `params` is `{ node = "<id>", name = "<param>", value = <any> }`. Persists the tweaked graph when `out` is given (else the edit is in-memory for this preview only).

**Parameters**

- `args` `TweakArgs`

**Returns** `TweakResult`

```lua
{ graph = "/source/proc/tower.procGraph", params = { { node = "floors", name = "count", value = 12 } } }
```

## tools/procgen/validate {#tools-procgen-validate}

```lua
procgen.validate(args: ValidateArgs) -> ValidateResult
```

Type-check a `.procGraph` graph: missing inputs, type mismatches, cycles, invalid composite bindings. Returns the diagnostics list.

**Parameters**

- `args` `ValidateArgs`

**Returns** `ValidateResult`

```lua
{ graph = "/source/proc/box.procGraph" }
```

## tools/profiler/compare {#tools-profiler-compare}

```lua
profiler.compare(before?: string, after?: string) -> string
```

Diff two recordings (made with `record`): the change in avg/p90 frametime, and the systems that moved the most between them. Record a baseline, make a change, record again, then compare to see whether the change helped and which system it moved. A positive frametime delta means B is slower than A.

**Parameters**

- `before` `string` _(optional)_
- `after` `string` _(optional)_

**Returns** `string`

```lua
"baseline", "optimized"  -- did 'optimized' beat 'baseline', and where
```

## tools/profiler/flamegraph {#tools-profiler-flamegraph}

```lua
profiler.flamegraph(seconds?: number, mode?: ("run" | "start" | "stop" | "snapshot"), label?: string) -> string
```

Sample the Luau call stack to find the hottest code paths — the depth view under a hot component or `task_scheduler`. `flamegraph(seconds)` runs the sampler for that long (resetting first) and returns the top stacks plus a folded-stack file for flamegraph rendering. `mode` picks a manual phase instead: "start" / "stop" a long session, or "snapshot" the current top stacks without stopping.

**Parameters**

- `seconds` `number` _(optional)_
- `mode` `("run" | "start" | "stop" | "snapshot")` _(optional)_
- `label` `string` _(optional)_

**Returns** `string`

```lua
-- 3s sample, top stacks + folded file
5          -- 5s sample
0, "snapshot" -- top stacks right now without stopping a session
```

## tools/profiler/frame {#tools-profiler-frame}

```lua
profiler.frame(source?: string, which?: ("worst" | "typical"), minMs?: number) -> string
```

The current frame's time as a self-accounting tree: schedule -> system -> sub-timing, each row showing SELF (own time excluding children), TOTAL, calls, and % of frame. Time no schedule covers (the frame limiter / vsync wait, GPU present, event loop) shows as `present / idle`; each expensive component's update(dt) is listed by component + entity under `lua_update.vm_call`. Read the live frame with source "now", or a stopped recording by its label (its worst or typical frame). Read from top: the row with the largest SELF time is where the frame time actually goes.

**Parameters**

- `source` `string` _(optional)_
- `which` `("worst" | "typical")` _(optional)_
- `minMs` `number` _(optional)_

**Returns** `string`

```lua
-- live frame, full attributed tree
"combat", "worst"     -- the worst frame of the 'combat' recording
"combat", "typical"   -- the typical (average) frame of that recording
```

## tools/profiler/gpu {#tools-profiler-gpu}

```lua
profiler.gpu(n?: number) -> string
```

GPU pass timings over the last resolved frames, from GPU timestamp queries. Each row is one label: the median of its per-frame total, the min/max that median sits in, the passes per frame, and how many of the window's frames carried it. A row marked `floor` ran but the device resolved no duration for it — its two timestamps retired within a few ticks of each other. The `last` column is how many resolved frames ago the label last recorded a pass: `now` is a pass running in the frame the table describes, and anything else is a row the window still holds after the work under it stopped. The readback is asynchronous, so the window lags the live frame by a few frames. Use this when `frame` shows the time under `present / idle` (GPU-bound) and you need to know which passes the GPU spends it on.

**Parameters**

- `n` `number` _(optional)_

**Returns** `string`

```lua
-- top 20 GPU spans of the window
40     -- top 40
```

## tools/profiler/hits {#tools-profiler-hits}

```lua
profiler.hits(label?: string, spikeMs?: number) -> string
```

Drain the frames a record-mode `watch` caught and report them as a spike-cluster distribution — every caught frame grouped by its dominant hotspot, so repeated hitches collapse to their handful of causes instead of a wall of individual frames. Draining clears the buffer. The snapshot is stored under `label` — drill into any cluster with `frame <label>` / `hotspots <label>`. Arm the watchdog first with `watch <ceilingMs>`.

**Parameters**

- `label` `string` _(optional)_
- `spikeMs` `number` _(optional)_

**Returns** `string`

```lua
-- review everything the watchdog caught
"collapse_hits"    -- store under a name for later drill-down
```

## tools/profiler/hotspots {#tools-profiler-hotspots}

```lua
profiler.hotspots(n?: number, source?: string, which?: ("worst" | "typical")) -> string
```

Rank the frame's costs by SELF time (a node's own cost, excluding its children) and return the top `n` as a flat table. Because it ranks by SELF, the top rows are the actual expensive leaves — a system's own work or a single heavy component's update(dt) — not the schedules that merely contain them. This is the "just tell me what's slow" tool; follow a hit down with `frame`. Reads the live frame ("now") or a stopped recording's worst/typical frame (pass its label).

**Parameters**

- `n` `number` _(optional)_
- `source` `string` _(optional)_
- `which` `("worst" | "typical")` _(optional)_

**Returns** `string`

```lua
-- top 12 costs in the live frame
20           -- top 20
12, "combat" -- top 12 in the worst frame of the 'combat' recording
```

## tools/profiler/memory {#tools-profiler-memory}

```lua
profiler.memory(n?: number) -> string
```

The Luau VM's memory: total heap + GC state, then the components retaining the most memory (per instance). Use it to catch a growing script — take it, play/test, take it again, and watch which component's retained bytes climb. Complements the frame-time tools: memory pressure shows up as GC cost in `frame` (the `gc` node) and as crashes under load, not as one slow system.

**Parameters**

- `n` `number` _(optional)_

**Returns** `string`

```lua
-- VM total + top 20 components by retained memory
10    -- top 10
```

## tools/profiler/record {#tools-profiler-record}

```lua
profiler.record(action?: ("start" | "stop" | "status"), label?: string, spikeMs?: number) -> string
```

Start / stop / check a background profiling recording that spans a play session. `record("start", label)` begins capturing every frame; play or test the game across as many turns as you want, then `record("stop", label)` returns the windowed breakdown: avg / p50 / p90 / p99 / max frametime, spike count, the single WORST frame's attributed tree, and a typical-frame tree. `record("status")` reports whether a recording is running. A stopped recording is kept under its label — analyse it later with `frame`, `hotspots`, or `scripts` (pass the label as their source), or diff two of them with `compare`.

**Parameters**

- `action` `("start" | "stop" | "status")` _(optional)_
- `label` `string` _(optional)_
- `spikeMs` `number` _(optional)_

**Returns** `string`

```lua
"start", "combat"      -- begin recording a combat encounter
"stop", "combat"       -- end it, get the windowed breakdown
"status"               -- is a recording running right now?
```

## tools/profiler/retro {#tools-profiler-retro}

```lua
profiler.retro(seconds?: number, label?: string, spikeMs?: number) -> string
```

Retroactively read the ring buffer's last `seconds` of frames (default: the whole ring) as a spike-cluster report — the latency-immune profiler. Enable the ring first (`ring on`), drive the scene, then call this AFTER the spike: the data is historical, so your call's timing does not matter. The report groups every spike frame by its dominant hotspot (so one call shows the full distribution of what's slow, not one anecdote), on EFFECTIVE frame time (your own `execute` cost excluded). The snapshot is stored under `label` — drill into any cluster with `frame <label>` / `hotspots <label>`.

**Parameters**

- `seconds` `number` _(optional)_
- `label` `string` _(optional)_
- `spikeMs` `number` _(optional)_

**Returns** `string`

```lua
-- the whole ring, clustered
8               -- just the last 8 seconds
8, "collapse"   -- last 8s, stored as 'collapse' for drill-down
```

## tools/profiler/ring {#tools-profiler-ring}

```lua
profiler.ring(action?: ("on" | "off" | "status"), seconds?: number) -> string
```

Control the retroactive ring buffer — an always-recording, bounded history of the last N seconds of per-frame data you query AFTER the fact with `retro`. `ring("on", seconds)` starts it (default 20s); `ring("off")` stops and clears it; `ring("status")` reports whether it's on, how many frames and seconds it holds. Editor profile only — a no-op in the runtime profile. The ring is off until you turn it on, so it costs nothing until then. This is the fix for "I can't profile a spike I only see afterwards".

**Parameters**

- `action` `("on" | "off" | "status")` _(optional)_
- `seconds` `number` _(optional)_

**Returns** `string`

```lua
"on", 30      -- keep the last 30 seconds, always
"status"      -- is the ring on? how much does it hold?
"off"         -- stop recording and clear the history
```

## tools/profiler/scripts {#tools-profiler-scripts}

```lua
profiler.scripts(n?: number, minMs?: number, type_?: string) -> string
```

Rank components by update(dt) cost — the content view of where the frame's script time goes. Rolled up per component type by default (many instances of a type collapse to `Type xN` with summed cost; a lone instance keeps its `@ entity`), so it stays readable whether a world has three scripts or three hundred. Pass a `type` to drill into that one type's individual instances (which entity is the heavy one). Use it after `hotspots`/`frame` point at `lua_update.vm_call`. Counts COMPONENT update loops; a scene entrypoint's per-frame `update` / `editorUpdate` runs on the scheduler, and `scene.cost` ranks the loaded scenes by what theirs costs.

**Parameters**

- `n` `number` _(optional)_
- `minMs` `number` _(optional)_
- `type_` `string` _(optional)_

**Returns** `string`

```lua
-- every component type, heaviest first
10       -- the 10 heaviest types
20, 0.5, "MyMover" -- instances of MyMover costing >= 0.5ms, by entity
```

## tools/profiler/tasks {#tools-profiler-tasks}

```lua
profiler.tasks(n?: number, minMs?: number) -> string
```

Rank components by the time the scheduler spent resuming their coroutines this frame — the content breakdown of `task_scheduler`. Rolled up per component type (many instances collapse to `Type xN`; a lone instance keeps its `@ entity`). Use this when `frame`/`hotspots` show `task_scheduler` hot and you need to know whose `task.spawn` / `task.wait` work is behind it.

**Parameters**

- `n` `number` _(optional)_
- `minMs` `number` _(optional)_

**Returns** `string`

```lua
-- every component's coroutine cost, heaviest first
10       -- the 10 heaviest
```

## tools/profiler/watch {#tools-profiler-watch}

```lua
profiler.watch(ceilingMs?: (number | "off" | "status"), mode?: ("record" | "pause"), excludeAgent?: boolean) -> string
```

Arm a frame-time watchdog that catches bad frames without you having to poll (which always lands seconds late). Call with a number to arm: `watch(50)` records every frame whose EFFECTIVE time (agent cost excluded) is >= 50ms; `watch(50, "pause")` instead pauses gameplay the first time the ceiling is crossed, freezing the bad state for you to inspect (then read it with `retro`). `watch("off")` disarms; `watch("status")` (or no arg) reports state and hit count. Read recorded hits with the `hits` tool. Editor profile only — returns a refusal in the runtime profile. `excludeAgent` (default true) keeps your own `execute`/write frames from tripping it.

**Parameters**

- `ceilingMs` `(number | "off" | "status")` _(optional)_
- `mode` `("record" | "pause")` _(optional)_
- `excludeAgent` `boolean` _(optional)_

**Returns** `string`

```lua
50                 -- record every frame over 50ms effective
50, "pause"        -- pause gameplay the first time a frame exceeds 50ms
"status"           -- armed? how many hits so far?
"off"              -- disarm
```

## tools/renderLayer/camera {#tools-renderlayer-camera}

```lua
renderLayer.camera(targets: Targets, filter?: string) -> { any }
```

Choose which render layers a camera DRAWS, as a layer-NAME filter — the visibility half of the render-layer system, and the way to keep a wall, prop or overlay out of a shot while it still exists for lighting, shadows and physics. A filter is a space-separated spec like `"all"`, `"all !ui"`, or `"all !shell !EditorUI"`, where a bare name includes that layer and a `!name` excludes it; this is the same language `capture` and the `Camera` component already speak. Targets are entities carrying a `Camera` component, resolved by NAME or id, an array of them, `scene.find` records, or a QUERY (`{ component = { "Camera" } }` reaches every camera at once). Call with NO filter to read the current one back. An entity without a `Camera` component is an error. Returns `{ id, name, renderLayers }` per camera, so the write is also the read. Use `renderLayer.set` to put geometry ON a layer — that is the other half; a camera can only exclude a layer something is actually on.

**Parameters**

- `targets` `Targets`
- `filter` `string` _(optional)_

**Returns** `{ any }`

```lua
"mainCamera", "all !shell"
"previewCamera", "all !ui !EditorUI"
{ component = { "Camera" } }, "all !debug"
"mainCamera"
```

## tools/renderLayer/get {#tools-renderlayer-get}

```lua
renderLayer.get(targets: Targets, opts?: GetOpts) -> { any }
```

Read which render layers one or many entities are on — what is actually drawn where, reported as layer NAMES rather than a bitmask. Targets resolve by entity NAME or id, an array of them, `scene.find` records, or a QUERY resolved for you (`{ name = { "wall" } }`, `{ component = { "Camera" } }`, `{ under = { "building" } }`) so you can inspect a selection without looking its ids up first. Pass `tree = true` to read each target AND every descendant, which is how you check a hierarchy a `renderLayer.set` with `tree = true` just wrote. Returns `{ id, name, layers, skipped }` per entity read: `layers` is the array of layer names it carries — an entity nothing has re-layered reports `default` — and `skipped` counts the descendants this read left out, so reading a root of 46 children on its own reports `skipped = 46` rather than reading like the whole building. An unrecognised option key is an error naming the accepted set. Use `renderLayer.set` to change membership, `renderLayer.camera` to see or change which layers a camera draws, and `renderLayer.list` to see every layer that exists and who is on it.

**Parameters**

- `targets` `Targets`
- `opts` `GetOpts` _(optional)_

**Returns** `{ any }`

```lua
"frontWall"
{ "marker_a", "marker_b" }
{ under = { "building" } }
"building", { tree = true }
```

## tools/renderLayer/list {#tools-renderlayer-list}

```lua
renderLayer.list() -> any
```

Show every render layer that exists in this world, which entities are on each, and which cameras include or exclude it — the answer to "what layers do I have" and "why is nothing showing up on this one". Layers are reported in bit order with a `builtin` flag: `default`, `ui`, `debug`, `sky` and `EditorUI` are seeded, everything else interned the first time a name was referenced. This is the tool that makes a MISTYPED layer visible — referencing a name that does not exist CREATES it, so `"shel"` instead of `"shell"` yields a real but empty layer that renders nothing and raises no error, and listing shows it with no members. Returns `{ layers, cameras }` where each layer is `{ name, bit, builtin, entities }` and each camera is `{ id, name, renderLayers }` carrying its name filter. Use `renderLayer.set` to change entity membership and `renderLayer.camera` to change what a camera draws.

**Returns** `any`

## tools/renderLayer/screen {#tools-renderlayer-screen}

```lua
renderLayer.screen(screen: string, layers: LayerNames) -> any
```

Choose which render layers a UI screen DRAWS INTO, by layer NAME — the way to keep a HUD, overlay or editor panel out of a camera's shot or a screenshot while it keeps rendering elsewhere. A screen appears in a camera or capture only when its layers intersect that camera's include filter, the same rule geometry follows. Layers are given by NAME — a single name, an array of names, or a space-separated string like `"debug ui"` — never a bitmask. Content UI sits on `ui` by default and the editor places its chrome on `EditorUI`, which is why a capture asking for `"all !EditorUI"` drops the editor panels and keeps the game HUD. Returns `{ screen, layers }` with the layers the screen now draws into. Use `renderLayer.camera` to choose what a camera renders, `renderLayer.set` for entity membership, and `renderLayer.list` to see every layer that exists.

**Parameters**

- `screen` `string`
- `layers` `LayerNames`

**Returns** `any`

```lua
"hud", "ui"
"debugOverlay", { "debug", "ui" }
```

## tools/renderLayer/set {#tools-renderlayer-set}

```lua
renderLayer.set(targets: Targets, layers: LayerNames, opts?: SetOpts) -> { any }
```

Put one or many entities ON named render layers — the membership half of the render-layer system, and the way to hide a wall, prop or whole building from a camera while it still exists for lighting, shadows and physics. Targets resolve by entity NAME or id, an array of them, `scene.find` records, or a QUERY resolved for you (`{ name = { "wall" } }`, `{ component = { "Light" } }`, `{ under = { "building" } }`) so you can re-layer things without looking their ids up first. Layers are given by NAME — a single name, an array of names, or a space-separated string like `"shell debug"` — never a bitmask. Pass `tree = true` to apply to each target AND every descendant, which is what you want when the thing you are re-layering is a hierarchy rather than a single mesh. Setting layers REPLACES an entity's membership rather than adding to it. An unrecognised option key is an error naming the accepted set, so a mistyped key never reads back as a successful no-op. Referencing a layer name that does not exist yet CREATES it, so a typo yields a real but empty layer that renders nothing and raises no error — `renderLayer.list` shows what actually exists. Returns `{ id, name, layers, moved, skipped }` per TARGET: `layers` is what that entity now carries, `moved` counts the entities this call wrote through it — an entity two targets both span is written and counted once, so the counts sum to the entities the call moved — and `skipped` counts the descendants it left where they were. A root of 46 children re-layered on its own reports `moved = 1, skipped = 46`; the same call with `tree = true` reports `moved = 47, skipped = 0`, so the size of the write is in the reply rather than in a follow-up read. Pair it with `renderLayer.camera`, which chooses which layers a camera draws.

**Parameters**

- `targets` `Targets`
- `layers` `LayerNames`
- `opts` `SetOpts` _(optional)_

**Returns** `{ any }`

```lua
"frontWall", "shell"
{ "marker_a", "marker_b" }, "debug"
{ name = { "wall" } }, "shell"
"building", "shell", { tree = true }
```

## tools/sc/addComponent {#tools-sc-addcomponent}

```lua
sc.addComponent(ids: IdList, typeName: string, data?: ComponentData)
```

Add component to one or many entities.

**Parameters**

- `ids` `IdList`
- `typeName` `string`
- `data` `ComponentData` _(optional)_

```lua
"box", "Physics", { kind = "dynamic" }
ids, "Physics", { kind = "static" }
```

## tools/sc/clear {#tools-sc-clear}

```lua
sc.clear()
```

Clear scene (despawn all non-camera entities).

## tools/sc/clearVisuals {#tools-sc-clearvisuals}

```lua
sc.clearVisuals(ids: IdList)
```

Clear tint + outline from one or many entities.

**Parameters**

- `ids` `IdList`

```lua
"box"
ids
```

## tools/sc/despawn {#tools-sc-despawn}

```lua
sc.despawn(ids: IdList)
```

Despawn one or many entities.

**Parameters**

- `ids` `IdList`

```lua
"box"
ids
```

## tools/sc/move {#tools-sc-move}

```lua
sc.move(ids: IdList, dx: number, dy: number, dz: number)
```

Move entities by offset with smooth visual transition.

**Parameters**

- `ids` `IdList`
- `dx` `number`
- `dy` `number`
- `dz` `number`

```lua
"box", 0, 1, 0
ids, 0, 5, 0
```

## tools/sc/outline {#tools-sc-outline}

```lua
sc.outline(ids: IdList, r: number, g: number, b: number, intensity?: number)
```

Set outline on one or many entities.

**Parameters**

- `ids` `IdList`
- `r` `number`
- `g` `number`
- `b` `number`
- `intensity` `number` _(optional)_

```lua
"box", 1, 1, 0
ids, 1, 0, 0, 2
```

## tools/sc/quantize {#tools-sc-quantize}

```lua
sc.quantize(ids: IdList, opts?: QuantizeOpts)
```

Snap transform to grid. Each axis is optional.

**Parameters**

- `ids` `IdList`
- `opts` `QuantizeOpts` _(optional)_

```lua
"box", { position = 0.5 }
ids, { rotation = 45 }
```

## tools/sc/removeComponent {#tools-sc-removecomponent}

```lua
sc.removeComponent(ids: IdList, typeName: string)
```

Remove component from one or many entities.

**Parameters**

- `ids` `IdList`
- `typeName` `string`

```lua
"box", "Physics"
ids, "BoxCollider"
```

## tools/sc/replaceWithAsset {#tools-sc-replacewithasset}

```lua
sc.replaceWithAsset(target: string, source: AssetRef<bundle|mesh>, opts?: ReplaceWithAssetOpts) -> string
```

Replace an already-spawned placeholder entity with a real asset, in place — keeping its position, rotation, scale, name, and parent. Removes the placeholder (and its whole subtree) and spawns the asset in its slot. The everyday "I blocked this out, now drop in the generated (or library) asset" move — so a scene built from rough stand-ins becomes the real thing without re-laying-out anything.

**Parameters**

- `target` `string`
- `source` `AssetRef<bundle|mesh>`
- `opts` `ReplaceWithAssetOpts` _(optional)_

**Returns** `string`

```lua
"rider_blockout", "/zero/source/generated/meshes/a_snowboarder.bundle"
"tree_box", "@libname::pine.bundle", { keepScale = false }
```

## tools/sc/setParent {#tools-sc-setparent}

```lua
sc.setParent(ids: IdList, parentId: string | EntityRef)
```

Parent one or many entities to a single parent.

**Parameters**

- `ids` `IdList`
- `parentId` `string | EntityRef`

```lua
"hat", "player"
ids, "torso"
```

## tools/sc/spawnCamera {#tools-sc-spawncamera}

```lua
sc.spawnCamera(name: string, ...) -> string
```

Spawn a camera at position.

**Parameters**

- `name` `string`

**Returns** `string`

```lua
"main_cam", 0, 5, 10, { fov = 60 }
```

## tools/sc/spawnCircle {#tools-sc-spawncircle}

```lua
sc.spawnCircle(model: AssetRef<bundle|mesh>, name: string, count: number, opts?: CircleOpts) -> { string }
```

Spawn entities in a circle. Returns array of IDs.

**Parameters**

- `model` `AssetRef<bundle|mesh>`
- `name` `string`
- `count` `number`
- `opts` `CircleOpts` _(optional)_

**Returns** `{ string }`

```lua
"sphere", "pillar", 8, { radius = 10 }
```

## tools/sc/spawnGrid {#tools-sc-spawngrid}

```lua
sc.spawnGrid(model: AssetRef<bundle|mesh>, name: string, cols: number, rows: number, opts?: GridOpts) -> { string }
```

Spawn a grid of entities. Returns array of IDs.

**Parameters**

- `model` `AssetRef<bundle|mesh>`
- `name` `string`
- `cols` `number`
- `rows` `number`
- `opts` `GridOpts` _(optional)_

**Returns** `{ string }`

```lua
"cube", "tile", 5, 5, { spacing = 2, y = 0.5 }
```

## tools/sc/spawnLine {#tools-sc-spawnline}

```lua
sc.spawnLine(model: AssetRef<bundle|mesh>, name: string, count: number, opts?: LineOpts) -> { string }
```

Spawn entities in a line. Returns array of IDs.

**Parameters**

- `model` `AssetRef<bundle|mesh>`
- `name` `string`
- `count` `number`
- `opts` `LineOpts` _(optional)_

**Returns** `{ string }`

```lua
"cube", "wall", 10, { spacing = 1 }
"cube", "col", 5, { spacing = 1, dirY = 1 }
```

## tools/sc/spawnModel {#tools-sc-spawnmodel}

```lua
sc.spawnModel(name: string, source: AssetRef<bundle|mesh>, ...) -> string
```

Spawn entity with model at position. Includes static Physics by default unless opts.physics overrides it.

**Parameters**

- `name` `string`
- `source` `AssetRef<bundle|mesh>`

**Returns** `string`

```lua
"box", "plain_name", 0, 2, 0
"crate", "cube", 0, 0, 0, { scale = { 6, 3, 1 }, rotation = { 0, 45, 0 } }
"player", "@libname::identity.in.library", 0, 0, 0
"player", "@identity.of.asset", 0, 0, 0
"player", "path/to/asset.bundle", 0, 0, 0
```

## tools/sc/spawnPhysics {#tools-sc-spawnphysics}

```lua
sc.spawnPhysics(name: string, model: AssetRef<bundle|mesh>, ...) -> string
```

Spawn entity with model + dynamic physics. Shorthand for `sc.spawnModel` with `physics = "dynamic"`.

**Parameters**

- `name` `string`
- `model` `AssetRef<bundle|mesh>`

**Returns** `string`

```lua
"ball", "sphere", 0, 5, 0
```

## tools/sc/spawnText {#tools-sc-spawntext}

```lua
sc.spawnText(name: string, content: string, ...) -> string
```

Spawn 3D text at position.

**Parameters**

- `name` `string`
- `content` `string`

**Returns** `string`

```lua
"title", "Hello!", 0, 3, 0, { color = "yellow", fontSize = 48 }
```

## tools/sc/tint {#tools-sc-tint}

```lua
sc.tint(ids: IdList, r: number, g: number, b: number, blend?: number)
```

Set tint color on one or many entities.

**Parameters**

- `ids` `IdList`
- `r` `number`
- `g` `number`
- `b` `number`
- `blend` `number` _(optional)_

```lua
"box", 1, 0, 0
ids, 0, 1, 0, 0.8
```

## tools/sc/transform {#tools-sc-transform}

```lua
sc.transform(ids: IdList, opts?: TransformOpts)
```

Set transform on one or many entities. All fields optional. Smooth visual transition by default.

**Parameters**

- `ids` `IdList`
- `opts` `TransformOpts` _(optional)_

```lua
"box", { position = {5, 0, 0} }
"box", { scale = 2, position = {0, 5, 0} }
ids, { scale = 0.5 }
ids, { pos = {0, 0, 0}, smooth = 0 }
```

## tools/sc/unparent {#tools-sc-unparent}

```lua
sc.unparent(ids: IdList)
```

Unparent one or many entities (make roots).

**Parameters**

- `ids` `IdList`

```lua
"hat"
ids
```

## tools/scene/clean {#tools-scene-clean}

```lua
scene.clean(opts?: CleanOpts) -> CleanResult
```

Clear the active scene down to a protected keep-set. By default ONLY the player setup survives — every PlayerPrototype subtree (with its body/camera) and every PlayerSpawn — so a clean never orphans or destroys the player (a joining user's avatar replaces the default body, so the player must persist). `keep` additionally protects a CATEGORY of entities: `{ component = { "Light", "Sky" } }` keeps lighting, `{ tag = { "landmark" } }` keeps tagged entities, `{ under = "rig" }` keeps a subtree. It selects a group, not one named entity. `keepPlayer = false` wipes the player too (a full empty). `dryRun = true` reports what would be removed/kept without mutating. This is a LIVE mutation only — it does not commit to the saved scene, so `scene.revert` undoes it and `scene.save` commits it. If the scene has UNSAVED edits, clean REFUSES (it errors without changing anything): its removals would fold into those edits and no revert could restore the pre-clean state, so the unsaved work would be lost. There is no confirm/force flag — resolve the edits first with `scene.save` (keep them) or `scene.revert` (discard them), then clean. (The one exception is play mode running unpaused, where the dirty overlay is gated and untouched, so the guard does not apply.) Returns `{ removed, kept, intent }`, plus a `warning` when the clean left the scene with no lights.

**Parameters**

- `opts` `CleanOpts` _(optional)_

**Returns** `CleanResult`

```lua
-- clear everything except the player
{ keep = { component = { "Light", "Sky" } } }  -- also keep lighting
{ keepPlayer = false }  -- full empty
{ dryRun = true }  -- preview only
```

## tools/scene/cost {#tools-scene-cost}

```lua
scene.cost(reset?: boolean) -> { [string]: any }
```

Rank the loaded scenes by what their per-frame tick costs — the answer to "which scene is eating the frame time". A scene entrypoint's `update` / `editorUpdate` runs on the scheduler rather than as a component, so it is absent from `profiler.scripts`; this times it where it runs and attributes it to the layer that owns it. `avgMs` is the per-tick cost, which is the per-frame cost for a tick that runs every frame; `totalMs` is a SUM across the window since the last `reset` (or engine start), which `window` reports. A layer whose entrypoint declares no tick is absent — it costs nothing per frame. Answers in edit mode as well as play.

**Parameters**

- `reset` `boolean` _(optional)_

**Returns** `{ [string]: any }`

```lua
true
```

## tools/scene/create {#tools-scene-create}

```lua
scene.create(name: string, template?: ("empty" | "flat" | "player" | "menu"), opts?: { startup: boolean? }) -> CreateCatalog | CreateResult
```

Create a NEW scene from a template and load it. `template` is typed — omit it to get the catalog of templates and what each contains; an unknown value errors with the valid choices. `"empty"` is a blank canvas; `"flat"` is ground + lighting, no player; `"player"` is ground + lighting + the default player (the canonical, reusable setup a joining user's avatar replaces — prefer this over hand-rolling a player); `"menu"` is lighting only, no ground, no player (title screens / UI). Pass `opts.startup = true` to also make the new scene the world's startup scene. The scene is minted as a fresh asset and loaded — a name a scene already stands at is refused, naming the path it stands at, since writing a template over it would load that scene rather than a new one.

**Parameters**

- `name` `string`
- `template` `("empty" | "flat" | "player" | "menu")` _(optional)_
- `opts` `{ startup: boolean? }` _(optional)_

**Returns** `CreateCatalog | CreateResult`

```lua
"level_1", "flat"  -- ground + lighting, no player
"title", "menu", { startup = true }  -- a menu scene the world opens into
"sandbox"  -- returns the template catalog
```

## tools/scene/find {#tools-scene-find}

```lua
scene.find(query: shared.FindQueryArg) -> shared.FindResult
```

Find entities across the whole live population — runtime clones, temporary, and detached-root entities included (it reads the flat entity query, not a scene-root walk). Pick a search axis, each taking a LIST so many candidates resolve in one call: `id` (exact entity ids — the ids this tool itself reports, so a batch of them resolves in one call), `name` (entity name terms), `component` (component names/identities — entities carrying any), `attribute` (an attribute name + optional value). Multiple axes = union; each hit reports WHICH criterion matched so you learn the right wording. A key the query does not recognise is an error naming the axes and scopes it takes, so a misspelled axis never reads back as the whole scene. `substring` (default true) matches name parts case-insensitively — a short term like "muzzle" reaches "muzzle_light_ent_620d…"; set false for exact. A name term carrying `*` or `?` is matched as a GLOB over the whole name (`"ik_*"`, `"wall_?"`) — the same convention `entity.find` / `entity.findAll` take — in both modes, since `*` states a pattern either way. Entities marked internal are excluded unless `includeInternal`. Scope with `roots` (top-level only), `maxDepth`, `under` (only descendants of these entities), `ancestorsOf` (only ancestors of these), and `additive` (default false = active scene only; true also searches loaded overlay scenes). `include` makes the query ANSWER the question instead of handing back a list to interrogate one entity at a time: pass an array of projections and every match carries them — `"transform"` adds `position` / `rotation` / `eulerAngles` / `scale`, `"componentValues"` adds each component's LIVE field values (not just its name, and not the type's declared defaults), `"attributes"` adds the entity's attributes, `"scriptComponents"` adds every script-component instance the live entity carries. Nothing is added unless `include` names it, and an unrecognised projection is an error listing the valid ones. Returns every hit with its FULL canonical id + name.

**Parameters**

- `query` `shared.FindQueryArg`

**Returns** `shared.FindResult`

```lua
"muzzle"
{ id = { "ent_5ce7e0a14d3f9b07" } }
{ id = ids, include = { "transform" } }
{ name = { "muzzle", "flash" } }
{ name = { "body" }, substring = false }
{ component = { "Light", "Camera" } }
{ name = { "wheel" }, under = { "car" } }
{ attribute = { name = "team", value = "red" } }
{ roots = true }
{ name = { "wall" }, include = { "transform" } }
{ component = { "Light" }, include = { "transform", "componentValues" } }
{ under = { "room" }, include = { "transform", "componentValues", "attributes", "scriptComponents" } }
```

## tools/scene/list {#tools-scene-list}

```lua
scene.list(scope?: SceneListScope) -> SceneListResult
```

List the SCENES available to load — the world's own scenes and the builtin library scenes — so you can find a scene by name instead of guessing. `scope` filters: `"world"` (default, the scenes this world authored), `"library"` (builtin `@builtin::` scenes — templates, demos, canonical setups), or `"all"`. Each entry reports its `name`, `identity`, `path`, `scope` (`"world"`/`"library"`), and whether it is currently `loaded` / `active`. Load one with `scene.load(name)`; the currently loaded LAYERS are `scene.loadedLayers`.

**Parameters**

- `scope` `SceneListScope` _(optional)_

**Returns** `SceneListResult`

```lua
-- the world's own scenes
"library"  -- builtin templates / demos / canonical scenes
"all"
```

## tools/scene/load {#tools-scene-load}

```lua
scene.load(name: string) -> LoadResult
```

Load a scene into the live world and make it the active scene. `name` is a scene NAME (see `scene.list`), a builtin identity (e.g. `"@builtin::scenes.test_arena"`), or a scene path. Loading a scene that is ALREADY loaded changes nothing — it is idempotent — so this is NOT how you re-run an edited scene: use `scene.reload` for that. Records `name` so a later no-arg `scene.save()` targets it.

**Parameters**

- `name` `string`

**Returns** `LoadResult`

```lua
"main"
"@builtin::scenes.test_arena"
```

## tools/scene/loadedLayers {#tools-scene-loadedlayers}

```lua
scene.loadedLayers() -> { LoadedLayer }
```

The scene LAYERS loaded into the live scene right now — the root scene plus any additive overlays (editor UI, effect passes). Each layer is reported as `{ name, scene, status, visible, additive, root }`: `scene` is the backing scene identity, `status` the load state, `additive` false marks the active ROOT scene and true an overlay layered over it. This is what is composing the live scene; to list the scenes you can LOAD (world + library), use `scene.list`.

**Returns** `{ LoadedLayer }`

## tools/scene/migrate_v6_to_v7 {#tools-scene-migrate-v6-to-v7}

```lua
scene.migrate_v6_to_v7(opts: MigrateOpts) -> MigrateResult
```

Convert a v6 scene.json to v7 in place. Reads the scene's scene.json, decides the player intent from the v6 `player.required_in_play` flag ("spawns" when true, else "none"), removes the v6 `player{}`/`camera{}` blocks, sets the top-level string `player` intent, and — for "spawns" — injects the final-shape PlayerSetups/Spawns structure with per-scene-unique entity ids. `format`, `lighting`, and every existing entity are preserved. Idempotent: a scene already at version >= 7 returns "skipped"; a scene with a missing or > 7 version returns an "error" result without mutating. Also scans the sibling entrypoint.luau for references to the removed player/camera flow and returns them as review notes (the Luau is never rewritten).

**Parameters**

- `opts` `MigrateOpts`

**Returns** `MigrateResult`

```lua
{ path = "/source/libs/@builtin/demos/minecraft/minecraft.scene/scene.json" }
```

## tools/scene/observe {#tools-scene-observe}

```lua
scene.observe() -> { [string]: any }
```

What every scene load did, and what each loaded scene costs in frame time. `lastLoad` is the most recent load's report: the scene it loaded, the root it replaced and the overlays that went with it, the entity counts on each side, the milliseconds each phase took, and every failure it produced. `layers` is one row per loaded layer with what the engine attributes to it and whether it came up whole; `cost` is the per-frame tick cost of each layer whose entrypoint declares one, summed across the window `window` reports. A load that produced failures reports `outcome = "partial"` and a `reason` from a closed set. Answers in edit mode as well as play mode.

**Returns** `{ [string]: any }`

## tools/scene/player {#tools-scene-player}

```lua
scene.player(intent?: ("spawns" | "none"), roles?: PlayerRoles) -> PlayerReport
```

The scene's player setup, in one tool. No argument reports the intent plus every PlayerPrototype (with body/camera wiring) and PlayerSpawn. `intent` is typed: `"none"` makes the scene playerless — every PlayerPrototype subtree and PlayerSpawn is removed; `"spawns"` gives it a player per joining user, authoring the canonical default setup (humanoid body, third-person camera, origin spawn) when the scene has no PlayerPrototype yet. Pass `roles` (implies `"spawns"`) to reshape: `body` adopts an entity OR repoints the body's avatar from an avatar asset ref; `camera` wires an entity carrying a Camera (scoped OwnerOnly); `behavior` sets the player camera's Camera.behavior to any cameraBehavior component (built-in or your own); `prototype` picks one when the scene has several. Reuse this default setup — a joining player's avatar replaces the default body, so a hand-rolled player breaks multiplayer. Setting anything saves the scene.

**Parameters**

- `intent` `("spawns" | "none")` _(optional)_
- `roles` `PlayerRoles` _(optional)_

**Returns** `PlayerReport`

```lua
-- report the current player setup
"none"  -- strip the player: no prototype, no spawns
"spawns"  -- player per joining user (authors the canonical setup if missing)
"spawns", { body = "@builtin::avatars.minimal_player" }  -- swap the avatar
"spawns", { behavior = "@builtin::controller.first_person" }  -- first-person player
```

## tools/scene/reload {#tools-scene-reload}

```lua
scene.reload(name?: string) -> ReloadResult
```

Unload and re-load a scene layer in place, running the scene's `build.luau` and its entrypoint again. `scene.load` of a scene that is already loaded and unchanged is an idempotent no-op, so editing either of them and loading it again rebuilds nothing; this re-runs them. The build runs against what it resolves right now, so a script whose inputs moved — a component that now exists, an asset that now resolves — produces the scene it describes today. Works in edit mode, so iterating on a build script does not mean flipping to play and back through the play shadow.

**Parameters**

- `name` `string` _(optional)_

**Returns** `ReloadResult`

```lua
"main"
```

## tools/scene/remove {#tools-scene-remove}

```lua
scene.remove(targets: Targets) -> RemoveResult
```

Remove entities from the active scene. Targets are entity NAMES or ids, arrays of them, `scene.find` records, or a `scene.find` QUERY (`{ name = { "ground" } }`, `{ component = { "Light" } }`, `{ under = { "rig" } }`) — so you delete a selection without looking it up first. Each target is removed with its whole subtree. Removing the default floor is `scene.remove("ground")`. This is a LIVE mutation only — it does not commit to the saved scene, so `scene.revert` undoes it and `scene.save` commits it. To clear a scene while keeping the player setup, use `scene.clean` instead.

**Parameters**

- `targets` `Targets`

**Returns** `RemoveResult`

```lua
"ground"  -- delete the default floor
{ "probe_a", "probe_b" }
{ component = { "PointLight" } }  -- delete every entity with that component
```

## tools/scene/replace {#tools-scene-replace}

```lua
scene.replace(source: string, opts?: ReplaceOpts) -> ReplaceReport
```

Replace a loaded scene's content with another scene's content. `source` is a scene name / path / identity string, resolved internally (the resolved identity is reported back). Every content file in the target scene's folder (scene.json, entrypoint.luau, ...) is overwritten with the source's copy; target content files the source doesn't have are removed; the target scene's identity (guid, name) is untouched. The scene then reloads so the world reflects the new content immediately. Targets the ACTIVE scene unless `opts.scene` names another loaded scene. `dryRun = true` reports exactly which files would be written and removed, and the entities the source declares, without touching anything.

**Parameters**

- `source` `string`
- `opts` `ReplaceOpts` _(optional)_

**Returns** `ReplaceReport`

```lua
"@builtin::scenes.test_arena"
"my_template", { dryRun = true }
```

## tools/scene/revert {#tools-scene-revert}

```lua
scene.revert(name?: string) -> RevertResult
```

Discard the active scene's UNSAVED edits and restore its saved `scene.json`. Every edit-mode change since the last `scene.save` — a `scene.clean`, a `scene.remove`, spawned/moved/deleted entities — lives in the scene's dirty overlay until you commit it; this throws that overlay away and respawns the scene from its saved state. It is the undo for `scene.clean` / `scene.remove` and the counterpart to `scene.save`. Pass `name` to revert a specific loaded scene; omit it for the active one. Reverts only the edit-mode dirty overlay — it is not the play-mode baseline restore.

**Parameters**

- `name` `string` _(optional)_

**Returns** `RevertResult`

```lua
-- undo unsaved edits on the active scene
"main"  -- revert the loaded scene named 'main'
```

## tools/scene/save {#tools-scene-save}

```lua
scene.save(name?: string) -> SaveResult
```

Save the loaded scene named `name` back to its own `scene.json`, writing the path synchronously. Resolves the scene by name via `layers.find` and saves THAT scene — so saving scene A never writes scene B. No-args form uses the toolbox's last scene name (default `"main"`). Errors when no scene by that name is loaded.

**Parameters**

- `name` `string` _(optional)_

**Returns** `SaveResult`

```lua
-- save the last-used scene (or "main")
"main"  -- save the loaded scene named 'main'
```

## tools/scene/setStartup {#tools-scene-setstartup}

```lua
scene.setStartup() -> SetStartupResult
```

Make the CURRENTLY ACTIVE scene the world's startup scene — the scene the world auto-loads every time it loads. Takes no arguments: it pins whatever scene is live now, so after building or loading a scene you call this to make it stick across world reloads instead of the world snapping back to its previous startup. There is no clear/unset — a world with no startup scene has no scene context and every script fails. The current startup scene shows in `scene.summary`.

**Returns** `SetStartupResult`

```lua
-- pin the active scene as the world's entry point
```

## tools/scene/spawns {#tools-scene-spawns}

```lua
scene.spawns(action?: SpawnAction, opts?: SpawnOpts) -> SpawnsResult
```

The scene's spawn points, in one tool — where joining players arrive, as opposed to `scene.player`, which decides whether the scene spawns players at all and what they become. No argument reports every PlayerSpawn with its world transform, the prototype it instantiates (and whether that reference still resolves), and its team / role / maxPlayers / spawnPolicy / placement. `"add"` places a new spawn: it takes the prototype named in `opts.prototype`, or the scene's only one, and is parented alongside the spawns already there so spawn points stay collected. `"set"` reconfigures an existing spawn — every field is optional and the ones you leave out keep their value. `"remove"` deletes one. `opts.spawn` names WHICH spawn (`"set"` / `"remove"`), or the new entity's name (`"add"`); with several spawns in the scene it is required, with one it is implied. A spawn's own transform is where a player's body lands while `placement` is `at_spawn_transform`, and its forward axis is the direction they face — the edit-mode overlay draws that ring, heading arrow and standing capsule so a spawn is visible where it stands. Mutating saves the scene.

**Parameters**

- `action` `SpawnAction` _(optional)_
- `opts` `SpawnOpts` _(optional)_

**Returns** `SpawnsResult`

```lua
-- report every spawn point in the scene
"add", { position = { 12, 0, -4 } }  -- a second spawn over there
"add", { spawn = "RedSpawn", position = { -8, 0, 0 }, rotation = { 0, 90, 0 }, team = "red" }
"set", { spawn = "DefaultSpawn", position = { 0, 0, 6 } }  -- move it
"set", { spawn = "RedSpawn", maxPlayers = 4 }
"remove", { spawn = "RedSpawn" }
```

## tools/scene/summary {#tools-scene-summary}

```lua
scene.summary() -> SceneSummary
```

Scene overview: entity + root counts, the root list, the active camera, the PLAYER setup (intent + prototype/spawn counts, with the verb to change it), and the world's STARTUP scene. The player block is how you learn a scene's player type without knowing it exists — `intent` is `"spawns"` (a player per joining user) or `"none"` (playerless), changed with `scene.player("spawns" / "none")`.

**Returns** `SceneSummary`

## tools/scene/tree {#tools-scene-tree}

```lua
scene.tree(target?: TreeTarget, opts?: TreeOpts) -> TreeResult
```

Render the entity hierarchy as an indented tree — the quick "what is the current structure" view. Shows each entity's name, its component types, and its children down to `depth` levels; anything deeper (or beyond the node budget) is summarized as a count instead of silently dropped. With no target it draws the whole active world from its roots; a target (id, name, proxy, or an array) draws just those subtrees.

**Parameters**

- `target` `TreeTarget` _(optional)_
- `opts` `TreeOpts` _(optional)_

**Returns** `TreeResult`

```lua
'player'
nil, { depth = 2 }
```

## tools/scene/whyPartial {#tools-scene-whypartial}

```lua
scene.whyPartial(scene?: string) -> { [string]: any }
```

Answer why a loaded scene layer is not whole. `reason` is the nearest cause from a closed set — `loaderRaised`, `entrypointCompileFailed`, `entrypointBodyRaised`, `entrypointRaised`, `buildRaised`, `entityFailed`, `parentMissing`, `parentRefused`, `parentAbandoned`, `componentUnresolved`, `componentRefused`, `subscriberRaised`, `updateRaised` — so it names the thing to fix. `failures` carries every one of them, each with the entity, component identity or lifecycle hook it is about and the engine's own message. A layer that produced everything its scene declared reports `ok = true` with no reason.

**Parameters**

- `scene` `string` _(optional)_

**Returns** `{ [string]: any }`

```lua
"scenes.main"
```

## tools/sceneAuthoring/acceptChanges {#tools-sceneauthoring-acceptchanges}

```lua
sceneAuthoring.acceptChanges(selection?: shared.Selection, opts?: AcceptOpts) -> AcceptResult
```

Accept changes shown by `tools.use("sceneAuthoring", "changes")` — all of them, or a selection. Writes the scene, saves the play-created assets the accepted entities reference into the world's source, saves play-edited source files when included (group "edits"), and resumes the engine. A partial accept leaves the rest live — the next `changes()` shows the remainder. If another editor changed a record since the review was taken, the accept ABORTS with a conflict report (nothing written) unless `opts.onConflict` is "mine" or "theirs".

**Parameters**

- `selection` `shared.Selection` _(optional)_
- `opts` `AcceptOpts` _(optional)_

**Returns** `AcceptResult`

```lua
{ "tile_", "hero", "edits" }
"my_component/init.luau"
nil, { onConflict = "mine" }
```

## tools/sceneAuthoring/changes {#tools-sceneauthoring-changes}

```lua
sceneAuthoring.changes(opts?: ChangesOpts) -> string
```

Review what exists live but is not yet part of the scene: entities you spawned (clustered — one spawn batch is one decision), edits to existing scene entities (with field-level diffs), the assets those changes reference (flagging play-created ones and reload-broken orphans), source files edited during play, and session post-process effects and UI screens. Pauses the engine at the exact moment of the call so the review is a frozen snapshot; writes NOTHING. The scene only ever changes via `tools.use("sceneAuthoring", "acceptChanges")`; drop changes with `tools.use("sceneAuthoring", "rejectChanges")`.

**Parameters**

- `opts` `ChangesOpts` _(optional)_

**Returns** `string`

```lua
{ as = "checkpoint_1" }
```

## tools/sceneAuthoring/rejectChanges {#tools-sceneauthoring-rejectchanges}

```lua
sceneAuthoring.rejectChanges(selection?: shared.Selection) -> RejectResult
```

Reject changes shown by `tools.use("sceneAuthoring", "changes")` — all of them, or a selection. Rejected entity changes never enter the scene; the live objects stay live for this session and are hidden from later reviews. Each rejected change is taken back out of the scene's pending set, so the scene stops carrying it and the publication gate that refuses a commit over unbaked entity edits reads it as settled. A rejected REMOVAL keeps the entity in the scene: the despawn holds for this session and the entity is there again on the next load. Rejected file edits (group "edits", or individual paths) are REVERTED: each file is restored to its pre-play state (the last edit-mode content, unstaged edits included; a file created during play is removed), hot-reload picks the original back up, and the edit no longer blocks leaving play. Resumes the engine.

**Parameters**

- `selection` `shared.Selection` _(optional)_

**Returns** `RejectResult`

```lua
"tile_"
"my_component/init.luau"
```

## tools/sceneAuthoring/reviewState {#tools-sceneauthoring-reviewstate}

```lua
sceneAuthoring.reviewState() -> ReviewState
```

What is under review right now, and how much of it: the entities the scene does not have or holds differently, the ones despawned this session, the source files edited during play, and the effects, screens and features registered from `execute`. On an engine several agents drive at once, `edits` counts this agent's own in-play source edits and `foreignEdits` counts a co-author's — both hold the play-exit gate, and only the first is what a bare `acceptChanges` or `rejectChanges` settles. The counts come from the same survey of the live world `tools.use("sceneAuthoring", "changes")` renders and the leave-play safeguard enforces, so an agent deciding whether it has work to keep reads the same answer all three give. `open` is true while anything is pending, which is when `acceptChanges` and `rejectChanges` have something to settle. Every count is zero and `open` is false once a verdict has settled the lot, and while no scene is loaded as the root layer. The engine keeps running across the call.

**Returns** `ReviewState`

```lua
-- branch instead of relying on the refusal
-- local s = tools.use("sceneAuthoring", "reviewState")
-- if s.open then tools.use("sceneAuthoring", "changes") end
```

## tools/services/attach {#tools-services-attach}

```lua
services.attach(jobId: string) -> AttachedJob
```

Read a generation's gateway job by its `jobId` (the `jobId` on a services.status / services.jobs row). A submitted job is kept on the gateway, with its result, for hours after it finishes — longer than the engine that started it is guaranteed to last — so this reaches work that was already paid for once the run that started it is gone. Returns { jobId, status, result?, error? }: `status` is pending / running / succeeded / failed, and a succeeded job's `result` is the provider's response — the URL or bytes the run would have downloaded. Costs nothing; the charge happened at submit.

**Parameters**

- `jobId` `string`

**Returns** `AttachedJob`

```lua
"01M12BH4P0C1CVXMN3QX9JEJKZ"
```

## tools/services/balance {#tools-services-balance}

```lua
services.balance() -> (BalanceReport?, string?)
```

What you can spend on a generation. Compare an operation's `cost` (from services.list) against `spendable` — that is the binding number. `pool` is the account's whole balance and `agentRemaining` is what is left of your own allocation when you work under one; whichever is smaller is what `spendable` reports, because an allocation that is spent stops a generation however large the pool is. Returns (report, nil) when signed in, (nil, reason) otherwise.

**Returns** `(BalanceReport?, string?)`

## tools/services/generate {#tools-services-generate}

```lua
services.generate(service: string, input?: GenerateInput, operation?: string) -> GenerateHandle
```

Start a generation. `service` is a name from services.list; `input` is that operation's inputs (e.g. { prompt = "a wooden treasure chest" }). Returns immediately with { id, service, operation } — generation takes minutes and runs in the background, surviving this call. Track it with services.status(id) across turns until status == "completed", then spawn the row's `asset`. Consumes credits (see the operation's cost in services.list).

**Parameters**

- `service` `string`
- `input` `GenerateInput` _(optional)_
- `operation` `string` _(optional)_

**Returns** `GenerateHandle`

```lua
"mesh_gen", { prompt = "a wooden chest" }
"audio_gen", { operation = "sfx", prompt = "a heavy wooden door slamming" }
"audio_gen", { text = "Welcome aboard." }, "speech"
"image_gen", { prompt = "a mossy stone idol", asset_path = "/zero/source/game/art/idol.png" }
```

## tools/services/jobs {#tools-services-jobs}

```lua
services.jobs() -> { JobStatus }
```

List every generation job this session has started — active and finished, newest first. Each row is the same shape as services.status: { id, service, operation, prompt, status, progress, asset, error }. The live view of what's running and where the finished ones landed. (For the durable, cross-session record of generated assets, use services.outputs.)

**Returns** `{ JobStatus }`

## tools/services/list {#tools-services-list}

```lua
services.list() -> { ServiceEntry }
```

List the generation services you can use — the things you can generate (3D meshes, textures, audio, …) by spending credits. Fully self-describing: each service entry is { name, description, default, operations }, and each operation is { name, description, inputs, produces, cost } — `inputs` is an ordered array of { name, type, required, desc, framework }, `produces` is the kind of asset you get back, `cost` is credits per call. An input marked `framework = true` belongs to the call rather than to the operation: `operation` names which of the service's operations to run (required where the service declares no default), and `asset_path` names where the run writes what it generates. Every input listed here — framework or declared — goes in the one input table. An entry's `default` is the operation a call runs when it names none, so a service listing several operations says which of them a bare call reaches. The entry point: call this first, then `services.generate(service, input)` with what you learn here.

**Returns** `{ ServiceEntry }`

## tools/services/outputs {#tools-services-outputs}

```lua
services.outputs() -> { GeneratedOutput }
```

List the assets generated by services in this world — the durable record that survives restarts (the in-flight job records in services.jobs do not). Reads the provenance stamped on each produced asset. Each row: { asset, service, operation, prompt, generatedAt }, where `asset` is the spawnable asset path. Use this to find what you've generated across sessions.

**Returns** `{ GeneratedOutput }`

## tools/services/status {#tools-services-status}

```lua
services.status(id: string) -> JobStatus?
```

Read one generation job's current status by its handle id (the `id` from services.generate). Returns { id, service, operation, prompt, status, progress, asset, error } or nil if the id is unknown. `status` moves through the operation's stages to "completed" or "failed"; `asset` is the spawnable asset path once "completed". Poll this across turns — the generation keeps running in the background regardless, so each call is quick.

**Parameters**

- `id` `string`

**Returns** `JobStatus?`

```lua
g.id
```

## tools/skills/invoke {#tools-skills-invoke}

```lua
skills.invoke(name: string) -> string
```

Open a skill and return everything needed to act on it: its instructions, the assets and guides it depends on (each marked present or missing in this world), the toolboxes and tool names it runs through with their live signatures, and the subskills available under it. Address a top-level skill by name (`"scenes"`) and a subskill through its parent (`"scenes/player-setup"`). A name that matches nothing returns the available names. The skill stays open afterwards — it and its subskills ride your tool responses, each marked as you open it — until `skills.release` puts it down.

**Parameters**

- `name` `string`

**Returns** `string`

```lua
"scenes"
```

## tools/skills/list {#tools-skills-list}

```lua
skills.list(scope?: ("builtin" | "world" | "library")) -> SkillListing
```

List the skills this world knows — every top-level skill with the one-line description it advertises itself by, ordered built-in first then by name. Each row's `name` is the address `skills.invoke` takes, and `open` says whether you already have that skill open. Subskills are not listed: they are reachable only through their parent, and the parent's `invoke` result names them. `open` at the top level carries every address you have open, subskills included.

**Parameters**

- `scope` `("builtin" | "world" | "library")` _(optional)_

**Returns** `SkillListing`

## tools/skills/release {#tools-skills-release}

```lua
skills.release(name?: string) -> ReleaseResult
```

Put a skill down: stop it and its subskills riding your tool responses. Releasing a parent releases the subskills opened under it, since those are passes within the same job. Pass no name to release everything you have open. Naming a skill that is not open returns the ones that are.

**Parameters**

- `name` `string` _(optional)_

**Returns** `ReleaseResult`

```lua
"scenes"
```

## tools/temp/run {#tools-temp-run}

```lua
temp.run(code: string, duration?: number) -> RunResult
```

Execute Luau code and auto-cleanup the entities it creates after a timeout. Snapshots existing entity IDs via `ecs.entities()` before running the code, compiles the code with `loadstring`, executes it under `pcall`, diffs the post-execution entity set against the pre-set to find newly-created entities, then schedules a deferred cleanup pass that despawns each created entity. Useful for temporary visualizations, test setups, and previews that should not leak.

**Parameters**

- `code` `string`
- `duration` `number` _(optional)_

**Returns** `RunResult`

```lua
'sc.spawnModel("test", "sphere", {0, 3, 0})'
[[ sc.spawnGrid("cube", "preview", 3, 3, { spacing = 2, y = 1 }) ]], 10
```

## tools/tests/compare {#tools-tests-compare}

```lua
tests.compare(before: string | Report, after?: (string | Report)) -> Diff
```

Diff two test reports and report NEW failures (regressions), fixes, and added/removed tests. The headline is `newFailures` — tests failing now that passed (or didn't exist) in the baseline.

**Parameters**

- `before` `string | Report`
- `after` `(string | Report)` _(optional)_

**Returns** `Diff`

```lua
"/source/before.json"
"/source/before.json", "/source/after.json"
```

## tools/tests/list {#tools-tests-list}

```lua
tests.list(opts?: (ListOpts | string)) -> ListResult
```

List discovered test suites (registrable `.testSuite` assets). Scoped to `scope = "user"` (world-authored) by default — the same default as `tests.run`; pass `scope = "all"` to include the baked `@builtin` library. Optionally load each suite to also report its test count.

**Parameters**

- `opts` `(ListOpts | string)` _(optional)_

**Returns** `ListResult`

```lua
{ filter = "vfs", details = true }
```

## tools/tests/loadHttp {#tools-tests-loadhttp}

```lua
tests.loadHttp(opts?: LoadOpts) -> (LoadResult | LoadHandle)
```

Fetch engine `.testSuite` assets over HTTP and materialize them into the `@builtin` library so `tests.list` / `tests.run` discover them. For the browser/WASM test runner, where the suites are not embedded in the binary.

**Parameters**

- `opts` `LoadOpts` _(optional)_

**Returns** `(LoadResult | LoadHandle)`

```lua
-- browser default: async task
{ async = false }  -- block, return counts
{ suites = { "effects_core" } }  -- just this suite
```

## tools/tests/run {#tools-tests-run}

```lua
tests.run(opts?: (RunOpts | string)) -> (RunResult | AsyncHandle)
```

Run the engine test suite. A bare `tests.run()` runs every world-authored suite (`scope = "user"`, the default) — never the baked `@builtin` library a content session can't edit; `tests.run("*")` runs EVERYTHING (scope "all"); `tests.run("name")` runs one suite (an explicit name bypasses scope); a table form `{ suite?, suites?, scope?, save?, format?, quiet?, async?, measurements? }` gives full control. Each suite runs through its own `AssetRef:run()` inside a task, so the engine stays responsive. Always writes `/source/tmp/test_results.{md,json}` (under `/source/tmp/`, which is excluded from world saves, so reports never sync). With `async = true` the whole run is spawned as one task and the call returns `{ taskId, reportMd, reportJson }` immediately — watch `/runtime/tasks/{completed,failed}/<taskId>` and read the report when it lands. Refused in play mode.

**Parameters**

- `opts` `(RunOpts | string)` _(optional)_

**Returns** `(RunResult | AsyncHandle)`

```lua
"*"
"vfs"
{ async = true }  -- watch /runtime/tasks/<h.taskId>
```

## tools/voxelConfig/all {#tools-voxelconfig-all}

```lua
voxelConfig.all() -> { [string]: any }
```

Return a deep copy of the fully-merged voxel-engine config — built-in defaults, overlaid with `WORLD_SETTINGS.[voxel].*`, overlaid with runtime overrides installed via `voxelConfig.set`. Mutating the result has no effect on live state.

**Returns** `{ [string]: any }`

## tools/voxelConfig/get {#tools-voxelconfig-get}

```lua
voxelConfig.get(key: string) -> any
```

Read a voxel-engine config value by dotted key. Resolution order: runtime overrides first, then `WORLD_SETTINGS.[voxel].*`, then built-in defaults. Raises if the key is unknown.

**Parameters**

- `key` `string`

**Returns** `any`

```lua
"paths.shapes"
"chunkSize"
```

## tools/voxelConfig/reset {#tools-voxelconfig-reset}

```lua
voxelConfig.reset(key?: string)
```

Clear one or all runtime overrides installed via `voxelConfig.set`. With a key, clears just that key; with no argument, clears every runtime override. Raises if `key` is a string but not a known key.

**Parameters**

- `key` `string` _(optional)_

```lua
"paths.shapes"  -- revert a single override
-- revert every runtime override
```

## tools/voxelConfig/set {#tools-voxelconfig-set}

```lua
voxelConfig.set(key: string, value: any)
```

Install a runtime override for a voxel-engine config key. Runtime overrides take precedence over `WORLD_SETTINGS.[voxel]` and built-in defaults. Raises if the key is unknown.

**Parameters**

- `key` `string`
- `value` `any` _(optional)_

```lua
"paths.shapes", "/my/game/voxel/props"
"chunkSize", 16
```

## tools/voxelEngine/composite {#tools-voxelengine-composite}

```lua
voxelEngine.composite(opts: CompositeOpts)
```

Kitbash new props from existing templates. Builds a composite with per-child offset + rotation, then saves in one of three forms: (a) flattened `.voxbin` via `op='saveBaked'`, (b) live entity hierarchy via `op='explode'`, (c) reusable asset bundle via `op='saveAsBundle'`. Handles are plain Lua tables returned by `op='new'`; they carry their own methods (`:addChild`, `:saveBaked`, `:explode`, ...) so callers can compose fluently or route every step through this tool.

**Parameters**

- `opts` `CompositeOpts`

```lua
{ op = 'new', name = 'car' }
{ op = 'addChild', handle = c, template = 'car_body' }
{ op = 'addChild', handle = c, template = 'car_wheel', offset = {2, 0, 2} }
{ op = 'saveBaked', handle = c }
{ op = 'saveAsBundle', handle = c, bundleName = 'my_vehicle' }
```

## tools/voxelEngine/destroy {#tools-voxelengine-destroy}

```lua
voxelEngine.destroy(target?: (Handle | string))
```

Despawn a voxel entity (world or shape). Single-purpose generic tool — `target` is a handle, `"world"`, an entity id, or an entity name. Shape handles return their `componentName = "VoxelShape"`; world target `"world"` resolves the singleton.

**Parameters**

- `target` `(Handle | string)` _(optional)_

```lua
'world'
myShapeHandle
'ent_42'
```

## tools/voxelEngine/edit {#tools-voxelengine-edit}

```lua
voxelEngine.edit(opts: EditOpts) -> any
```

Generic mutating block operations on worlds or shapes. One tool dispatches to every write op — `setBlock`, fills, `mirror`, `translate`, `pasteRegion`, `flush`, `clear`. Pick behavior via `opts.op`, target via `opts.target`. Target is resolved via `shared.resolveTarget` and defaults to the singleton VoxelWorld.

**Parameters**

- `opts` `EditOpts`

**Returns** `any`

```lua
{ op = 'set', target = 'world', x = 0, y = 0, z = 0, block = 'stone' }
{ op = 'fillBox', target = 'myShape', min = {0,0,0}, max = {3,3,3}, block = 'red' }
{ op = 'mirror', target = shape, axis = 'x' }
```

## tools/voxelEngine/palette {#tools-voxelengine-palette}

```lua
voxelEngine.palette(opts: PaletteOpts) -> any
```

Palette operations for voxel worlds or shapes. One tool dispatches to `set` / `add` / `list`. Target is resolved via `shared.resolveTarget` and defaults to the singleton VoxelWorld.

**Parameters**

- `opts` `PaletteOpts`

**Returns** `any`

```lua
{ op = 'set', target = 'world', palette = { grass = {0.2,0.7,0.2}, dirt = {0.5,0.3,0.15} } }
{ op = 'add', target = myShape, name = 'gold', color = {1.0,0.9,0.2} }
{ op = 'list', target = 'world' }
```

## tools/voxelEngine/persist {#tools-voxelengine-persist}

```lua
voxelEngine.persist(opts: PersistOpts) -> any
```

Voxbin + template lifecycle ops. One tool dispatches to `save` / `bake` / `saveAsTemplate` / `deleteTemplate` / `copyTemplate` / `captureArea`. Target is resolved via `shared.resolveTarget` where applicable.

**Parameters**

- `opts` `PersistOpts`

**Returns** `any`

```lua
{ op = 'saveAsTemplate', target = myShape, name = 'redcube' }
{ op = 'captureArea', box = { min={0,0,0}, max={16,4,16} }, saveAs = 'scene1' }
{ op = 'deleteTemplate', name = 'obsolete' }
```

## tools/voxelEngine/query {#tools-voxelengine-query}

```lua
voxelEngine.query(opts: QueryOpts) -> any
```

Generic read-only query operations on voxel worlds or shapes. One tool dispatches to every read op — block lookup, neighbors, region iteration, count by name, raycast, sphere/line queries, region copy, bounds, list templates. Pick behavior via `opts.op`. Target is resolved via `shared.resolveTarget` and defaults to the singleton VoxelWorld.

**Parameters**

- `opts` `QueryOpts`

**Returns** `any`

```lua
{ op = 'getBlock', target = 'world', x = 0, y = 0, z = 0 }
{ op = 'raycast', target = 'world', origin = {0,10,0}, dir = {0,-1,0}, maxDist = 100 }
{ op = 'listTemplates' }
```

## tools/voxelEngine/spawn {#tools-voxelengine-spawn}

```lua
voxelEngine.spawn(opts: SpawnOpts) -> Handle | { Handle }
```

Generic voxel entity creation + placement. One tool, many scenarios — pick behavior via `opts.op`. Covers world creation, single-shape creation, template instantiation, handle cloning, bulk scatter across a region, and single stamped placement. Returns a handle (or array of handles for scatter).

**Parameters**

- `opts` `SpawnOpts`

**Returns** `Handle | { Handle }`

```lua
{ op = 'world', size = {32,16,32}, palette = { grass = {0.2,0.7,0.2} } }
{ op = 'shape', size = {4,4,4}, palette = { red = {1,0,0} }, position = {0,2,0} }
{ op = 'template', name = 'redcube', position = {10,0,0} }
{ op = 'scatter', template = 'redcube', region = { box = { min={0,0,0}, max={20,0,20} } }, count = 8, seed = 42 }
```

## tools/voxelEngine/transform {#tools-voxelengine-transform}

```lua
voxelEngine.transform(opts: TransformOpts) -> any
```

Generic entity-transform + snap operations for voxel entities (worlds or shapes). One tool dispatches to `setPosition` / `setRotation` / `setScale` / `setParent` / `snapToWorld` plus getter ops `getPosition` / `getRotation`. Target is resolved via `shared.resolveTarget` and defaults to the singleton VoxelWorld.

**Parameters**

- `opts` `TransformOpts`

**Returns** `any`

```lua
{ op = 'setPosition', target = myShape, x = 5, y = 0, z = 5 }
{ op = 'setRotation', target = myShape, x = 0, y = 1.57, z = 0 }
{ op = 'snapToWorld', target = myShape }
```

## tools/wld/edit {#tools-wld-edit}

```lua
wld.edit() -> EditResult
```

Return to edit mode. Leaving play would lose live changes the scene doesn't carry, so the engine's safeguard refuses while any of them are unaccepted, and names what they are. List them with the sceneAuthoring.changes tool and settle them with sceneAuthoring.acceptChanges or sceneAuthoring.rejectChanges — either takes everything or a selection — then call this again.

**Returns** `EditResult`

## tools/wld/hideLayer {#tools-wld-hidelayer}

```lua
wld.hideLayer(name: string) -> ()
```

Hide a scene layer. Keeps the layer's entities in the world (they are not despawned) but stops them rendering. Use `wld.showLayer` to make them visible again, or `wld.unloadLayer` to despawn them.

**Parameters**

- `name` `string`

**Returns** `()`

```lua
"ui"
```

## tools/wld/info {#tools-wld-info}

```lua
wld.info() -> WorldInfo
```

Return a snapshot of the current world's high-level state — the bound world name, current mode (`"edit"` / `"play"`), the active scene layer, and the list of saved scene snapshots in this world.

**Returns** `WorldInfo`

## tools/wld/listWorlds {#tools-wld-listworlds}

```lua
wld.listWorlds() -> { string }
```

List worlds saved on disk. Reads `/zero/worlds` via the VFS and returns each directory entry's `name`. Empty array if no worlds have been persisted.

**Returns** `{ string }`

## tools/wld/loadLayer {#tools-wld-loadlayer}

```lua
wld.loadLayer(name: string, opts?: LoadLayerOpts) -> SceneLayer
```

Load a scene as an additive layer alongside the current scene. Thin wrapper over `layers.load(name, opts)`.

**Parameters**

- `name` `string`
- `opts` `LoadLayerOpts` _(optional)_

**Returns** `SceneLayer`

```lua
"ui"
"props", { origin = {0, 0, 10} }
```

## tools/wld/mode {#tools-wld-mode}

```lua
wld.mode() -> "edit" | "play"
```

Get the engine's current mode. Returns `"edit"` when in edit mode (scripts + physics paused), `"play"` when in play mode. Inverse pair: `wld.play` / `wld.edit`.

**Returns** `"edit" | "play"`

## tools/wld/play {#tools-wld-play}

```lua
wld.play() -> ()
```

Enter play mode. Delegates to `engine.mode = "play"`.

**Returns** `()`

## tools/wld/promoteAndSwitch {#tools-wld-promoteandswitch}

```lua
wld.promoteAndSwitch(mode: "edit" | "play") -> PromoteAndSwitchResult
```

Promote pending play-mode source-file edits (everything accepting the "edits" group would promote), then switch the engine to `mode` — one call replacing the changes() / acceptChanges("edits") / wld.edit()-or-wld.play() sequence. With zero pending edits the promote is a no-op and the mode switch still happens. Entity changes are untouched: a flip to edit with unaccepted entity changes stops at the leave-play safeguard, exactly as `wld.edit()` does.

**Parameters**

- `mode` `"edit" | "play"`

**Returns** `PromoteAndSwitchResult`

```lua
"edit"
"play"
```

## tools/wld/showLayer {#tools-wld-showlayer}

```lua
wld.showLayer(name: string) -> ()
```

Show a previously-hidden scene layer. Restores rendering of the layer's entities. Inverse of `wld.hideLayer`.

**Parameters**

- `name` `string`

**Returns** `()`

```lua
"ui"
```

## tools/wld/unloadLayer {#tools-wld-unloadlayer}

```lua
wld.unloadLayer(name: string)
```

Unload an additive scene layer. Despawns the layer's entities entirely — to keep the entities but stop rendering them, use `wld.hideLayer` instead.

**Parameters**

- `name` `string`

```lua
"props"
```

## tools/workflow/answer {#tools-workflow-answer}

```lua
workflow.answer(request: string, value: any) -> boolean
```

Answer a request a workflow is waiting on. The run resumes from where it parked and carries on with whatever it decides comes next. Pass the value the request asked for — where the request carried a schema, that is the shape it expects back.

**Parameters**

- `request` `string`
- `value` `any` _(optional)_

**Returns** `boolean`

```lua
{ request = "call-f6641ff8", value = { title = "Tornado Run", ready = true } }
```

## tools/workflow/fail {#tools-workflow-fail}

```lua
workflow.fail(request: string, reason: string) -> boolean
```

Tell a workflow that a request cannot be answered, and why. The run decides what that means — a stage may stop, or carry on without it. Use this rather than leaving a claim unanswered: an abandoned claim leaves the run parked until the claim expires.

**Parameters**

- `request` `string`
- `reason` `string`

**Returns** `boolean`

```lua
{ request = "call-f6641ff8", reason = "the world it names does not exist" }
```

## tools/workflow/list {#tools-workflow-list}

```lua
workflow.list() -> { { [string]: any } }
```

List the workflows in this world: what each one is for, when to reach for it, the phases it moves through, and what it expects in `args`. Start one by name with `workflow.start`. A job with a workflow is a job whose shape somebody already worked out — look here before deciding to do a multi-step job by hand.

**Returns** `{ { [string]: any } }`

## tools/workflow/next {#tools-workflow-next}

```lua
workflow.next(runner: string, run?: string) -> { [string]: any }?
```

Take the next request a workflow run is waiting on, claimed for you in the same call. Returns nothing when no workflow is waiting.

**Parameters**

- `runner` `string`
- `run` `string` _(optional)_

**Returns** `{ [string]: any }?`

```lua
"me"
```

## tools/workflow/start {#tools-workflow-start}

```lua
workflow.start(name: string, args?: { [string]: any }) -> { [string]: any }
```

Start a workflow by name. The workflow decides what happens and in what order; your job is to answer the questions it stops to ask, with `workflow.next` and `workflow.answer`. Returns the run id — the run is already going, and its first request arrives on your next tool response.

**Parameters**

- `name` `string`
- `args` `{ [string]: any }` _(optional)_

**Returns** `{ [string]: any }`

```lua
"lighthouse"
{ name = "lighthouse", args = { instance = "wfrt" } }
```

## tools/workflow/status {#tools-workflow-status}

```lua
workflow.status(run?: string) -> any
```

Report what workflow runs are doing: which phase each is in, what it is waiting on and who claimed it, how many questions it has asked and had answered, and what it returned once it is done. Give a run id for one run, or nothing for all of them.

**Parameters**

- `run` `string` _(optional)_

**Returns** `any`

```lua
"run-098c453d"
```

## tools/worldValidation/check {#tools-worldvalidation-check}

```lua
worldValidation.check(opts?: CheckOpts) -> ZmToolResult
```

Validate YOUR world's authored content — every script and asset under `/source/` EXCEPT `/source/libs/`. The cargo-check equivalent for a Zero world. Imported libraries and engine builtins are NOT scanned by default (they're not yours to validate, and walking the whole builtin tree is slow); pass `opts.scope` to widen — `"libraries"`, `"library:<name>"`, or `"all"` (world + libraries). Read-only. The report is on `.data`; a one-line health summary is on `.stdout`.

**Parameters**

- `opts` `CheckOpts` _(optional)_

**Returns** `ZmToolResult`

```lua
-- your world only
{ severity = "error", includePlaceholders = false }
{ scope = "all" }                    -- world + imported libraries
```

## tools/worldValidation/checkLibraries {#tools-worldvalidation-checklibraries}

```lua
worldValidation.checkLibraries(opts?: FilterOpts) -> ZmToolResult
```

Validate every imported library under `/source/libs/`. World content is skipped — the report's `world` bucket is nil. The `libraries` map carries one entry per library directory.

**Parameters**

- `opts` `FilterOpts` _(optional)_

**Returns** `ZmToolResult`

```lua
{ severity = "warning" }
```

## tools/worldValidation/checkLibrary {#tools-worldvalidation-checklibrary}

```lua
worldValidation.checkLibrary(name: string, opts?: FilterOpts) -> ZmToolResult
```

Validate one named library under `/source/libs/<name>/`. If the library does not exist the report carries a single `library.missing` error. Use when you want to isolate the health of one dependency.

**Parameters**

- `name` `string`
- `opts` `FilterOpts` _(optional)_

**Returns** `ZmToolResult`

```lua
"@builtin"
"@mylib", { severity = "error" }
```

## tools/worldValidation/checkWorld {#tools-worldvalidation-checkworld}

```lua
worldValidation.checkWorld(opts?: FilterOpts) -> ZmToolResult
```

Validate ONLY the world's authored content (everything under `/source/` except `/source/libs/`). Use to verify your own code without library noise. The report's `world` bucket is populated; the `libraries` map is empty.

**Parameters**

- `opts` `FilterOpts` _(optional)_

**Returns** `ZmToolResult`

```lua
{ severity = "error" }
```

## tools/worldValidation/report {#tools-worldvalidation-report}

```lua
worldValidation.report(opts?: ReportOpts) -> ZmToolResult
```

Generate a validation report with structured filtering + formatting options. `opts.scope` selects which bucket to scan (default `"world"` — YOUR content, never the imported libraries / engine builtins); `opts.format` picks the `.stdout` rendering; `opts.savePath` writes the rendered report to a VFS path (format inferred from extension when not set).

**Parameters**

- `opts` `ReportOpts` _(optional)_

**Returns** `ZmToolResult`

```lua
{ scope = "world", format = "markdown" }
{ savePath = "/source/.validation/last-run.md" }
{ severity = "error", includePlaceholders = false, format = "human" }
```

## tools/worldValidation/summary {#tools-worldvalidation-summary}

```lua
worldValidation.summary(opts?: CheckOpts) -> ZmToolResult
```

Validate your world and return ONLY the one-line health summary — no problem list. Cheap to call when all you need is a yes/no health gate. Same scope rules as worldValidation.check: YOUR content under `/source/` (excluding `/source/libs/`) by default; pass `opts.scope` to widen. Most useful filter here: `{ includePlaceholders = false }`.

**Parameters**

- `opts` `CheckOpts` _(optional)_

**Returns** `ZmToolResult`

```lua
{ includePlaceholders = false }
```

## tools/zm/add {#tools-zm-add}

```lua
zm.add(paths: string | { string }, opts?: { stage: string? }) -> boolean
```

Stage one or more paths' manifest rows for the next commit via `world.add(path)`. Pass a single VFS path string for the single-arg form, or an array of path strings to stage in one call. The "stage everything" sentinels `.` / `-A` / `--all` (matching the shell `zm add`) route to `world.add_all()`. Empty array / empty string raise an argument error. To stage the caller's whole slice of the dirty set, see `zm.addAll`. Naming a path takes it whoever holds it, which is how a path another staging area holds is handed over. A `world.add` runtime error raises with a clean `zm.add:`-prefixed message. `opts.stage` names one of the caller's own staging areas to stage into, so a commit naming that area carries these paths and leaves every other caller's staged. Omitted, the call stages into the default area every unnamed call shares.

**Parameters**

- `paths` `string | { string }`
- `opts` `{ stage: string? }` _(optional)_

**Returns** `boolean`

```lua
"/source/foo.luau"
{ "/source/a.luau", "/source/b.luau" }
"."  -- stage every dirty path
```

## tools/zm/addAll {#tools-zm-addall}

```lua
zm.addAll(opts?: { stage: string? }) -> boolean
```

Stage the dirty manifest rows this caller can claim, via `world.add_all()`. Equivalent to `zm add --all` / `zm add .`. Use when the caller's whole slice of the dirty set should land in the next commit; for selective staging, call `zm.add(paths)`. The working tree is one per branch and staging areas are not, so a path another area already holds stays with that caller — `zm.add(path)` names one and takes it. `opts.stage` names one of the caller's own staging areas to stage into. A runtime error raises with a clean `zm.addAll:`-prefixed message.

**Parameters**

- `opts` `{ stage: string? }` _(optional)_

**Returns** `boolean`

## tools/zm/branch {#tools-zm-branch}

```lua
zm.branch(name: string, opts?: ZmBranchOpts) -> string
```

Create a branch — `git branch <name> [<start>]`. The branch starts at `opts.from` (defaults to the session branch's HEAD) and gets its own working tree, materialized from that commit. The session stays on its current branch; move onto the new one with `zm.checkout`, which returns once the branch's content has landed. `zm.branches` lists what a world has. Merge it back later with `zm.merge`.

**Parameters**

- `name` `string`
- `opts` `ZmBranchOpts` _(optional)_

**Returns** `string`

```lua
"feature"
```

## tools/zm/branches {#tools-zm-branches}

```lua
zm.branches() -> { { branch: string, commit_id: string, current: boolean } }
```

List the world's branches — `git branch --list`. Each row carries the branch name, the commit its head names, and whether this session is on it. Sorted by name. A branch exists for everyone in the world; which one you are on is yours alone, so at most one row is marked current and it says nothing about where anybody else is working.

**Returns** `{ { branch: string, commit_id: string, current: boolean } }`

## tools/zm/checkout {#tools-zm-checkout}

```lua
zm.checkout(branch: string) -> string
```

Switch this session to another branch — `git checkout <branch>`. The branch must already exist; `zm.branch` creates one and `zm.branches` lists what a world has. The tree is replaced by the branch's own content. Which branch this session is on is this session's alone; the branch itself is shared. Uncommitted work is not at risk — it already has its row on the branch it was written against. Returns once the branch's content has landed, so a commit made straight afterwards targets the branch you asked for rather than the one you left. A branch that does not finish loading leaves the session back on the branch it came from.

**Parameters**

- `branch` `string`

**Returns** `string`

```lua
"feature"
```

## tools/zm/commit {#tools-zm-commit}

```lua
zm.commit(message: string, opts?: { stage: string? }) -> string
```

Materialize the staged tree as a new commit via `world.commit(message)`. Git semantics — commits ONLY what's already staged. The reducer auto-deletes the stage row on success so a subsequent `zm.commit` opens a fresh stage. A failure (empty stage, dangling dep, …) raises with a clean `zm.commit:`-prefixed message. `opts.stage` materialises one of the caller's own staging areas, so the commit carries the paths staged under that name and leaves every other caller's staged.

**Parameters**

- `message` `string`
- `opts` `{ stage: string? }` _(optional)_

**Returns** `string`

```lua
"add character controller component"
```

## tools/zm/contribute {#tools-zm-contribute}

```lua
zm.contribute(opts?: ZmContributeOpts) -> { worldVcs.ContributeOutcome }
```

Send improvements to installed content back upstream — `git subtree push` ending in a pull request. For each targeted origin world: the diverging subtree is remapped to the origin's canonical paths, three-way merged against the origin's CURRENT content (a region the origin also changed becomes a local conflict with markers to resolve first), pushed as a `contrib-<id>` branch in the origin world, and opened as a pull request there. By default the pull request is merged immediately when you have write access (otherwise it is left open for review), and the local fork re-syncs so the asset no longer reads as ahead. Discover what is ahead first with `zm.forkStatus`.

**Parameters**

- `opts` `ZmContributeOpts` _(optional)_

**Returns** `{ worldVcs.ContributeOutcome }`

## tools/zm/create {#tools-zm-create}

```lua
zm.create(title: string, opts?: ZmCreateOpts) -> ZmCreateResult
```

Creates a new world owned by the caller.

**Parameters**

- `title` `string`
- `opts` `ZmCreateOpts` _(optional)_

**Returns** `ZmCreateResult`

```lua
"Test Library A"
"Combat", { visibility = "private" }
```

## tools/zm/deleteBranch {#tools-zm-deletebranch}

```lua
zm.deleteBranch(branch: string) -> boolean
```

Delete a branch — `git branch -D <name>`. Drops the branch and the working tree it owns; its commits are left alone, since deleting a branch drops the name and the tree under it rather than rewriting history. Uncommitted work on that branch goes with it and is NOT recoverable from trash, so the first call refuses and hands back the affirmation needed to go through with it — affirm with `zm.affirm`. Refuses the branch this session is on (check out another first) and the world's last branch.

**Parameters**

- `branch` `string`

**Returns** `boolean`

```lua
"feature"
```

## tools/zm/discard {#tools-zm-discard}

```lua
zm.discard(paths: string | { string }) -> ZmDiscardResult
```

Discard the working edits on one or more paths, taking each back to what it was staged or committed as — the `git restore <path>` shape, and the shell `zm discard`. The stage is the baseline where the path is staged, the last commit where it is not, and where it is neither there is nothing to come back to, so the path goes away. Staging is left exactly as it was: `zm.unstage` is the verb that changes it. A path that goes back to a committed version snapshots the discarded bytes to trash first and is recoverable via `zm.restore`. Pass a single VFS path, an array of paths, or `.` / `-A` / `--all` for every dirty path — which leaves nothing dirty behind. A path with no working edits has nothing to discard and is reported under `skipped`. Returns `{ reverted, skipped }` path lists. A runtime error raises with a clean `zm.discard:`-prefixed message.

**Parameters**

- `paths` `string | { string }`

**Returns** `ZmDiscardResult`

```lua
"/source/foo.luau"
"."  -- discard every working edit in the world
```

## tools/zm/fetch {#tools-zm-fetch}

```lua
zm.fetch(branch?: string) -> worldVcs.FetchResult
```

Update the `origin/<branch>` remote-tracking ref — `git fetch`. Mirrors the world's ZeroMind branch head into local commit history (no working-tree change) and reports how the session branch relates to it: `behind` origin commits to pull, `ahead` local commits to push, `diverged` when both. A stale pin or an out-of-band ZeroMind change shows up as `behind` — reconcile with `zm.pull()`.

**Parameters**

- `branch` `string` _(optional)_

**Returns** `worldVcs.FetchResult`

## tools/zm/forkStatus {#tools-zm-forkstatus}

```lua
zm.forkStatus() -> { worldVcs.ForkStatus }
```

Per-asset "ahead of origin" — the fork analogue of git status against an upstream. Every installed (pulled) asset whose content diverges from its pinned origin is listed, partitioned by the TRUE origin world it was pulled from (a nested dependency carries the world that authored it, not the intermediary it arrived through). Use this to decide what belongs upstream, then `zm.contribute`.

**Returns** `{ worldVcs.ForkStatus }`

## tools/zm/installAsset {#tools-zm-installasset}

```lua
zm.installAsset(guid: string, opts?: ZmInstallAssetOpts) -> worldVcs.InstallAssetResult
```

Installs the specified asset into the world.

**Parameters**

- `guid` `string`
- `opts` `ZmInstallAssetOpts` _(optional)_

**Returns** `worldVcs.InstallAssetResult`

```lua
asset_guid
asset_guid, { path = "/source/imported/foo" }
```

## tools/zm/installLib {#tools-zm-installlib}

```lua
zm.installLib(guid: string, opts?: ZmInstallLibOpts) -> worldVcs.InstallLibraryResult
```

Installs the specified world as a library. After install, it is reachable as `@<name>::<path>` from this world's scripts (e.g. `require("@combat::weapons.sword")`).

**Parameters**

- `guid` `string`
- `opts` `ZmInstallLibOpts` _(optional)_

**Returns** `worldVcs.InstallLibraryResult`

```lua
world_guid
world_guid, { as = "combat" }
world_guid, { as = "@combat", version = commit_id }
```

## tools/zm/list {#tools-zm-list}

```lua
zm.list() -> { ZmWorldEntry }
```

List every world the authenticated user has access to (owner / maintainer / contributor / viewer). Calls `world.list()` which wraps the spacetime `list_my_worlds` procedure (forwards ZeroMind's `GET /v1/me/worlds`). Returns the array of `ZmWorldEntry` records, sorted by title. A runtime error raises with a clean `zm.list:`-prefixed message.

**Returns** `{ ZmWorldEntry }`

## tools/zm/log {#tools-zm-log}

```lua
zm.log(opts?: ZmLogOpts) -> { worldVcs.CommitRow }
```

List commits on the active branch, newest first. Returns the raw commit-info array from `world.log` (each entry carries `commit_id` + `message`). Pass `opts.limit` to cap the count; omit for default 50, pass 0 for unlimited. A runtime error raises with a clean `zm.log:`-prefixed message.

**Parameters**

- `opts` `ZmLogOpts` _(optional)_

**Returns** `{ worldVcs.CommitRow }`

```lua
{ limit = 10 }
```

## tools/zm/merge {#tools-zm-merge}

```lua
zm.merge(sourceBranch: string) -> worldVcs.MergeResult
```

Merge another branch into the session branch — `git merge <source>`. The merge runs locally in the world and is abortable with `zm.mergeAbort`; nothing reaches ZeroMind until the result is pushed. Requires a clean working tree (commit or stash first). A clean merge lands a two-parent merge commit and the merged content appears in the working tree. On conflicts, git-style markers are written into each conflicting text file and the cleanly-merged remainder is applied as working-tree changes; `zm.status` lists the unmerged paths. Resolve each path (edit out the markers / rewrite / remove the file), then `zm.add` + `zm.commit` — that commit records the merge (second parent = the source head) and clears the unmerged set. Push with `zm.push` to land the merge in ZeroMind as a two-parent commit.

**Parameters**

- `sourceBranch` `string`

**Returns** `worldVcs.MergeResult`

```lua
"feature"
```

## tools/zm/mergeAbort {#tools-zm-mergeabort}

```lua
zm.mergeAbort() -> string
```

Abort the in-progress merge — `git merge --abort`. Clears the unmerged set and restores the working tree to its pre-merge state (the branch head never moved during a conflicted merge). Errors when no merge is in progress.

**Returns** `string`

```lua
-- back out of a conflicted zm.merge
```

## tools/zm/prConflicts {#tools-zm-prconflicts}

```lua
zm.prConflicts(worldGuid?: string, number: number) -> any
```

Read what stands between a pull request and a merge. Returns the mergeability verdict, the merge base, both heads, and one entry per conflicting path. A conflicting TEXT path carries `marked_text`: the same `<<<<<<<` / `=======` / `>>>>>>>` rendering a merge leaves in a working tree, source and target laid against their common ancestor. Resolve a path by writing the settled bytes back to it and committing on the source branch — the request re-analyses on the next read. A binary path carries the two sides' hashes and no text, so pick a side. A mergeable request returns an empty conflict list. `zm.prView` says HOW MANY conflicts there are; this says WHAT they are.

**Parameters**

- `worldGuid` `string` _(optional)_
- `number` `number`

**Returns** `any`

```lua
nil, 1
"11111111-2222-3333-4444-555555555555", 3
```

## tools/zm/prList {#tools-zm-prlist}

```lua
zm.prList(worldGuid?: string, number?: number) -> any
```

List pull requests in a world. To read ONE request — its diff, mergeability and conflict count — use `zm.prView`. A fork's outgoing pull requests live in the world they target, so read them there.

**Parameters**

- `worldGuid` `string` _(optional)_
- `number` `number` _(optional)_

**Returns** `any`

```lua
"11111111-2222-3333-4444-555555555555", 3
```

## tools/zm/prMerge {#tools-zm-prmerge}

```lua
zm.prMerge(worldGuid: string, number: number, strategy?: ("merge" | "squash" | "fast_forward")) -> any
```

Merge a pull request — the agent-side merge button. Read the request with `zm.prView` first: it reports what the request changes and whether it merges cleanly.

**Parameters**

- `worldGuid` `string`
- `number` `number`
- `strategy` `("merge" | "squash" | "fast_forward")` _(optional)_

**Returns** `any`

```lua
"11111111-2222-3333-4444-555555555555", 3
"11111111-2222-3333-4444-555555555555", 3, "squash"
```

## tools/zm/prOpen {#tools-zm-propen}

```lua
zm.prOpen(opts: any) -> any
```

Open a pull request — propose the work on one `(world, branch)` pair to another. From a fork the target defaults to the world it was forked from, so opening one with just a title proposes your work upstream; in an ordinary world the target is the same world, giving a branch → `main` request. The pull request lives in — and is numbered by — the world it targets, and that is the world `zm.prList` reads.

**Parameters**

- `opts` `any` _(optional)_

**Returns** `any`

```lua
{ title = "fix the door hinge" }
{ title = "ship the HUD", sourceBranch = "hud", targetBranch = "main" }
```

## tools/zm/prView {#tools-zm-prview}

```lua
zm.prView(worldGuid?: string, number: number) -> any
```

Read one pull request in full — `gh pr view`. Returns the record plus a LIVE re-analysis against the current branch heads: `mergeability` (`clean` / `conflicts` / `fast_forwardable` / `up_to_date` / `unrelated`), `conflict_count`, and `diff` — every path the request adds, modifies or deletes, with its checksums. Read this before merging: it is what tells you WHAT the request changes.

**Parameters**

- `worldGuid` `string` _(optional)_
- `number` `number`

**Returns** `any`

```lua
nil, 1
"11111111-2222-3333-4444-555555555555", 3
```

## tools/zm/preview {#tools-zm-preview}

```lua
zm.preview(guid: string, opts?: ZmPreviewOpts) -> worldVcs.PreviewResult
```

Preview what installing an asset WOULD write, without writing anything. Returns the resolved closure tree: every file and dependency with its computed dest_path, size, content hash, and dependency reason, plus rollup totals and a `truncated` flag. Use it to vet a package before `zm.installAsset`.

**Parameters**

- `guid` `string`
- `opts` `ZmPreviewOpts` _(optional)_

**Returns** `worldVcs.PreviewResult`

```lua
asset_guid
asset_guid, { path = "/source/imported/foo" }
```

## tools/zm/pull {#tools-zm-pull}

```lua
zm.pull(ref?: string, opts?: ZmPullOpts) -> worldVcs.PullResult | worldVcs.PullAssetResult
```

With no `ref`: fetch + reconcile the session branch with its ZeroMind origin — `git pull`. Strictly behind fast-forwards; diverged three-way merges the origin head with the same conflict/marker flow as `zm.merge` (resolve, then `zm.add` + `zm.commit`; abortable with `zm.mergeAbort`). Returns `{ status, commit?, conflicts? }`. With a `ref`: pull upstream updates into a previously-installed asset, three-way merging every file against your local edits. Files you never touched fast-forward to the upstream version; files where your edits and the upstream edits don't overlap merge cleanly; files that clash land as conflicts (markers written) that block staging until you resolve them with `zm.resolve(path, "ours"|"theirs")`. Discover what has updates first with `zm.updates`. Returns `{ merged, conflicts, added, pruned }`.

**Parameters**

- `ref` `string` _(optional)_
- `opts` `ZmPullOpts` _(optional)_

**Returns** `worldVcs.PullResult | worldVcs.PullAssetResult`

```lua
asset_guid
"/source/greeter.module"
```

## tools/zm/push {#tools-zm-push}

```lua
zm.push() -> ZmPushResult
```

Publish unpushed commits to ZeroMind via `world.push()`. Mirrors `git push` semantics — no-args form walks the parent chain from HEAD back to the most-recently-pushed ancestor and pushes the entire unpushed stack oldest-first. The engine reads the caller's JWT from `UserCredential` / `ZERO_USER_TOKEN`; Luau scripts never see the token. A refused publish (parent-chain gap, ACL denial, network error) raises with a clean `zm.push:`-prefixed message.

**Returns** `ZmPushResult`

## tools/zm/reset {#tools-zm-reset}

```lua
zm.reset(commit: string) -> string?
```

Rewind the branch HEAD to `commit` in one shot. Non-destructive — the commits rewound past stay in storage and each becomes a trash entry recoverable via `zm.restore` (in chain order, oldest first). Errors when `commit` is not an ancestor of the current HEAD. Returns a summary listing what was rewound. Use `zm.log` to discover commit ids. A runtime error raises with a clean `zm.reset:`-prefixed message.

**Parameters**

- `commit` `string`

**Returns** `string?`

```lua
"01HABC..."
```

## tools/zm/resolve {#tools-zm-resolve}

```lua
zm.resolve(path: string, choice: "ours" | "theirs") -> string
```

Resolve a conflicted pulled path by choosing a side. `"theirs"` rewrites the file to the upstream version and advances the origin pin; `"ours"` keeps your local bytes. Applies to conflicts `zm.pull` left behind (listed by `world.conflicts()` and held back from staging). For a text conflict you can also just edit the `<<<<<<<` / `=======` / `>>>>>>>` markers out of the file by hand — staging it then counts as resolved. Branch-merge conflicts from `zm.merge` resolve by editing the marker'd file, not through this tool.

**Parameters**

- `path` `string`
- `choice` `"ours" | "theirs"`

**Returns** `string`

```lua
"/source/zerojs.package/init.luau", "theirs"
```

## tools/zm/restore {#tools-zm-restore}

```lua
zm.restore(handle: number | string) -> boolean
```

Recover a trashed entry by its handle — the `row_id` shown by `zm.trash`. This is the undo for the destructive verbs: orphaned commits from `zm.reset`, discarded file edits from `zm.discard`, dropped stashes, and removed files. Reset orphans must be restored in chain order (oldest first). A runtime error raises with a clean `zm.restore:`-prefixed message.

**Parameters**

- `handle` `number | string`

**Returns** `boolean`

```lua
42
```

## tools/zm/status {#tools-zm-status}

```lua
zm.status(opts?: { stage: string? }) -> ZmStatusResult
```

Show staged + dirty paths in the active world. Returns the raw `{ dirty, staged, untracked }` table from `world.vcsStatus`, with `branch` and `head` folded in. `blockers` is `world.publishBlockers()`: every reason a push would refuse, one entry per class (script errors, unmet content requirements, references that can't be statically pinned), each naming its offending subjects and the one remedy for them. It reads the whole world, the scope a push gates on, so a blocker committed earlier is named here too; `zm push` publishes once the list is empty. A runtime error from the underlying `world.*` reads raises with a clean `zm.status:`-prefixed message. `opts.stage` reports one of the caller's own staging areas in place of the default one every unnamed call shares. The dirty and untracked sets are the world's working tree and read the same whichever area is named.

**Parameters**

- `opts` `{ stage: string? }` _(optional)_

**Returns** `ZmStatusResult`

## tools/zm/swap {#tools-zm-swap}

```lua
zm.swap(guid: string, opts?: ZmSwapOpts) -> string
```

Switches the engine over to a different world. After this returns, every `vfs.*` / `zm.*` call targets the new world.

**Parameters**

- `guid` `string`
- `opts` `ZmSwapOpts` _(optional)_

**Returns** `string`

```lua
"11111111-2222-3333-4444-555555555555"
guid, { version = "passthrough:01HXAMPLECOMMITID" }
guid, { mode = "play" }
```

## tools/zm/trash {#tools-zm-trash}

```lua
zm.trash() -> { worldVcs.TrashRow }
```

List recently-destroyed items in the world's trash: orphaned commits (from `zm.reset`), discarded file edits (from `zm.discard`), dropped stashes, and removed files. Trash is a shared, world-wide safety net with a retention window. Each row's `row_id` is the handle you pass to `zm.restore`. A runtime error raises with a clean `zm.trash:`-prefixed message.

**Returns** `{ worldVcs.TrashRow }`

## tools/zm/uninstallLib {#tools-zm-uninstalllib}

```lua
zm.uninstallLib(name: string) -> string
```

Removes a previously installed library from this world.

**Parameters**

- `name` `string`

**Returns** `string`

```lua
"combat"
"@combat"
```

## tools/zm/unstage {#tools-zm-unstage}

```lua
zm.unstage(paths: string | { string }, opts?: { stage: string? }) -> boolean
```

Remove one or more paths from the staging area via `world.unstage(path)`, leaving live manifest dirty state untouched — the inverse of `zm.add`. Pass a single VFS path string, or an array of path strings to unstage in one call. The "unstage everything" sentinels `.` / `-A` / `--all` drop the whole staging area via `world.discard()`. Empty array / empty string raise an argument error. A `world.unstage` runtime error raises with a clean `zm.unstage:`-prefixed message. `opts.stage` names one of the caller's own staging areas to act on. Omitted, the call acts on the default area every unnamed call shares.

**Parameters**

- `paths` `string | { string }`
- `opts` `{ stage: string? }` _(optional)_

**Returns** `boolean`

```lua
"/source/foo.luau"
{ "/source/a.luau", "/source/b.luau" }
"."  -- drop the whole staging area
```

## tools/zm/updates {#tools-zm-updates}

```lua
zm.updates() -> { worldVcs.UpdateReport }
```

List installed content that has upstream updates available. Every asset you installed keeps a live link to the world it came from; this reports which of them the origin has changed since your pinned version, so you know what has a fresh version without guessing. Each entry names the origin root asset and the local paths whose upstream content moved. Empty result = everything installed is up to date. Apply an update with `zm.pull`.

**Returns** `{ worldVcs.UpdateReport }`

```lua
-- what needs updating?
```
