Log inGet started

tools

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

The tools namespace — 376 functions.

globals/tools/create

globals/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.

globals/tools/createToolbox

globals/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.

globals/tools/delete

globals/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).

globals/tools/get

globals/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.

globals/tools/list

globals/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.

globals/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 + positional args), the form each entry's examples are rendered in.

globals/tools/toolboxes

globals/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.

globals/tools/use

globals/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/MaterialAuthor/composeFolder

MaterialAuthor.composeFolder(folder: string, opts?: { [string]: any }) -> any

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>.

tools/MaterialAuthor/duplicate

MaterialAuthor.duplicate(source: any, newName: string, overrides?: { [string]: any }) -> any

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>.

tools/MaterialAuthor/fromColor

MaterialAuthor.fromColor(color: any, opts?: { [string]: any }) -> any

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>.

tools/MaterialAuthor/fromTexture

MaterialAuthor.fromTexture(texture: any, opts?: { [string]: any }) -> any

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 (*_normalnormal_texture, …), else base_color_texture. Returns the new AssetRef<material>.

tools/animation/play

animation.play(animation: any, entityArg: any, opts?: table) -> table

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.

tools/api/docs

api.docs(...) -> string

Browse engine VFS documentation under /zero/docs. No args

tools/api/searchDocs

api.searchDocs(query: string) -> string

Search docs across every category under /zero/docs. The query is

tools/appearance/replaceWithContent

appearance.replaceWithContent(targets, source: string, opts?: ReplaceOpts)

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. dryRun = true resolves everything and returns the plan (which entities would be removed, how big each subtree is, what would spawn) without mutating.

tools/appearance/setModel

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

Assign a model (mesh) to the Model / SkinnedModel-bearing entities

tools/appearance/swapMaterials

appearance.swapMaterials(material: any, opts?: SwapMaterialsOpts) -> AssignSummary

Batch-assign one material across entity hierarchies in a single call.

tools/appearance/swapShader

appearance.swapShader(roots: any, shader: string | table, opts?: AppearanceOpts)

Convert every material in use across entity hierarchies to a new

tools/assets/describe

assets.describe(assetName: string, opts?: { [string]: any }) -> string

Describe an asset in agent-readable markdown: identity, type,

tools/assets/fetchInUse

assets.fetchInUse(targets?: any) -> FetchSummary

Fetch the unique set of asset references in use on the targeted

tools/assets/find

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

Find every asset in the entire project whose identity contains

tools/assets/generatePreviews

assets.generatePreviews(opts?: { [string]: any }) -> 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.

tools/assets/previewStatus

assets.previewStatus() -> { [string]: any }

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.

tools/assets/users

assets.users(assetArg: any) -> UsersSummary

Find every entity in the active layer that uses an asset. Give the

tools/cam/frame

cam.frame(entityId: string, opts?: FrameOpts)

Position a camera to frame a target entity from a distance

tools/cam/get

cam.get(camId?: string) -> CamGetResult

Read camera info — transform + fov. Defaults to the active

tools/cam/lookAt

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

Point a camera at a world position. Computes a look-at

tools/cam/spawn

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

Spawn a camera entity with a Camera component at a

tools/capture/cleanup

capture.cleanup() -> number

Clear every file in /source/tmp/capture/. Walks the directory

tools/capture/fromEntity

capture.fromEntity(opts: CaptureEntityOpts) -> (string?, string?)

Render the scene framed on an entity (or several) and write the

tools/capture/fromPosition

capture.fromPosition(opts: CapturePositionOpts) -> (string?, string?)

Render the scene from a world-space position and write the PNG

tools/capture/oneshot

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

Spawn an ephemeral offscreen camera configured from opts,

tools/capture/viewport

capture.viewport(path?: string) -> (string?, string?)

Capture the current viewport and write the PNG before returning —

tools/characterController/install

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

tools/characterController/preset

characterController.preset(entityId: string, presetName: string) -> PresetResult

tools/characterController/uninstall

characterController.uninstall(entityId: string) -> UninstallResult

tools/debug/inspect

debug.inspect(target: 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.

tools/debug/playSession

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

Show or hide the debug overlay in play mode. Edit mode always shows the

tools/debug/problems

debug.problems(opts?: { level: string?, limit: number?, all: boolean?, peek: boolean? })

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.

tools/debug/scope

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

Limit every enabled debug category to a set of entities, so the overlay

tools/debug/set

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

Show or hide a debug-visualization overlay category. The overlay is

tools/debug/state

debug.state() -> { categories: { [string]: boolean }, scope: { string }, count: number, playSession: boolean, mode: string }

Read the current debug-visualization state: which categories draw, the

tools/diagnostics/errors

diagnostics.errors(entityFilter?: (string | { entity: string?, id: string? })) -> { EntityErrors }

Get all component/entity errors in the scene. With no

tools/diagnostics/summary

diagnostics.summary() -> SceneSummary

Get a summary of the current scene. Enumerates entities via

tools/diagnostics/validate

diagnostics.validate() -> ValidationReport

Validate scene integrity: check for broken materials,

tools/entityOps/addComponents

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). 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"), or a VFS path (resolved to its identity before anything mutates). 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.

tools/entityOps/component

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.

tools/entityOps/fromAsset

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 is instantiated through its own :instantiate() when it has one, else a legible error. position / rotation / scale place it; synced replicates gameplay edits to peers.

tools/entityOps/modify

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

Change entity state — one tool for delete / enable / disable / hide / show / temporary / reparent / rename, over one or many entities. Targets are entity NAMES or ids (arrays and scene.find records work too). 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, hidden drives visibility + exclusion from queries and serialization, 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). Returns one { id, name, applied } record per entity.

tools/entityOps/roots

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). 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.

tools/entityOps/spawn

entityOps.spawn(components: SpawnComponents, opts?: SpawnOpts) -> SpawnResult

Spawn an entity from a template — you name the components it carries and their field values, plus a transform. Components is a { [ComponentName] = { field = value } } map: each 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. temporary, hidden, synced (replicate gameplay edits to peers), and attributes control lifecycle. Pass an empty components map to spawn a bare entity. Returns the spawned id, name, and the component leaf names added.

tools/entityOps/swap

entityOps.swap(target: string, replacement: string, opts?: SwapOpts)

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 or ids. dryRun = true reports exactly what would move and what would be removed, without mutating.

tools/gui/click

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

Simulate a widget click by callback ID. Forwards to

tools/gui/getTree

gui.getTree(name: string)

Get the widget tree of a screen (for reading state). Forwards

tools/gui/hide

gui.hide(name: string)

Hide a screen. Forwards to ui.hideScreen(name) — the

tools/gui/panel

gui.panel(name: string, widgets: { any }, opts?: { layer: number?, visible: boolean?, position: any? })

Create and show a UI screen from a widget list. Registers the

tools/gui/scroll

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

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

tools/gui/setTheme

gui.setTheme(name: string)

Set the active UI theme. Forwards to ui.setTheme(name)

tools/gui/show

gui.show(name: string)

Show a screen. Forwards to ui.showScreen(name). The screen

tools/gui/themes

gui.themes() -> { string }

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

tools/gui/toggle

gui.toggle(name: string)

Toggle a screen's visibility. Walks ui.listScreens(),

tools/gui/update

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

Update a specific widget inside a screen by widget ID. Reads

tools/gui/widgetProps

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

Get property definitions for a widget type. Forwards to

tools/gui/widgetTypes

gui.widgetTypes() -> { string }

List available widget types. Forwards to

tools/importers/await

importers.await(path: string, timeoutSecs?: number) -> { [string]: any }

Wait until the import job for path reaches a terminal state —

tools/importers/cancel

importers.cancel(path?: string) -> { [string]: any }

Cancel a queued source before it imports, or every queued source at

tools/importers/explain

importers.explain(path: string) -> { [string]: any }

Explain what the importer system would do with path right now,

tools/importers/imported

importers.imported() -> { count: number, assets: { { [string]: any } } }

List every imported asset in the world with its source and the importer

tools/importers/list

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

List every registered importer with its name and identity. Importers

tools/importers/queue

importers.queue() -> { [string]: any }

The live import backlog: queued sources first (still waiting behind the

tools/importers/run

importers.run(target: any, opts?: { [string]: any }) -> { [string]: any }

Import/reimport one target, many targets, or a folder. A produced asset

tools/importers/status

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

List recent import jobs, newest first, with the live queue depth.

tools/lib/has

lib.has(path: string) -> boolean

Check if a library asset exists at a path. Thin wrapper

tools/lib/list

lib.list(assetType?: string)

List available library content. No args returns every

tools/lighting/addLight

lighting.addLight(name: string, position: Vec3, opts?: AddLightOpts) -> { [string]: any }

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.

tools/lighting/get

lighting.get() -> { [string]: any }

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).

tools/lighting/removeLight

lighting.removeLight(name: string) -> { [string]: any }

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

tools/lighting/setAmbient

lighting.setAmbient(opts: AmbientOpts) -> { [string]: any }

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

tools/lighting/setLight

lighting.setLight(name: string, opts: SetLightOpts) -> { [string]: any }

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 lights and the sun/ambient entities.

tools/lighting/setSky

lighting.setSky(sky?: string, opts?: SkyOpts) -> { [string]: any }

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.

tools/lighting/setSun

lighting.setSun(opts: SunOpts) -> { [string]: any }

Set the scene's directional sun light. Modifies the canonical "sun" entity, spawning it when the scene has none. Read-merge-write: ONLY the fields you pass change — { intensity = 2 } keeps the authored direction and color.

tools/lighting/setup

lighting.setup(opts: SetupOpts) -> { [string]: any }

Set up the whole scene lighting rig in one call. Only provided sections change. sun/ambient are partial updates on the canonical entities; 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.

tools/lightmap/attach

lightmap.attach(entityId: string, opts?: LightmapOpts) -> LightmapReport

One-shot: add the Lightmap component (replacing existing

tools/lightmap/bake

lightmap.bake(entityId: string, opts?: LightmapOpts) -> LightmapReport

Bake the per-entity lightmap. Accepts an opts table that

tools/lightmap/bakeAll

lightmap.bakeAll(opts?: LightmapOpts) -> { LightmapBakeAllEntry }

Bake global illumination — indirect / bounced light + soft

tools/lightmap/clear

lightmap.clear(entityId: string) -> LightmapClearResult

Tear down lightmap resources on an entity (frees the

tools/notices/list

notices.list() -> { pending: { { [string]: any } }, suppressed: { { key: string, count: number } } }

Show the currently pending notice keys and the suppressed set. Each

tools/notices/suppress

notices.suppress(key: string) -> { suppressed: string }

Mute a notice key you have acknowledged. It keeps counting (visible in

tools/notices/unsuppress

notices.unsuppress(key: string) -> { unsuppressed: string }

Un-mute a notice key you previously suppressed, so it renders on a tool

tools/phys/addBody

phys.addBody(id: string, bodyType?: string, opts?: BodyOpts) -> string

Add Physics + Collider components to an entity in one call.

tools/phys/addConstraint

phys.addConstraint(entityId: string, constraintType: string, targetEntityId: string, opts?: table) -> string

Add a transform constraint to an entity. Constraint types:

tools/phys/addJoint

phys.addJoint(entityIdA: string, entityIdB: string | table, opts?: table) -> string

Add a physics joint between two entities. Both must have

tools/phys/ignoreCollision

phys.ignoreCollision(entityIdA: string, entityIdB: string, ignore?: boolean) -> string

Set whether two entities ignore collisions between each

tools/phys/lockRotation

phys.lockRotation(entityId: string, lockX: boolean, lockY: boolean, lockZ: boolean) -> string

Lock rotation axes on an entity's rigid body. Each flag is

tools/phys/lockTranslation

phys.lockTranslation(entityId: string, lockX: boolean, lockY: boolean, lockZ: boolean) -> string

Lock translation axes on an entity's rigid body. Each flag

tools/phys/removeBody

phys.removeBody(id: string) -> string

Remove Physics + Collider components from an entity. Strips

tools/phys/removeConstraint

phys.removeConstraint(entityId: string, index?: number) -> string

Remove transform constraints from an entity. Pass an

tools/phys/removeJoint

phys.removeJoint(entityId: string) -> string

Remove all joints from an entity. Thin wrapper over

tools/phys/setBodyType

phys.setBodyType(entityId: string, bodyType: "dynamic" | "kinematic" | "static") -> string

Change a rigid body's type at runtime. Preserves mass,

tools/phys/setCollisionGroups

phys.setCollisionGroups(entityId: string, membership: number, filter: number) -> string

Set collision group bitmasks on an entity's colliders. The

tools/phys/spawnDynamic

phys.spawnDynamic(name: string, url?: string, x?: number, y?: number, z?: number, opts?: SpawnOpts) -> string

Spawn an entity with a Model + dynamic Physics body in one

tools/phys/spawnStatic

phys.spawnStatic(name: string, url?: string, x?: number, y?: number, z?: number, opts?: SpawnOpts) -> string

Spawn an entity with a Model + static collider in one call.

tools/pixelArt/add

pixelArt.add(opts)

tools/pixelArt/addFrame

pixelArt.addFrame(opts)

tools/pixelArt/anim

pixelArt.anim(opts: { op: string, [string]: any }) -> any

tools/pixelArt/bounds

pixelArt.bounds(opts)

tools/pixelArt/circle

pixelArt.circle(opts)

tools/pixelArt/clear

pixelArt.clear(opts)

tools/pixelArt/clone

pixelArt.clone(opts)

tools/pixelArt/countByName

pixelArt.countByName(opts)

tools/pixelArt/create

pixelArt.create(opts)

tools/pixelArt/deleteTemplate

pixelArt.deleteTemplate(opts)

tools/pixelArt/destroy

pixelArt.destroy(target: any)

tools/pixelArt/draw

pixelArt.draw(opts: { op: string, [string]: any })

tools/pixelArt/duplicateFrame

pixelArt.duplicateFrame(opts)

tools/pixelArt/ellipse

pixelArt.ellipse(opts)

tools/pixelArt/fillCircle

pixelArt.fillCircle(opts)

tools/pixelArt/fillRect

pixelArt.fillRect(opts)

tools/pixelArt/frameCount

pixelArt.frameCount(opts)

tools/pixelArt/getPixel

pixelArt.getPixel(opts)

tools/pixelArt/line

pixelArt.line(opts)

tools/pixelArt/listTemplates

pixelArt.listTemplates(_opts)

tools/pixelArt/palette

pixelArt.palette(opts: { op: string, [string]: any })

tools/pixelArt/persist

pixelArt.persist(opts: { op: string, [string]: any }) -> any

tools/pixelArt/pixel

pixelArt.pixel(opts)

tools/pixelArt/play

pixelArt.play(opts)

tools/pixelArt/query

pixelArt.query(opts: { op: string, [string]: any }) -> any

tools/pixelArt/rect

pixelArt.rect(opts)

tools/pixelArt/removeFrame

pixelArt.removeFrame(opts)

tools/pixelArt/rows

pixelArt.rows(opts)

tools/pixelArt/saveAsTemplate

pixelArt.saveAsTemplate(opts)

tools/pixelArt/set

pixelArt.set(opts)

tools/pixelArt/setActiveFrame

pixelArt.setActiveFrame(opts)

tools/pixelArt/setFps

pixelArt.setFps(opts)

tools/pixelArt/spawn

pixelArt.spawn(opts: { op: string, [string]: any }) -> any

tools/pixelArt/stop

pixelArt.stop(opts)

tools/pixelArt/template

pixelArt.template(opts)

tools/pp/add

pp.add(name: string, opts?: table) -> string

Add a post-processing effect. Use a built-in preset name

tools/pp/list

pp.list() -> { string }

List the currently-active post-processing effects.

tools/pp/presets

pp.presets() -> {string}

List the built-in preset effect names sorted

tools/pp/remove

pp.remove(name: string)

Remove a post-processing effect by name. Forwards to

tools/pp/setEnabled

pp.setEnabled(name: string, enabled: boolean)

Enable or disable a post-processing effect without

tools/primitives/cube

primitives.cube(name: string, x?: number, y?: number, z?: number, opts?: CubeOpts) -> string

Spawn a visual cube at a position. No physics by default

tools/primitives/ground

primitives.ground(name: string, x?: number, y?: number, z?: number, color?: Rgba, size?: number) -> string

Spawn a flat ground plane with optional color tint and

tools/primitives/groundHex

primitives.groundHex(name: string, x?: number, y?: number, z?: number, color?: Rgba) -> string

Spawn a hexagonal ground tile with optional color tint.

tools/primitives/tree

primitives.tree(name: string, x?: number, y?: number, z?: number, preset?: string, seed?: number) -> string

Spawn a procedural tree from a preset. Includes static

tools/procgen/adjust

procgen.adjust(args: AdjustArgs)

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.

tools/procgen/apply

procgen.apply(args: { entity: string })

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.

tools/procgen/bake

procgen.bake(args: BakeArgs)

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/Template 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.

tools/procgen/build

procgen.build(args: BuildArgs)

Compile a .proc.lua graph-builder source into a serialized .procGraph

tools/procgen/cancel

procgen.cancel(args: { entity: string })

Cancel the in-flight cook on an entity's Generator. The previous output stays; the next param change cooks fresh.

tools/procgen/collapse

procgen.collapse(args: { graph: string, nodes: { string }, name: string, out: string? })

Extract nodes out of a .procGraph graph into a new .procGraph subgraph named name, replacing them in the source with one node referencing the child by typed ref. Every ref crossing the extracted cluster's boundary becomes a declared child input (outside producer -> extracted node) or a declared child output (extracted node -> outside consumer); identical boundary sources/sinks share one socket. The source graph evaluates to the same result before and after — this is the inverse of flatten.

tools/procgen/cooks

procgen.cooks()

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.

tools/procgen/diff

procgen.diff(args: DiffArgs)

Compare two .procGraph graphs and report changed/added/removed nodes plus per-node op/param/input deltas (structural, via the content hash).

tools/procgen/explain

procgen.explain(args: ExplainArgs)

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.

tools/procgen/find

procgen.find(args?: { name: string?, tag: string?, category: string?, produces: string? })

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.

tools/procgen/flatten

procgen.flatten(args: { graph: any, out: string? })

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.

tools/procgen/generators

procgen.generators()

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).

tools/procgen/inspect

procgen.inspect(args: InspectArgs)

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).

tools/procgen/modify

procgen.modify(args: ModifyArgs)

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 }.

tools/procgen/patch

procgen.patch(args: PatchArgs)

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.

tools/procgen/preview

procgen.preview(args: PreviewArgs)

Evaluate a .procGraph graph target, visualize it (any output type), render it,

procgen.search(args?: SearchArgs)

Search the procedural-graph library — every .procGraph asset (via asset.list("procGraph")) filtered by a free-text query (matched against name/id/description), a tag, and/or a category. Returns the matching asset summaries.

tools/procgen/spawn

procgen.spawn(args: SpawnArgs)

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.

tools/procgen/stats

procgen.stats(args: StatsArgs)

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).

tools/procgen/trace

procgen.trace(args: TraceArgs)

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.

tools/procgen/tweak

procgen.tweak(args: TweakArgs)

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).

tools/procgen/validate

procgen.validate(args: ValidateArgs)

Type-check a .procGraph graph: missing inputs, type mismatches, cycles, invalid composite bindings. Returns the diagnostics list.

tools/profiler/compare

profiler.compare(before?: string, after?: string) -> string

Diff two recordings (made with record): the change in avg/p90

tools/profiler/flamegraph

profiler.flamegraph(seconds?: number, mode?: string, label?: string) -> string

Sample the Luau call stack to find the hottest code paths — the depth

tools/profiler/frame

profiler.frame(source?: string, which?: string, minMs?: number) -> string

The current frame's time as a self-accounting tree: schedule -> system ->

tools/profiler/gpu

profiler.gpu(n?: number) -> string

GPU pass timings of the most recent resolved frame, from GPU

tools/profiler/hits

profiler.hits(label?: string, spikeMs?: number) -> string

Drain the frames a record-mode watch caught and report them as a

tools/profiler/hotspots

profiler.hotspots(n?: number, source?: string, which?: string) -> string

Rank the frame's costs by SELF time (a node's own cost, excluding its

tools/profiler/memory

profiler.memory(n?: number) -> string

The Luau VM's memory: total heap + GC state, then the components

tools/profiler/record

profiler.record(action?: string, label?: string, spikeMs?: number) -> string

Start / stop / check a background profiling recording that spans a play

tools/profiler/retro

profiler.retro(seconds?: number, label?: string, spikeMs?: number) -> string

Retroactively read the ring buffer's last seconds of frames (default:

tools/profiler/ring

profiler.ring(action?: string, seconds?: number) -> string

Control the retroactive ring buffer — an always-recording, bounded

tools/profiler/scripts

profiler.scripts(n?: number, minMs?: number, type_?: string) -> string

Rank components by update(dt) cost — the content view of where the

tools/profiler/tasks

profiler.tasks(n?: number, minMs?: number) -> string

Rank components by the time the scheduler spent resuming their

tools/profiler/watch

profiler.watch(ceilingMs: any, mode?: string, excludeAgent?: boolean) -> string

Arm a frame-time watchdog that catches bad frames without you having to

tools/sc/addComponent

sc.addComponent(ids: IdList, typeName: string, data?: table)

Add component to one or many entities.

tools/sc/clear

sc.clear()

Clear scene (despawn all non-camera entities).

tools/sc/clearVisuals

sc.clearVisuals(ids: IdList)

Clear tint + outline from one or many entities.

tools/sc/despawn

sc.despawn(ids: IdList)

Despawn one or many entities.

tools/sc/move

sc.move(ids: IdList, dx: number, dy: number, dz: number)

Move entities by offset with smooth visual transition.

tools/sc/outline

sc.outline(ids: IdList, r: number, g: number, b: number, intensity?: number)

Set outline on one or many entities.

tools/sc/quantize

sc.quantize(ids: IdList, opts?: QuantizeOpts)

Snap transform to grid. Each axis is optional.

tools/sc/removeComponent

sc.removeComponent(ids: IdList, typeName: string)

Remove component from one or many entities.

tools/sc/replaceWithAsset

sc.replaceWithAsset(target: string, source: AssetRef<bundle|mesh>, opts?: { [string]: any }) -> 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.

tools/sc/setParent

sc.setParent(ids: IdList, parentId: string)

Parent one or many entities to a single parent.

tools/sc/spawnCamera

sc.spawnCamera(name: string, ...) -> string

Spawn a camera at position.

tools/sc/spawnCircle

sc.spawnCircle(model: string, name: string, count: number, opts?: CircleOpts) -> { string }

Spawn entities in a circle. Returns array of IDs.

tools/sc/spawnGrid

sc.spawnGrid(model: string, name: string, cols: number, rows: number, opts?: GridOpts) -> { string }

Spawn a grid of entities. Returns array of IDs.

tools/sc/spawnLine

sc.spawnLine(model: string, name: string, count: number, opts?: LineOpts) -> { string }

Spawn entities in a line. Returns array of IDs.

tools/sc/spawnModel

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.

tools/sc/spawnPhysics

sc.spawnPhysics(name: string, model: string, ...) -> string

Spawn entity with model + dynamic physics. Shorthand for sc.spawnModel with physics = "dynamic".

tools/sc/spawnText

sc.spawnText(name: string, content: string, ...) -> string

Spawn 3D text at position.

tools/sc/tint

sc.tint(ids: IdList, r: number, g: number, b: number, blend?: number)

Set tint color on one or many entities.

tools/sc/transform

sc.transform(ids: IdList, opts?: TransformOpts)

Set transform on one or many entities. All fields optional. Smooth visual transition by default.

tools/sc/unparent

sc.unparent(ids: IdList)

Unparent one or many entities (make roots).

tools/scene/find

scene.find(query: shared.FindQuery) -> 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: 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. substring (default true) matches name parts case-insensitively — a short term like "muzzle" reaches "muzzle_light_ent_620d…"; set false for exact. Hidden entities are excluded unless includeHidden. 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). Returns every hit with its FULL canonical id + name.

tools/scene/list

scene.list() -> { any }

List scene snapshots available in the current world.

tools/scene/load

scene.load(name: string) -> LoadResult

Load a scene snapshot via layers.load(name), which loads it

tools/scene/migrate_v6_to_v7

scene.migrate_v6_to_v7(opts: any) -> any

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).

tools/scene/player

scene.player(config?: PlayerConfig)

The scene's player setup, in one tool. No argument: report the active scene's player intent plus every PlayerPrototype (with its body/camera wiring) and PlayerSpawn. "none": the scene goes playerless — every PlayerPrototype subtree and PlayerSpawn is removed. "spawns": a player spawns per joining user — when the scene has no PlayerPrototype yet, the default setup is authored (humanoid body, third-person camera, a spawn at the origin). A PlayerRoles TABLE wires specific entities into the prototype (implying "spawns", authoring the default setup first when missing): body names the entity each joining user becomes, camera names the entity carrying the player's Camera — each is moved under the prototype keeping its local transform, the matching ref is repointed, and the entity it replaces is removed with its subtree (cameras are additionally scoped OwnerOnly). prototype picks one when the scene has several. Setting anything saves the scene.

tools/scene/replace

scene.replace(source: string, opts?: ReplaceOpts)

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.

tools/scene/save

scene.save(name?: string) -> SaveResult

Save the loaded scene named name back to its own scene.json,

tools/scene/summary

scene.summary() -> SceneSummary

Scene overview: entity count, root count + list, active camera.

tools/scene/tree

scene.tree(target: any, 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.

tools/sceneAuthoring/acceptChanges

sceneAuthoring.acceptChanges(selection?: shared.Selection, opts?: AcceptOpts) -> AcceptResult

Accept changes shown by tools.use("sceneAuthoring", "changes") — all of them,

tools/sceneAuthoring/changes

sceneAuthoring.changes(opts?: ChangesOpts) -> string

Review what exists live but is not yet part of the scene: entities

tools/sceneAuthoring/rejectChanges

sceneAuthoring.rejectChanges(selection?: shared.Selection) -> RejectResult

Reject changes shown by tools.use("sceneAuthoring", "changes") — all of them,

tools/services/balance

services.balance() -> (number?, string?)

Your current credit balance — the pool every generation spends from. Check it against an operation's cost (from services.list) before starting. Returns (credits, nil) when signed in, (nil, reason) otherwise.

tools/services/generate

services.generate(service: string, input?: { [string]: any }) -> { [string]: any }

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).

tools/services/jobs

services.jobs() -> { { [string]: any } }

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.)

tools/services/list

services.list() -> { { [string]: any } }

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, operations }, and each operation is { name, description, inputs, produces, cost } — inputs is an ordered array of { name, type, required, desc }, produces is the kind of asset you get back, cost is credits per call. The entry point: call this first, then services.generate(service, input) with what you learn here.

tools/services/outputs

services.outputs() -> { { [string]: any } }

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.

tools/services/status

services.status(id: string) -> { [string]: any }?

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.

tools/sim/click

sim.click(opts?: ClickOpts)

Simulate a mouse click — down + up — at an optional

tools/sim/key

sim.key(key: string, duration?: number)

Tap a key — down + up, optionally held for duration

tools/sim/keyDown

sim.keyDown(key: string)

Hold a key down. Call sim.keyUp to release. Thin

tools/sim/keyUp

sim.keyUp(key: string)

Release a held key. Thin wrapper over

tools/sim/listMacros

sim.listMacros() -> { any }

Enumerate every registered .inputMacro asset in the

tools/sim/lockPointer

sim.lockPointer(locked: boolean)

Set pointer-lock state. The engine treats locked +

tools/sim/macro

sim.macro(refOrEvents: any)

Play a macro of timed input events. Accepts:

tools/sim/mouseDown

sim.mouseDown(button?: number)

Press a mouse button. Buttons follow

tools/sim/mouseMove

sim.mouseMove(x: number, y: number)

Move the cursor to a screen position. Thin wrapper over

tools/sim/mouseUp

sim.mouseUp(button?: number)

Release a mouse button. Buttons match sim.mouseDown:

tools/sim/recordMacro

sim.recordMacro(opts?: RecordMacroOpts) -> ({ MacroEvent }, any)

Capture real input over a window. Polls Zin.state.*

tools/sim/saveMacro

sim.saveMacro(name: string, events: { any }) -> any

Persist a recorded or hand-authored event list to a

tools/sim/scroll

sim.scroll(dy: number)

Simulate scroll wheel. Thin wrapper over Zin.test.scroll.

tools/temp/run

temp.run(code: string, duration?: number) -> RunResult

Execute Luau code and auto-cleanup the entities it creates

tools/terrainSystem/asset

terrainSystem.asset(opts)

Spawn a terrain entity from a .terrain asset.

tools/terrainSystem/cone

terrainSystem.cone(opts)

Radial cone/mountain centred at (x, z) of given height + radius. Cosine-shaped profile so the peak rounds.

tools/terrainSystem/destroy

terrainSystem.destroy(opts: table) -> DestroyResult

Despawn a terrain entity — removes the MeshInstance, drops the cached handle in the component, and frees the entity. The on-disk .terrain asset (if any) is left untouched; only the live world entity is removed.

tools/terrainSystem/edit

terrainSystem.edit(opts: table) -> table

tools/terrainSystem/erode

terrainSystem.erode(opts)

Pseudo-hydraulic erosion — iteratively spreads height from each cell into its lower neighbours. Optional region (x, z, radius) restricts the area; without region the whole map erodes.

tools/terrainSystem/flat

terrainSystem.flat(opts)

Spawn a flat (zero-height) terrain — the canonical starting point for the painter.

tools/terrainSystem/flatten

terrainSystem.flatten(opts)

Pull heights toward target (defaults to the centre sample) inside a radial brush.

tools/terrainSystem/handle

terrainSystem.handle(opts)

Spawn a terrain entity from an already-built handle (from terrainSystem.create / fromNoise).

tools/terrainSystem/height

terrainSystem.height(opts)

Sample the height at world (x, z). Bilinear interpolation.

tools/terrainSystem/info

terrainSystem.info(opts)

Return the terrain handle's metadata table — dimensions, world size, origins, material.

tools/terrainSystem/load

terrainSystem.load(opts)

Load a .terrain asset into a fresh handle.

tools/terrainSystem/lower

terrainSystem.lower(opts)

Lower heights inside a radial brush.

tools/terrainSystem/noise

terrainSystem.noise(opts)

Generate an FBM heightmap, optionally save to a .terrain asset, and spawn the entity.

tools/terrainSystem/normal

terrainSystem.normal(opts)

Sample the surface normal at world (x, z).

tools/terrainSystem/persist

terrainSystem.persist(opts: table) -> table

tools/terrainSystem/plateau

terrainSystem.plateau(opts)

Flatten every cell in a radius to a single target altitude (full replace, not blend). Useful for mesa tops / building pads.

tools/terrainSystem/query

terrainSystem.query(opts: table) -> any

tools/terrainSystem/raise

terrainSystem.raise(opts)

Raise heights inside a radial brush.

tools/terrainSystem/ramp

terrainSystem.ramp(opts)

Linear ramp between two altitudes along a vector — access roads, mesa ramps, dock approaches.

tools/terrainSystem/ridge

terrainSystem.ridge(opts)

Additive mountain ridge along a line segment from (x1, z1) to (x2, z2).

tools/terrainSystem/save

terrainSystem.save(opts)

Persist a terrain handle (or live entity) to a .terrain asset.

tools/terrainSystem/scatter

terrainSystem.scatter(opts: table) -> { string }

Scatter mesh instances on a terrain by rules. Returns the array of placed entity ids.

tools/terrainSystem/set

terrainSystem.set(opts)

Wholesale-replace the heights table. Length must equal width*depth.

tools/terrainSystem/slope

terrainSystem.slope(opts)

Sample the slope (radians) at world (x, z). 0 = flat, π/2 = vertical.

tools/terrainSystem/smooth

terrainSystem.smooth(opts)

3x3 box-blur of heights inside a radial brush.

tools/terrainSystem/spawn

terrainSystem.spawn(opts: table) -> string

tools/terrainSystem/terrace

terrainSystem.terrace(opts)

Quantize heights into stepped levels — the "rice paddy / hillfort" look.

tools/terrainSystem/valley

terrainSystem.valley(opts)

U-shaped valley carved along a line segment.

tools/tests/compare

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.

tools/tests/list

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.

tools/tests/run

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? } 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.

tools/volumeProbe/bake

volumeProbe.bake(entityId: string, opts?: BakeOpts) -> BakeReport

Bake a single probe volume. Reads bounds + resolution +

tools/volumeProbe/bakeAll

volumeProbe.bakeAll(opts?: BakeOpts) -> { BakeAllEntry }

Bake every entity carrying a VolumeProbe component.

tools/volumeProbe/bind

volumeProbe.bind(receiverId: string, volumeEntityId: string, opts?: BindOpts) -> string

Bind a dynamic entity to a probe-volume's SH buffer.

tools/volumeProbe/create

volumeProbe.create(name: string, opts?: table) -> { id: string, report: table? }

Create a volume-probe entity in one call: spawns the entity

tools/volumeProbe/unbind

volumeProbe.unbind(receiverId: string) -> UnbindResult

Restore the prior material on a probe-receiver entity.

tools/voxelConfig/all

voxelConfig.all() -> { [string]: any }

Return a deep copy of the fully-merged voxel-engine config — built-in

tools/voxelConfig/get

voxelConfig.get(key: string) -> any

Read a voxel-engine config value by dotted key. Resolution order:

tools/voxelConfig/reset

voxelConfig.reset(key?: string)

Clear one or all runtime overrides installed via voxelConfig.set.

tools/voxelConfig/set

voxelConfig.set(key: string, value: any)

Install a runtime override for a voxel-engine config key. Runtime

tools/voxelEngine/add

voxelEngine.add(opts)

tools/voxelEngine/addChild

voxelEngine.addChild(opts)

tools/voxelEngine/bake

voxelEngine.bake(opts)

tools/voxelEngine/bounds

voxelEngine.bounds(opts)

tools/voxelEngine/captureArea

voxelEngine.captureArea(opts)

tools/voxelEngine/children

voxelEngine.children(opts)

tools/voxelEngine/clear

voxelEngine.clear(opts)

tools/voxelEngine/clone

voxelEngine.clone(opts)

tools/voxelEngine/composite

voxelEngine.composite(opts: CompositeOpts)

tools/voxelEngine/copyRegion

voxelEngine.copyRegion(opts)

tools/voxelEngine/copyTemplate

voxelEngine.copyTemplate(opts)

tools/voxelEngine/count

voxelEngine.count(opts)

tools/voxelEngine/deleteTemplate

voxelEngine.deleteTemplate(opts)

tools/voxelEngine/destroy

voxelEngine.destroy(target?: (Handle | string))

tools/voxelEngine/edit

voxelEngine.edit(opts: EditOpts) -> any

tools/voxelEngine/explode

voxelEngine.explode(opts)

tools/voxelEngine/fillBox

voxelEngine.fillBox(opts)

tools/voxelEngine/fillCylinder

voxelEngine.fillCylinder(opts)

tools/voxelEngine/fillLine

voxelEngine.fillLine(opts)

tools/voxelEngine/fillSphere

voxelEngine.fillSphere(opts)

tools/voxelEngine/flush

voxelEngine.flush(opts)

tools/voxelEngine/getBlock

voxelEngine.getBlock(opts)

tools/voxelEngine/getNeighbors

voxelEngine.getNeighbors(opts)

tools/voxelEngine/getPosition

voxelEngine.getPosition(opts)

tools/voxelEngine/getRotation

voxelEngine.getRotation(opts)

tools/voxelEngine/line

voxelEngine.line(opts)

tools/voxelEngine/list

voxelEngine.list(opts)

tools/voxelEngine/listTemplates

voxelEngine.listTemplates(_opts)

tools/voxelEngine/mirror

voxelEngine.mirror(opts)

tools/voxelEngine/new

voxelEngine.new(opts)

tools/voxelEngine/palette

voxelEngine.palette(opts: PaletteOpts) -> any

tools/voxelEngine/pasteRegion

voxelEngine.pasteRegion(opts)

tools/voxelEngine/persist

voxelEngine.persist(opts: PersistOpts) -> any

tools/voxelEngine/query

voxelEngine.query(opts: QueryOpts) -> any

tools/voxelEngine/raycast

voxelEngine.raycast(opts)

tools/voxelEngine/region

voxelEngine.region(opts)

tools/voxelEngine/save

voxelEngine.save(opts)

tools/voxelEngine/saveAsBundle

voxelEngine.saveAsBundle(opts)

tools/voxelEngine/saveAsTemplate

voxelEngine.saveAsTemplate(opts)

tools/voxelEngine/saveBaked

voxelEngine.saveBaked(opts)

tools/voxelEngine/saveLinked

voxelEngine.saveLinked(opts)

tools/voxelEngine/scatter

voxelEngine.scatter(opts)

tools/voxelEngine/set

voxelEngine.set(opts)

tools/voxelEngine/setParent

voxelEngine.setParent(opts)

tools/voxelEngine/setPosition

voxelEngine.setPosition(opts)

tools/voxelEngine/setRotation

voxelEngine.setRotation(opts)

tools/voxelEngine/setScale

voxelEngine.setScale(opts)

tools/voxelEngine/shape

voxelEngine.shape(opts)

tools/voxelEngine/snapToWorld

voxelEngine.snapToWorld(opts)

tools/voxelEngine/spawn

voxelEngine.spawn(opts: SpawnOpts) -> Handle | { Handle }

tools/voxelEngine/sphere

voxelEngine.sphere(opts)

tools/voxelEngine/stamp

voxelEngine.stamp(opts)

tools/voxelEngine/template

voxelEngine.template(opts)

tools/voxelEngine/transform

voxelEngine.transform(opts: TransformOpts) -> any

tools/voxelEngine/translate

voxelEngine.translate(opts)

tools/voxelEngine/world

voxelEngine.world(opts)

tools/wld/edit

wld.edit(force?: boolean) -> EditResult

Return to edit mode. Leaving play discards live changes the

tools/wld/hideLayer

wld.hideLayer(name: string)

Hide a scene layer. Keeps the layer's entities in the world

tools/wld/info

wld.info() -> WorldInfo

Return a snapshot of the current world's high-level state —

tools/wld/layers

wld.layers() -> { any }

List currently loaded scene layers. Returns whatever

tools/wld/listWorlds

wld.listWorlds() -> { string }

List worlds saved on disk. Reads /zero/worlds via the VFS

tools/wld/loadLayer

wld.loadLayer(name: string, opts?: LoadLayerOpts) -> any

Load a scene as an additive layer alongside the current

tools/wld/mode

wld.mode() -> "edit" | "play"

Get the engine's current mode. Returns "edit" when in

tools/wld/play

wld.play()

Enter play mode. Delegates to engine.mode = "play".

tools/wld/promoteAndSwitch

wld.promoteAndSwitch(mode: string) -> PromoteAndSwitchResult

Promote pending play-mode source-file edits (everything accepting

tools/wld/showLayer

wld.showLayer(name: string)

Show a previously-hidden scene layer. Restores rendering of

tools/wld/unloadLayer

wld.unloadLayer(name: string) -> nil

Unload an additive scene layer. Despawns the layer's

tools/worldValidation/check

worldValidation.check(opts?: table) -> 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.

tools/worldValidation/checkLibraries

worldValidation.checkLibraries(opts?: table) -> 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.

tools/worldValidation/checkLibrary

worldValidation.checkLibrary(name: string, opts?: table) -> 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.

tools/worldValidation/checkWorld

worldValidation.checkWorld(opts?: table) -> 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.

tools/worldValidation/report

worldValidation.report(opts?: table) -> 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).

tools/worldValidation/summary

worldValidation.summary(opts?: table) -> 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 }.

tools/zm/add

zm.add(paths: string | { string }) -> boolean

Stage one or more paths' manifest rows for the next commit

tools/zm/addAll

zm.addAll() -> boolean

Stage every dirty manifest row via world.add_all().

tools/zm/branch

zm.branch(name: string, opts?: ZmBranchOpts)

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.swap(world.guid(), ...) passing the branch, or world.swap(world.guid(), "", "", "<branch>"). Merge it back later with zm.merge.

tools/zm/commit

zm.commit(message: string)

Materialize the staged tree as a new commit via

tools/zm/contribute

zm.contribute(opts?: ZmContributeOpts)

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.

tools/zm/create

zm.create(title: string, opts?: ZmCreateOpts)

Creates a new world owned by the caller.

tools/zm/discard

zm.discard(paths: string | { string })

Discard uncommitted changes to one or more paths. Per path,

tools/zm/fetch

zm.fetch(branch?: string)

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().

tools/zm/forkStatus

zm.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.

tools/zm/installAsset

zm.installAsset(guid: string, opts?: ZmInstallAssetOpts)

Installs the specified asset into the world.

tools/zm/installLib

zm.installLib(guid: string, opts?: ZmInstallLibOpts)

Installs the specified world as a library. After install,

tools/zm/list

zm.list()

List every world the authenticated user has access to

tools/zm/log

zm.log(opts?: ZmLogOpts)

List commits on the active branch, newest first. Returns

tools/zm/merge

zm.merge(sourceBranch: string)

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.

tools/zm/mergeAbort

zm.mergeAbort()

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.

tools/zm/prList

zm.prList(worldGuid?: string, number?: number)

List pull requests in a world — the ones zm.contribute opens, plus any others on that world.

tools/zm/prMerge

zm.prMerge(worldGuid: string, number: number, strategy?: string)

Merge a pull request — the agent-side merge button.

tools/zm/preview

zm.preview(guid: string, opts?: ZmPreviewOpts)

Preview what installing an asset WOULD write, without writing

tools/zm/pull

zm.pull(ref?: string, opts?: ZmPullOpts)

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 }.

tools/zm/push

zm.push()

Publish unpushed commits to ZeroMind via world.push().

tools/zm/reset

zm.reset(commit: string) -> string?

Rewind the branch HEAD to commit in one shot. Non-destructive

tools/zm/resolve

zm.resolve(path: string, choice: 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.

tools/zm/restore

zm.restore(handle: any) -> boolean

Recover a trashed entry by its handle — the row_id shown by

tools/zm/status

zm.status()

Show staged + dirty paths in the active world. Returns the

tools/zm/swap

zm.swap(guid: string, opts?: ZmSwapOpts)

Switches the engine over to a different world. After this

tools/zm/trash

zm.trash()

List recently-destroyed items in the world's trash: orphaned

tools/zm/uninstallLib

zm.uninstallLib(name: string)

Removes a previously installed library from this world.

tools/zm/unstage

zm.unstage(paths: string | { string }) -> boolean

Remove one or more paths from the staging area via

tools/zm/updates

zm.updates()

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.

  • api
  • reference