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
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
nameis the tool's<toolbox>.<tool>identity; invoke it with theuse_toolMCP tool (toolbox+tool+ positionalargs), the form each entry'sexamplesare 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 (*_normal → normal_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,
tools/procgen/search
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.