# Command reference

> Every command in the Feather 2 catalog, grouped by domain, as exposed to the editor, feather-cli and MCP clients.

Source: https://feather-engine.com/docs/agents/commands/

<!-- Generated by scripts/sync-commands.mjs from `feather-cli catalog`. Do not edit by hand. -->

The catalog currently contains **270 commands** in 63 domains. The same list drives the editor's **Cmd/Ctrl+K** search, `feather-cli` and every MCP client, so a command listed here works identically in all three.

This page is generated from the output of `feather-cli catalog`. Each command's full JSON input schema, including field ranges and enums, is in that output; MCP clients receive the same schemas through tool discovery.

```bash
feather-cli catalog > catalog.json
```

**Read-only** commands never change the document, its revision or its undo history. Every other command is validated, applied atomically and recorded as one undo step, unless its description says otherwise. Commands under `runtime`, `viewport` and `job` need a connected editor; see [Command-line control](/docs/agents/cli/).

:::note
The catalog grows with the engine. Run `feather-cli catalog` against your build for the authoritative list.
:::

## ai

| Command | Description | Read-only |
| --- | --- | --- |
| `ai.assign` | Assign one behaviour tree to exactly one authored object (object_id, including objects in other levels) or combat enemy kind (enemy_id). tree is a tree id; null removes the assignment. Targets and trees must already exist. One atomic undo step. | No |
| `ai.get` | Read authored behaviour-tree AI. With id, returns that complete tree. Without id, returns global settings, tree summaries, object/enemy assignments, ready presets, every node type with its category/parameters/semantics, perception-written blackboard keys and emitted events. Read-only and renderer-free; game.simulate reports live active nodes, perception, paths, blackboards and events. | Yes |
| `ai.remove` | Delete a behaviour tree by id. Refused while an object or enemy is assigned to it; detach those users first with ai.assign and tree:null. One atomic undo step. | No |
| `ai.set` | Patch global AI settings: enabled; max_agents 1..1024 (nearest agents allowed to think); repath_seconds 0.05..10 (route refresh interval for a moving goal). One atomic undo step. | No |
| `ai.upsert` | Create or patch one behaviour tree. value.id is required; name and description label it; perception controls sight_range m, fov_degrees, eye_height m, line_of_sight, close_range m, hearing_range m and memory_seconds; blackboard is up to 64 initial number/bool/text/[x,y,z] values; locomotion names idle/walk/run clips and run_above m/s; root is a strict recursive node tree. Objects merge, but supplied root and blackboard replace completely. preset guard\|coward\|sentry starts from a usable tree. rename_from renames a tree and updates all object/enemy assignments. One atomic undo step. | No |

## ambience

| Command | Description | Read-only |
| --- | --- | --- |
| `ambience.get` | Read the ambience (on out of the box): enabled, wind_trails, wind_trail_color, leaves, leaf_kind, dust_motes, fireflies, firefly_color, butterflies, bird_flocks, ground_birds, bird_scatter_distance and grass {amount, height, width, shape, color (null = match the ground), root_shade, variation, patches, patch_color, patch_scale, translucency, wind, gusts, flowers, flower_colors, push, push_radius, recovery_seconds, cuttable, regrow_seconds, rain_droop, distance}. Presets: ambience.grass_preset. | Yes |
| `ambience.grass_preset` | Give the built-in grass a ready-made look (replaces the grass settings; tweak after with ambience.set {grass}): meadow (default), lush (tall, deep green, flowers), lawn (short and dense), savanna (tall dry gold), autumn (orange and rust), toon (soft bright blades that glow in the sun), alien (wide violet leaves with cyan patches), wildflowers (patches of big flowers in one colour each), forest_floor (short soft leafy undergrowth with weeds, for under trees), highland (tall thin wind-swept grass, big gusts), tundra (short tufts in rust and gold). | No |
| `ambience.set` | Patch the ambience: enabled; amounts 0 (off)..2 (1 = default) for wind_trails (streaks flying with the wind; need wind), leaves (blown on the wind, more in gusts), dust_motes (daylight), fireflies (dusk and night), butterflies (calm dry days), leaf_kind Leaves\|Autumn\|Petals\|Blossoms; wind_trail_color and firefly_color sRGB; wind_trail style {seconds 0.5..12 each flight, length m 0.5..20 visible streak, width m 0.005..0.2, loops 0..1 share that curl a loop, loop_size m 0.1..3, group 1..4 trails flying side by side, height [min, max] m above ground, speed 0.3..5 × wind, follow_ground 0..1 (level .. hug every rise), sway m 0..5 side-to-side wander, glow 0..2 bright head, rest 0..0.9 share of time between flights (sparser)}; bird_flocks 0..8 circling overhead; ground_birds (one flock lands near the player and scatters when they come within bird_scatter_distance 1..40 m, or on blasts). grass (built-in stylized grass around the camera on open terrain: off roads, paint, rivers, flattened plots, water and under buildings): amount 0..2; height and width [min, max] m; shape Sharp\|Soft\|Wide; color sRGB tip or null to match the base terrain layer; root_shade 0..1; variation 0..1 per-blade variety; patches 0..1 of patch_color sized patch_scale m; translucency 0..2 (sun glowing through); wind 0..3 and gusts 0..2 sway; flowers 0..0.3 share with flower_colors [3 sRGB]; push 0..2 and push_radius m (the player, characters, rolling bodies, landed birds and blasts press trails that stand back up over recovery_seconds); cuttable (melee swings, blasts and cut_grass cut it to stubble with clippings) and regrow_seconds (0 never); rain_droop 0..1; distance 10..60 m; tree_harmony 0..1 (generated trees' leaves take on the grass colour where they grow, so grass, trees and biomes share one palette); curvature 0..1 (how far blades arc); shape_mix 0..1 (share of blades in another shape: broad weeds among sharp blades); softness 0..1 (rounded shading across blades); tip_highlight 0..1 with tip_highlight_color sRGB (sun-bleached tips); flower_clusters 0..1 (flowers gather in single-colour patches) and flower_size 0.5..2.5; gust_scale m 4..80 (size of gust fronts rolling over the field); spring 0..1 (trampled grass overshoots and wobbles as it stands up); shots_cut (bullets hitting the ground flick the grass and clip a tuft). Rain, snow, night and strong wind thin or hide each kind; rain makes grass droop and shine, snow whitens and then buries it. Scripts: set_ambience(feature, amount), scatter_birds(), push_grass(x, y, z, radius), cut_grass(x, y, z, radius). Verify with --screenshot; --verify-run reports an ambience section. | No |

## animation

| Command | Description | Read-only |
| --- | --- | --- |
| `animation.clips` | List a model's animation clips read from its GLB: [{name, seconds}]. Use these names in animation graph states, animation.clip and player clips. | Yes |
| `animation.get` | Read animation graphs (state machines over a model's clips). No arguments: a summary of every graph plus the parameters the engine fills in (speed, grounded, vertical_speed for the avatar). id: one full graph. Use animation.clips to list a model's clip names. | Yes |
| `animation.remove` | Delete an animation graph that nothing uses. | No |
| `animation.retarget_auto` | Auto-detect a model's humanoid bone map from its GLB and store it in project.rigs (kind + bones). keep_overrides: true keeps hand-set bones over the detected ones. Returns bones, mapped count and missing required bones. One undo step. | No |
| `animation.retarget_map_get` | Read a model's humanoid rig map, read from its GLB: bones [{name, parent, position (rest, model-space metres), humanoid}], detected (auto-detected canonical VRM bone → rig bone for Synty, Mixamo, VRM/Character Forge and other common skeleton names), override (project.rigs), effective (detected + overrides; used at runtime), missing required bones, kind, hand_ik/look_at for graph-less characters, and every canonical humanoid_bones name. A model whose bone names differ from its clip library's is retargeted through these maps automatically. | Yes |
| `animation.retarget_map_set` | Set a model's humanoid rig map in project.rigs (creates it). asset_id; kind Generic\|Vrm\|Synty\|Mixamo\|Unreal; bones {canonical VRM humanoid name → the rig's bone name} merged into the map (null removes one; e.g. {"leftHand":"Hand_L","hips":"Pelvis"}; names: hips, spine, chest, upperChest, neck, head, left/rightShoulder, UpperArm, LowerArm, Hand, UpperLeg, LowerLeg, Foot, Toes, fingers like leftIndexProximal); hand_ik / look_at (JSON merge, null removes) give IK to characters without an animation graph (enemies, NPCs), same shape as on animation graphs; remove: true deletes the map. Bones not overridden are auto-detected (see animation.retarget_map_get, animation.retarget_auto). One undo step. | No |
| `animation.retarget_preview` | Check retargeting headlessly: sample clip (from source_asset_id's GLB) on target_asset_id at times (seconds; default 5 evenly spaced) through both rig maps with rest-pose (T/A-pose), facing and scale correction (0.01 armatures). Returns scale (target/source hips height), mapped_bones, max/mean_limb_error_degrees (angle between source and target limb directions; under ~5° is a good map), per-sample limb errors and hips/head/hands/feet positions (target model metres vs the scaled source), names_compatible (true = no retarget needed). | Yes |
| `animation.root_motion` | Measure root motion in a model's clips (from its GLB): per clip seconds, delta [x, z] (model-space metres over one loop), distance, speed m/s and yaw_degrees of bone (humanoid name such as hips, or a rig bone such as root; default hips), through the armature's own scale and axes. clips: names to measure (default all). Use it to choose root_motion clips on an animation graph and to check how far a character will travel. | Yes |
| `animation.upsert` | Create or patch an animation graph by id (JSON merge; arrays replace). value {id, name, library_asset_id (model with the clips; null = the object's own model), parameters [{name, kind Float\|Bool\|Trigger, default}], states [{id, clip \| blend [{clip, at}] + blend_param, speed, speed_param, looping, events [{at 0..1, name, sound (project sound), volume}]}], transitions [{from (state id or \*), to, conditions [{param, op Greater\|Less\|GreaterOrEqual\|LessOrEqual\|Equal\|NotEqual\|True\|False, value}], duration (cross-fade s), exit_time (0..1 or null)}], initial, foot_ik (null or {enabled, pelvis, left {hip, knee, foot}, right {hip, knee, foot}, max_adjust m, smoothing}; plants feet on the terrain; defaults are VRM bone names hips/leftUpperLeg/leftLowerLeg/leftFoot), hand_ik (null or {enabled, weight 0..1, smoothing, left/right {upper, lower, hand}, left_target (support hand) / right_target (trigger hand) {enabled, sockets [node names searched under the character, e.g. grip_left on the weapon model], relative_to (bone used when no socket exists, e.g. rightHand), offset [x,y,z] m in that frame, rotation [x,y,z]°, match_rotation 0..1, pole [outward, up, forward] m elbow hint from the shoulder}}; {} gives a rifle grip: the support hand 25 cm up the held weapon's barrel (-Z) from its origin at the pistol grip), look_at (null or {enabled, weight, chain [{bone, weight}] default spine/chest/upperChest/neck/head, max_yaw 0..180°, max_pitch 0..90°, falloff°, smoothing, source Auto (avatar: camera aim; others: the player) \| Player \| Script, range m}), root_motion (null or {enabled, bone (default hips), clips [names that move the character instead of playing in place], rotation}; see animation.root_motion), montages [{id, clip, mask full\|upper\|arms\|custom, bones (custom roots), blend_in s, blend_out s, speed, notifies [{at 0..1, name, sound, volume}], root_motion, release_hands (let go of hand IK, reloads), interruptible}]}. Bone names are humanoid (VRM) names resolved through each model's rig map (animation.retarget_map_get), so they work on Synty, Mixamo, Forge and other common rigs; a model whose bones differ from the clip library's is retargeted automatically. Triggers are consumed by the transition that tests them. Use a graph with scene.update animation.graph, project.player animation_graph (third person), or script anim_graph(id, graph). Scripts: anim_set(id, param, value), anim_trigger(id, name), anim_state(id), play_montage(id, montage_or_clip, #{mask, blend, speed, root_motion}), stop_montage(id[, blend]), montage_playing(id), look_at(id, target), clear_look(id), set_ik_weight(id, feet\|hands\|look, w) (id "player" = the third-person avatar); hook on_anim_event(id, name) also receives montage notifies. rename_from renames and updates users. | No |

## asset

| Command | Description | Read-only |
| --- | --- | --- |
| `asset.import` | Import a self-contained GLB inside the workspace, optionally adding it to the scene. One undo step. | No |
| `asset.remove` | Remove an imported model, texture or sound from the project. Fails with asset_in_use while any object, foliage layer, terrain layer, material or audio setting still refers to it. | No |
| `asset.rename` | Rename an imported model, texture or sound (kind: model\|texture\|sound). | No |

## audio

| Command | Description | Read-only |
| --- | --- | --- |
| `audio.assign` | Put a sound (name or id) on a bus (music, sfx, ambience, voice, ui or a project bus). | No |
| `audio.get` | Read the mixer: master/music/ambience volumes and beds, buses [{name, volume}], cues, ducking, occlusion, reverb_zones, and every sound with its bus. Sound names used anywhere (combat, animation events, play_sound) resolve to a cue of that name first. | Yes |
| `audio.remove` | Remove a bus, cue or reverb zone by name. | No |
| `audio.set` | Patch audio settings with a JSON merge (arrays replace): master_volume, music, music_volume, ambience_day/night, ambience_volume, buses, cues, ducking [{target bus, trigger bus, amount 0..1, attack s, release s}] (target gets quieter while trigger plays), occlusion {enabled, amount 0..1 behind walls, interval s}, reverb_zones. Prefer audio.upsert for one bus, cue or zone. | No |
| `audio.upsert` | Create or patch one mixer item by name. kind bus: {name, volume 0..4}. kind cue: {name, sounds [sound names], volume [min, max], pitch [min, max] 0.25..4, bus, max_instances (0 = unlimited), cooldown s, avoid_repeat} — playing the cue name picks a random sound with random volume and pitch (gunshot and footstep variety). kind reverb_zone: {name, center [x, y, z], radius m, echoes 0..6, delay s, volume 0..1, decay 0..1} — sounds heard while the listener is inside get echo taps. Players set music/effects/voice volumes in the pause menu; scripts fade buses with set_bus_volume(bus, volume, seconds). | No |

## biome

| Command | Description | Read-only |
| --- | --- | --- |
| `biome.get` | Read the open level's biomes: map settings (enabled, seed, scale, blend, warp), each biome's climate point and terrain, regions, biome presets (for biome.upsert), map presets (for biome.set), the level's foliage layer names and texture layer names (what biomes can reference) and valid surfaces. kind biome\|region + id returns one in full. | Yes |
| `biome.query` | See what the biomes do. x, z: the biome there (dominant id, blend weights, climate temperature/moisture, full ground height and the biomes' share, grass density/height multipliers, density multiplier per foliage layer, footstep surface, tags). Always returns a map: an ASCII grid of dominant biomes (one letter each, north up; legend maps letters to ids) of size×size cells over center [x, z] ± radius m (defaults [0,0], 1000, 40) and each biome's coverage share. Use it to tune scale/weight until the world reads well. | Yes |
| `biome.remove` | Delete a biome (kind biome, default; refused while a region places it) or a region (kind region) by id from the open level. | No |
| `biome.set` | Set up the open level's biome map. Biomes are climate regions: two noise fields (temperature, moisture) pick the nearest biomes' climate points, blended at borders; they add height and dress ground, grass and foliage, and set footsteps and gameplay tags. preset (temperate: meadow/forest/highlands/lakeland/scrub; wilderness: those plus cursed, placed only by regions; arid; frozen) replaces the biome list (keeps settings and regions whose biome remains) and points each biome at the level's matching texture layer by name (sand for scrub, rock/cliff for highlands, snow, dirt/mud for cursed, moss for forests). patch deep-merges {enabled, seed (0..4294967295), scale m 20..50000 (typical patch width, default 700), blend 0..1 (border softness), warp 0..1 (ragged borders), regions [{id, biome, center [x,z], radius m, blend m}] (replaces the list)}. clear true removes biomes from the level. Use the Flat landscape base for pure biome terrain; with Meadow the biome height adds on top. | No |
| `biome.upsert` | Create or patch one biome (kind biome, default) or region (kind region) in the open level by value.id. Biome fields: name, color sRGB (map preview), temperature 0..1 and moisture 0..1 (its climate point), weight 0..4 (climate space it claims; 0 = only placed by regions), elevation m -500..2000, relief m 0..1000 (hill height), roughness m 4..10000 (hill size), ridges 0..1 (rolling → sharp ridges), ground_color [r,g,b,strength] sRGB tint of the ground, ground_layer 0..3 + ground_layer_amount 0..1 (lay a texture layer, e.g. sand), grass 0..2 (built-in grass density), grass_height 0.2..3, grass_color [r,g,b,weight], foliage {layer name: multiplier 0..4} and foliage_default 0..4 (density of foliage layers such as Trees from tree.forest; 0 removes them here), surface (footsteps: grass, dirt, stone, wood, water, snow, metal, sand; empty = automatic), tags [ids] for scripts (biome_has_tag). preset (meadow, forest, highlands, scrub, lakeland, snowfield, taiga, cursed) starts a biome from a ready look. Region fields: biome, center [x, z], radius m (fully that biome), blend m (fade). rename_from renames (biome renames update regions). | No |

## blueprint

| Command | Description | Read-only |
| --- | --- | --- |
| `blueprint.add_node` | Add a node to an object's Blueprint (creates an empty enabled graph when it has none). operation is the node JSON from blueprint.nodes, e.g. {"type":"OnStart"}, {"type":"Delay","values":{"Seconds":2}}, {"type":"Call","function":"give_item","arity":2,"values":{"id":"medkit","count":1}}. function targets a Blueprint function graph. Returns node_id and its pins (names and types) for blueprint.connect, plus compile warnings. The whole graph must still compile (e.g. one On Start per graph). One undo step. | No |
| `blueprint.collapse` | Collapse event-graph nodes into a new Blueprint function: wires entering the selection become function inputs, values leaving it become outputs, and one Call Function node replaces the nodes. The selection needs one execution entry and at most one exit; events, Delay and Every stay outside. One undo step. | No |
| `blueprint.comment` | Create or update a comment box (comment {id?, title, position [x,y], size [w,h] px (20..100000), color [r,g,b]?}) or remove one (remove: comment id). Moving a comment in the editor moves the nodes inside it. One undo step. | No |
| `blueprint.connect` | Wire an output pin to an input pin (pin names from blueprint.add_node / blueprint.get / blueprint.nodes). Execution wires (Exec pins: Next, True, False, Then0…, Body, Completed, Tick, A, B, Exit) control order; data wires carry typed values (Number, Bool, String, Vec3, Object, Array; Any accepts all). Incompatible types are rejected. An execution output and a data input take one wire: a new wire replaces the old one (returned in replaced). Cycles of execution are rejected (use Every). One undo step. | No |
| `blueprint.disconnect` | Remove wires: one by edge id, or every wire on a node (or on one of its pins). One undo step. | No |
| `blueprint.function` | Create or change a Blueprint function: a new one starts with a Function Entry wired to a Return node. inputs / outputs replace its typed parameters ([{name, type}], types Number, Bool, String, Vec3, Object, Array, Any; wires to removed parameters are dropped), rename renames it and every call, remove deletes it (rejected while Call Function nodes use it). Build its body with blueprint.add_node/connect using function. Functions run inline where they are called; they cannot contain events, Delay or Every. One undo step. | No |
| `blueprint.get` | Read the authored Blueprint graph and its compiled Rhai source for an object (null if none). | Yes |
| `blueprint.nodes` | The Blueprint node palette: every built-in node (events On Start/On Update/contacts/On Event/Custom Event/On Key; flow Branch, Sequence, ForEach, DoOnce, Gate, FlipFlop, Delay, Every, Reroute; Emit, Emit To, Self, Get Object, Get Tagged, variables, math/compare/logic, Break Vec3, Append; legacy actions) plus a generated Call node for every engine function in script.api (grouped the same way) and this object's Blueprint functions. Each entry has key, label, group, doc, pure, typed inputs/outputs (pin name, type Exec/Number/Bool/String/Vec3/Object/Array/Any, default) and the operation JSON to pass to blueprint.add_node. Filter with query (all words must match label, key, group or doc) and group; page with limit (1..500, default 50) and offset. id adds that object's variables and functions. | Yes |
| `blueprint.remove_node` | Remove a node and its wires. One undo step. | No |
| `blueprint.runtime` | Which Blueprint nodes ran and how often (per-node execution counters compiled into Blueprints). Live editor: the current or last Play session's counts and seconds since each node last ran (the editor highlights recent ones). Headless: runs a fresh renderer-free simulation for frames (1..600, default 120) and returns the counts at the end, plus the simulation's success, errors and logs. Each entry: function (null = event graph), node, count, seconds_since. | Yes |
| `blueprint.selftest` | Test Blueprint nodes headless: every palette node matching query (built-ins and the Call node of every script.api function) is wired from On Start with sample pin values, compiled, checked and run for frames (1..600, default 30) in a small smoke world (self, a tagged "Smoke Target" and the player). Each node is ran (executed and passed execution on), compiled (its event cannot fire headless: contacts, keys), data_error (the engine rejected the placeholder data, e.g. an unknown item; the pins are fine) or failed (compile/check error, wrong argument types or a panic; message and generated source included). Returns counts plus the nodes that did not run; all: true lists every node. Read-only. | Yes |
| `blueprint.set` | Replace an object's Blueprint graph or clear it (null). The graph is compiled and validated before committing. | No |
| `blueprint.set_node` | Change a node: replace its operation, merge inline pin values (null removes one) and/or move it. Wires to pins the node no longer has are dropped and listed. Returns its pins. One undo step. | No |
| `blueprint.to_script` | Convert to Rhai script: compile the object's Blueprint into its Script component (readable Rhai using the same API) and disable the Blueprint (keep_blueprint true keeps both running). An existing enabled script is only replaced with replace_script true. Returns the source. One undo step. | No |

## build

| Command | Description | Read-only |
| --- | --- | --- |
| `build.get` | Read the game build settings: game_name — Name of the game's app and executable. Empty uses the project name; version — Version players see: three numbers, e.g. 1.2.0; build_number 1..1000000 — A number that goes up with every release you ship (macOS CFBundleVersion); bundle_id — Reverse-DNS app id, e.g. com.studio.mygame (macOS bundle id). Empty derives com.feather-engine.&lt;game>; profile Release\|Development — Release is optimised for players; Development compiles fast for quick tests; output_dir — Folder that receives builds, relative to the workspace (the project's folder in the editor); archive (on/off) — Also write a .zip (.tar.gz for Linux) next to the build, ready to upload or share | Yes |
| `build.set` | Patch the game build settings (JSON merge). How the game is packaged for players (`build.get` / `build.set`, the Build window, `game.export`). Fields: game_name — Name of the game's app and executable. Empty uses the project name; version — Version players see: three numbers, e.g. 1.2.0; build_number 1..1000000 — A number that goes up with every release you ship (macOS CFBundleVersion); bundle_id — Reverse-DNS app id, e.g. com.studio.mygame (macOS bundle id). Empty derives com.feather-engine.&lt;game>; profile Release\|Development — Release is optimised for players; Development compiles fast for quick tests; output_dir — Folder that receives builds, relative to the workspace (the project's folder in the editor); archive (on/off) — Also write a .zip (.tar.gz for Linux) next to the build, ready to upload or share. | No |

## city

| Command | Description | Read-only |
| --- | --- | --- |
| `city.generate` | Generate a city of ordinary static objects under one Group (one undo step; delete the group to remove it): a grid of streets, blocks raised on sidewalks and split into lots, buildings of varied heights taller toward downtown with cornice ledges, stepped towers and rooftop clutter (air conditioners, water tanks, antennas), street lamps with glowing heads, and parks with trees. The ground under it is levelled and kept clear of grass and wild trees. Roofs and ledges are walkable, climbable and swingable (traversal.set) and navigation finds them. preset town\|city\|metropolis, then any of: seed, center [x, z], blocks [x, z] 1..24, block_size 16..120 m, street_width 4..40 m, storeys [lowest, highest] 1..120, storey_height 2.5..6 m, downtown 0..1 (tallest in the middle), density 0..1, parks 0..1, rooftops 0..1, setbacks 0..1, lamps, palette [[r,g,b]…] (facades, sRGB), street_color, sidewalk_color, flatten, windows (a Facade Windows material per palette colour; windows glow warm at night). Returns group, buildings, parks, tallest and the ten tallest rooftops [[x, y, z]] (start the player or missions there). | No |

## climate

| Command | Description | Read-only |
| --- | --- | --- |
| `climate.get` | Read the climate settings: enabled (on/off) — The climate drives snow, freezing, fire dryness and warm-weather wildlife; base -30..40 °C — Temperature of a middling biome at sea level, in spring, mid-afternoon, clear weather; biome_span 0..40 °C — How much colder the coldest biome is and hotter the hottest (biome temperature 0..1); lapse 0..5 °C/100 m — Degrees lost per 100 m of height; season_swing 0..30 °C — How far summer warms and winter cools; day_night 0..25 °C — How much colder the night is than mid-afternoon; rain_cooling 0..15 °C — How much full rain cools the air; snow_below -10..10 °C — Rain turns to snow below this; freeze_below -40..5 °C — Liquids that freeze (water) turn to ice below this; cold_snow (on/off) — Rain turns to snow when it is cold enough; freezing (on/off) — Water freezes when it is cold enough, and thaws again; fire_dryness (on/off) — Hot, dry places burn fast; cold, damp ones barely | Yes |
| `climate.set` | Patch the climate settings (JSON merge). How warm and damp places are, and what follows from it. Fields: enabled (on/off) — The climate drives snow, freezing, fire dryness and warm-weather wildlife; base -30..40 °C — Temperature of a middling biome at sea level, in spring, mid-afternoon, clear weather; biome_span 0..40 °C — How much colder the coldest biome is and hotter the hottest (biome temperature 0..1); lapse 0..5 °C/100 m — Degrees lost per 100 m of height; season_swing 0..30 °C — How far summer warms and winter cools; day_night 0..25 °C — How much colder the night is than mid-afternoon; rain_cooling 0..15 °C — How much full rain cools the air; snow_below -10..10 °C — Rain turns to snow below this; freeze_below -40..5 °C — Liquids that freeze (water) turn to ice below this; cold_snow (on/off) — Rain turns to snow when it is cold enough; freezing (on/off) — Water freezes when it is cold enough, and thaws again; fire_dryness (on/off) — Hot, dry places burn fast; cold, damp ones barely. | No |

## combat

| Command | Description | Read-only |
| --- | --- | --- |
| `combat.get` | Read first-person combat data. No arguments: everything (movement, grapple, keys, weapon_parts, loadout, grenades, ability, enemies, pickups, feedback, budgets, hud_template) plus the assembled default weapon and the weapon stat names. section: one top-level section. kind (weapon_part\|grenade\|enemy) + id: one item. | Yes |
| `combat.remove` | Remove a weapon part, grenade or enemy archetype by id. | No |
| `combat.set` | Patch combat settings with a JSON merge (objects merge, arrays replace). Sections: enabled; player {max_health, max_armor, armor_absorb, armor_regen_delay, armor_regen_rate, hit_grace, melee_damage, melee_push, melee_range, melee_cooldown}; movement {run_speed, sprint_speed, acceleration, air_control, gravity, jump_speed, coyote_time, slide_speed, slide_duration, slide_cooldown, slide_height, dash_speed, dash_duration, dash_cooldown, fov, sprint_fov, head_bob, slide_tilt}; grapple {enabled, anchors Tagged\|AnySurface (Tagged: objects tagged 'grapple'), range, aim_cone, reel_speed, pull, swing_control, release_distance, max_attach_seconds, cooldown, release_boost, rope_color}; keys {sprint, dash, slide, jump, grapple, grenade, ability, reload, melee, fire, aim} (key names like Q, Space, ShiftLeft, Mouse1); loadout {frame, core, barrel, relic}; secondary {frame, core, barrel, relic} (sidearm on key 3; empty frame = none); grenade (equipped id), grenade_stock, grenade_max; ability {name, charge, charge_per_kill, radius, damage, impulse, stagger, color, sound}; pickups; feedback {shake, hit_markers, damage_vignette, gore, blood_particles, muzzle_light, explosion_light, tracer_width, hit_sound, hurt_sound (project sound names), head_burst, head_burst_sound, gore_chunks, blood_decals, max_decals, decal_seconds, bleed_seconds, flinch}; budgets {max_enemies, max_body_parts, max_particles, max_projectiles, corpse_seconds, part_seconds}; hud_template (markup; empty = built-in); third_person {shoulder [right, up] m, distance, aim_distance, aim_fov, clips {idle, walk, run, sprint, fire (firing or aiming while standing), move_fire (firing or aiming while moving), jump, death, hit, melee}} — over-the-shoulder shooting when project.player camera is ThirdPerson with an avatar (the avatar faces the aim; weapon parts' and melee weapons' hand_node shows that node of the avatar while equipped). Props tagged 'explosive' chain-explode and 'breakable' shatter when shot. melee [{id, name, model_asset_id, offset, rotation, scale, light_damage, heavy_damage, light_seconds, heavy_seconds, heavy_charge, hit_window, range, arc, cleave_light, cleave_heavy, stagger, impulse, limb_multiplier, weakpoint_multiplier, block_reduction, block_angle, swing_sound, hit_sound, block_sound, dig_radius (metres of earth per hit, 0..5; shovels and picks dig when destruction is on), dig_depth (0..3 m), structure_damage (multiplier against destructibles)}], melee_weapon (equipped id), start_melee; lantern {enabled, color, intensity, range, offset, shadows, flicker}; arms {model_asset_id, socket (weapon bone), melee_socket, anchor (bone kept at the eye), offset, melee_offset, scale, clips {idle, sprint, fire, reload, throw, cast, shove, equip, melee_idle, melee_sprint, light_a, light_b, heavy_windup, heavy, block}, reload_length, light_length, heavy_length}; keys also melee_slot, ranged_slot, secondary_slot. Use combat.upsert for parts, grenades, enemies and melee weapons. | No |
| `combat.upsert` | Create or patch one weapon part, grenade or enemy by id (new entries start from defaults). weapon_part {id, name, slot Frame\|Core\|Barrel\|Relic, description, family (frames), grip (frames: arms clip set, e.g. pistol or long), fits [families], base (frames: full WeaponStats), modifiers [{stat, op Add\|Multiply\|Set, value}], model_asset_id, offset, rotation (deg), scale, sockets {core, barrel, relic, muzzle} (frames), color, fire_sound, reload_sound, starter, hand_node (third person: the avatar node that is this gun)}. WeaponStats: damage, pellets, spread, fire_rate, magazine, reserve, reload_time, range, falloff, impulse, recoil, projectile_speed (0 hitscan), homing, limb_multiplier, weakpoint_multiplier, stagger, charge_per_hit, chain_bonus, ability_radius, automatic, explosion_radius (projectiles explode on impact: rockets, launchers; m), explosion_damage. grenade {id, name, kind Blast\|Gravity, fuse, radius, damage, impulse, stagger, pull, pull_duration, throw_speed, self_damage, color, model_asset_id, sound}. enemy {id, name, model_asset_id, animation_library_asset_id, model_variants [asset ids, each spawn picks one look], scale, behaviour Melee\|Ranged\|Charger, health, armor, heavy, walk_speed, run_speed, radius, height, attack_damage/range/windup/cooldown, ranged_damage/speed/range/windup/cooldown, charge_speed/range/windup, flank (0..1), leap_range [near, far] (pounce; 0 = never), leap_speed, leap_cooldown, vault_speed (hop when stuck; 0 = never), poise, gib_overkill, color, blood_color, clips {idle, walk, run, attack, cast, charge, hit, death, crawl, spawn, roar, leap, vault, attack_variants [], hit_variants [], death_variants []}, hitboxes [{bone, to_bone, length, radius, limb}], limbs [{id, sever_bone, health (fraction), damage_multiplier, effect None\|DisableAttack\|DisableRanged\|Limp\|Crawl\|Kill, weakpoint}], drops [{kind Ammo\|Health\|Armor\|Grenade\|Charge, chance, amount}], value, attack_sound, death_sound, team Hostile\|Ally (allies fight hostiles beside the player and follow them when no hostile is near; hostiles target the nearest of the player and allies; the player's shots pass allies by)}. melee {id, name, description, model_asset_id, offset, rotation, scale, light_damage, heavy_damage, light_seconds, heavy_seconds, heavy_charge, hit_window, range, arc, cleave_light, cleave_heavy, stagger, impulse, limb_multiplier, weakpoint_multiplier, block_reduction, block_angle, swing_sound, hit_sound, block_sound, dig_radius (0..5 m; shovels and picks dig the ground when destruction is on), dig_depth (0..3 m), structure_damage (×, against destructibles)}. Scripts spawn enemies with spawn_enemy(id, x, y, z). | No |

## data

| Command | Description | Read-only |
| --- | --- | --- |
| `data.get` | Read one gameplay data table. | Yes |
| `data.list` | List the project's gameplay data tables (JSON) with their sizes. | Yes |
| `data.remove` | Delete a gameplay data table. | No |
| `data.rename` | Rename a data table. Scripts reading the old name must be updated. | No |
| `data.set` | Create or replace a gameplay data table (any JSON up to 1 MiB; name: lowercase letters, digits, _). World scripts read it with data(name). | No |

## destruction

| Command | Description | Read-only |
| --- | --- | --- |
| `destruction.fracture` | Preview how an object breaks, without a renderer: its model read from the GLB (or its collider box for primitives) cut into Voronoi pieces exactly as at runtime. id; pieces 2..64 (default: its destructible's); hit [x, y, z] world point (pieces get smaller near it). Returns each piece's world centre, volume m³, size, mass kg and triangle count, links (touching pairs that glue the structure), the source triangle count, health and cracks_at (damage before it breaks), and milliseconds taken. | Yes |
| `destruction.get` | Destruction overview: settings (enabled, quality, budgets, collapse, craters, digging, fire, bullet holes, effect ids), the material table in effect (density kg/m³, health per m³, crack share, glue strength kPa, pattern, inside and dust colours, burns, break sound), patterns, quality levels, presets, the level's destructible objects, craters/digs and excavation columns, and effects that are missing. id: one object's destructible and its material rule. | Yes |
| `destruction.make` | Make objects breakable in one undo step. ids: objects or groups (a group stands for everything in it, so a whole building at once). preset (default auto: guessed per object from its name, physics surface, kind and size — window → glass, crate/table → wood, brick → brick, drum → explodes, Tree → wood, Rock → stone): wall, brick_wall, pillar, floor, wood, window, door, crate, fuel_drum, sandbags, vase (shatters on landing), bomb (explodes on landing) (structural parts hold each other up; windows shatter; drums explode). Or set material (Concrete\|Brick\|Stone\|Wood\|Glass\|Metal\|Plaster\|Sandbag), structural, pieces 2..64 on top. remove: true makes them unbreakable again. Turns destruction on (with starter effects) if it was off. Fine-tune one with scene.update {destructible:{…}}. | No |
| `destruction.runtime` | Live destruction while Playing or Simulating (live editor only). action blast {position [x,y,z], radius 0.1..100 m (default 4), damage (default 200)}: breaks structures, craters the ground, lights fires; damage {id, amount}; crater {position, radius, depth}; dig {position, to?, radius, depth, mode surface\|volume}; make {id, preset (auto by default)} turns any object breakable right now; unmake {id}; stats: pieces, moving, rubble, merged, peak_moving, destroyed and collapsed ids, fractures, craters, digs, fires, bullet_holes, last_fracture_ms. Ground edits during Play are recorded in saves and undone on Stop. | No |
| `destruction.set` | Patch destruction settings with a JSON merge: enabled; everything (every solid object breaks with a guessed material, no setup per object); quality Low\|Medium\|High\|Epic (pieces and budgets); max_debris 10..5000 (moving pieces; past it the smallest turn to dust); max_pieces 50..50000; debris_lifetime 0..3600 s (small debris fades; 0 keeps it); settle_rubble (resting debris freezes); merge_rubble (frozen rubble merges into one body per object); collapse (buildings fall when they lose support or joints are overloaded); structure_strength 0.1..10 (× material glue); second_breaks (big pieces break again on hard landings); impact_damage (falling and thrown bodies damage what they hit); blast_damage, bullet_damage, melee_damage 0..100 (× against structures); craters; crater_scale 0.05..2 (crater radius per blast radius); digging (melee weapons with dig_radius dig); bullet_holes; fire; fire_seconds 1..600; smoke_seconds 0..600; dust_effect, smoke_effect, fire_effect (vfx ids); collapse_sound; materials (whole list of overrides, one per material: {material, density, health, crack 0..1, strength kPa, pattern, inside_color, dust_color, burns, sound}). Undoable. | No |
| `destruction.setup` | Turn destruction on in one step for a game (a war game, a heist): enables it, adds the effects destruction_dust, destruction_smoke, destruction_fire and destruction_chips (if missing; tune with vfx.upsert), matches break sounds per material and a collapse sound from the project's own sounds by name, and adds an entrenching shovel melee weapon that digs (shovel: false skips it). Then make objects breakable with destruction.make. | No |

## drifts

| Command | Description | Read-only |
| --- | --- | --- |
| `drifts.get` | Read the snow and sand settings: enabled (on/off) — Snow and sand that feet, bodies and blasts push about, near the player; cell_size 0.1..0.5 m — Size of one cell of the field: smaller shows finer footprints; cells 64..256 — Cells across the field around the camera (cells × detail = how far it reaches); snow_depth 0..1.5 m — Snow a full snow cover lays on flat open ground; snow_refill 5..3600 s — Seconds steady snowfall takes to fill a track back in; snow_color sRGB [r, g, b]; sand (on/off) — Loose sand on the ground; sand_everywhere (on/off) — Sand on all ground (off: only where `sand_layer` is painted); sand_layer 0..3 — The terrain texture layer that is sand (0 = the base layer .. 3); sand_depth 0..0.6 m; ripples 0..1 — Ripples the wind draws across sand; sand_refill 5..3600 s — Seconds the wind takes to smooth a track in sand away; sand_color sRGB [r, g, b]; slow 0..0.9 — How much knee-deep fresh snow or sand slows the player (a trodden track much less) | Yes |
| `drifts.set` | Patch the snow and sand settings (JSON merge). Lying snow and loose sand (`drifts.set`). Fields: enabled (on/off) — Snow and sand that feet, bodies and blasts push about, near the player; cell_size 0.1..0.5 m — Size of one cell of the field: smaller shows finer footprints; cells 64..256 — Cells across the field around the camera (cells × detail = how far it reaches); snow_depth 0..1.5 m — Snow a full snow cover lays on flat open ground; snow_refill 5..3600 s — Seconds steady snowfall takes to fill a track back in; snow_color sRGB [r, g, b]; sand (on/off) — Loose sand on the ground; sand_everywhere (on/off) — Sand on all ground (off: only where `sand_layer` is painted); sand_layer 0..3 — The terrain texture layer that is sand (0 = the base layer .. 3); sand_depth 0..0.6 m; ripples 0..1 — Ripples the wind draws across sand; sand_refill 5..3600 s — Seconds the wind takes to smooth a track in sand away; sand_color sRGB [r, g, b]; slow 0..0.9 — How much knee-deep fresh snow or sand slows the player (a trodden track much less). | No |

## editor

| Command | Description | Read-only |
| --- | --- | --- |
| `editor.commit` | Validate and apply pending script, Blueprint and inspector edits as authoring changes. | No |
| `editor.discard` | Explicitly discard uncommitted inspector/programming buffers; saved project and undo history remain. | No |
| `editor.workspace.get` | Read the live editor's workspaces (task-focused panel layouts: level, terrain, art, logic, gameplay, story, ship) with each one's panels, the current workspace, the panels open now and the focused panel. Does not change the project or its revision. | Yes |
| `editor.workspace.set` | Switch the live editor to a workspace (the menu-bar tabs): level, terrain, art, logic, gameplay, story or ship. Each opens the panels its job needs and keeps the layout the user left it in; reset true restores its default arrangement. Returns the editor.workspace.get report. Stop Play first. Does not change the project or its revision. | No |

## elements

| Command | Description | Read-only |
| --- | --- | --- |
| `elements.get` | Read elements: settings, the effective damage multiplier per material and kind (material_multipliers), the damage kinds (physical, blunt, explosive, fire, electric, ice, water), statuses (burning, wet, shocked, chilled) and the combat enemy ids usable in enemies. | Yes |
| `elements.set` | Patch elements, the one damage path every system shares (JSON merge; maps merge): enabled (statuses and reactions); player_health 1..100000 and respawn_seconds 0..600 (player health and respawn at the spawn when combat is off; 0 waits for revive_player); burning_seconds 0..120 and burning_damage 0..1000 per second (fire sets things burning: they hurt each second and light the grass they cross when burning_spreads); wet_seconds 0..600 (water and rain soak — rain_wets — so fire does half and does not catch, electric does double); shock_seconds 0..30 (electric stuns); chill_seconds 0..120 and chill_slow 0..1 speed (ice slows; blunt hits on chilled targets do 1.5x); water_conducts with conduct_radius 0..100 m (electric hits in water reach everyone in it); lightning_damage 0..100000 at a strike (4 m, 0 = harmless); falling_trees_damage 0..100000 (felled trees hit what they land on); chain_blasts_hurt (exploding objects hurt the player and enemies); objects_catch_fire (burning grass lights wooden objects and burning objects light the grass); materials {concrete\|brick\|stone\|wood\|glass\|metal\|plaster\|sandbag: {kind: multiplier 0..10}} replacing the built-ins (wood fire x2, metal fire x0.2, glass blunt x2…); enemies {combat enemy id: {kind: multiplier 0..10}} (0 = immune). Scripts: deal_damage, damage_area, apply_status, clear_status, has_status, statuses; hooks on_damaged, on_status. --verify-run reports an elements section. | No |

## engine

| Command | Description | Read-only |
| --- | --- | --- |
| `engine.status` | Inspect session, document, runtime and pending editor state before making edits. | Yes |

## environment

| Command | Description | Read-only |
| --- | --- | --- |
| `environment.look` | Apply a lighting look in one undo step: time of day, haze and volumetric sun shafts, bloom, ambient occlusion and colour grading together (water and painted sky kept). look: clear_day, battlefield (dusty warm daylight with shafts through the haze), golden_hour, overcast, foggy_dawn, night_battle (smoky moonlight; explosions and lamps light the fog), storybook. Volumetric looks switch anti-aliasing to TAA so the shafts are smooth. Fine-tune with project.environment. | No |

## feel

| Command | Description | Read-only |
| --- | --- | --- |
| `feel.get` | Read game feel (stamina, movement, camera, footsteps, combat, sounds) plus the footstep surfaces and the built-in sound names. | Yes |
| `feel.set` | Patch game feel (JSON merge): stamina {enabled, max, sprint/climb/swim per second, climb_jump per leap, regen per second after delay s; running dry exhausts until full}; movement {crouch (Ctrl crouches: crouch_speed 0.05..1, a shorter capsule), climbing (walk into a steep surface to climb it at climb_speed m/s; jump leaps, crouch lets go; climb_tag limits it to tagged objects), mantle (jump at a ledge up to mantle_height m to pull up over mantle_seconds)}; camera {head_bob m, fov_kick degrees while sprinting, landing_dip m, shake 0..2, third-person collision_radius m with pull_in / ease_out rates}; footsteps {enabled, volume, stride m, landing, surfaces {grass\|dirt\|stone\|wood\|water\|snow\|metal\|sand: project sound cue}} (the surface comes from the object's physics.surface or name, or the terrain: grass, painted layer, snow); footprints {enabled, shape footprint\|pawprint\|any decal shape, texture_id (your own print PNG), size [w, l] m, stance m, wet_prints (rain leaves dark wet prints), surfaces {surface: {print, color, opacity, wetness, lifetime s, effect (step_dust\|step_sand\|step_snow\|step_splash or a project vfx; empty none)}}} (built-ins: dirt, sand and snow leave prints with puffs, water splashes); combat {hitstop_hit/kill/explosion real seconds, recoil_recovery per second, aim_assist 0..1 within aim_assist_angle degrees}; sounds {door\|lever\|pickup\|chest\|throw\|mantle\|land\|fire_loop\|cast_fire\|cast_ice\|cast_electric\|cast_water: project sound cue replacing the built-in}. Scripts: stamina(), max_stamina(), exhausted(), climbing(), crouching(); tune("feel", patch). --verify-run reports feel. | No |

## fire

| Command | Description | Read-only |
| --- | --- | --- |
| `fire.get` | Read the spreading fire settings: enabled (on/off) — Fire spreads at all; spread 0..3 — How eagerly fire runs through grass (0 never); burn_seconds 0.5..120 s — Seconds one patch of grass burns; wind 0..3 — How much the wind pushes it (downwind it races); rain_puts_out (on/off) — Rain puts fires out and wet ground will not catch; trees_burn (on/off) — Trees catch fire, burn and fall; damage 0..1000 /s — Damage a second to whoever stands in the flames; lightning_ignites (on/off) — Lightning strikes start fires; blasts_ignite (on/off) — Explosions start fires; max_burning 10..20000 — Burning cells kept at most (big fires stop growing past it); flame_size 0.3..3 — How big the flames are (1 = knee to head high); size_variety 0..1 — Mix of small licks, medium flames and big towering ones (0 all alike); patches blaze bigger; smoke 0..2 — Grey smoke rising from big fires (0 none); spot_fires 0..2 — Wind carries embers ahead of the fire and they start spot fires (0 never); crown_fire 0..2 — Burning tree crowns set the crowns around them alight, more downwind (0 never); litter 0..1 — Leaf litter under trees burns like grass, so fire runs through woods (0 bare); tree_burn_seconds 3..120 s — Seconds a tree's crown burns before it is charred; tree_fall_chance 0..1 — Share of burnt trees that come down; the rest stand charred and smouldering; smoulder_seconds 0..60 s — Seconds burnt ground keeps glowing embers after the flames pass; glow 0..2 — How brightly the flames glow (bloom and light on the surroundings); lights 0..4 — Flickering lights cast by the nearest fires (0 none) | Yes |
| `fire.set` | Patch the spreading fire settings (JSON merge). Fire that spreads through grass: it runs faster downwind and through thick grass, stalls on bare ground, water, snow and wet ground; trees catch and fall. Fields: enabled (on/off) — Fire spreads at all; spread 0..3 — How eagerly fire runs through grass (0 never); burn_seconds 0.5..120 s — Seconds one patch of grass burns; wind 0..3 — How much the wind pushes it (downwind it races); rain_puts_out (on/off) — Rain puts fires out and wet ground will not catch; trees_burn (on/off) — Trees catch fire, burn and fall; damage 0..1000 /s — Damage a second to whoever stands in the flames; lightning_ignites (on/off) — Lightning strikes start fires; blasts_ignite (on/off) — Explosions start fires; max_burning 10..20000 — Burning cells kept at most (big fires stop growing past it); flame_size 0.3..3 — How big the flames are (1 = knee to head high); size_variety 0..1 — Mix of small licks, medium flames and big towering ones (0 all alike); patches blaze bigger; smoke 0..2 — Grey smoke rising from big fires (0 none); spot_fires 0..2 — Wind carries embers ahead of the fire and they start spot fires (0 never); crown_fire 0..2 — Burning tree crowns set the crowns around them alight, more downwind (0 never); litter 0..1 — Leaf litter under trees burns like grass, so fire runs through woods (0 bare); tree_burn_seconds 3..120 s — Seconds a tree's crown burns before it is charred; tree_fall_chance 0..1 — Share of burnt trees that come down; the rest stand charred and smouldering; smoulder_seconds 0..60 s — Seconds burnt ground keeps glowing embers after the flames pass; glow 0..2 — How brightly the flames glow (bloom and light on the surroundings); lights 0..4 — Flickering lights cast by the nearest fires (0 none). | No |

## fluid

| Command | Description | Read-only |
| --- | --- | --- |
| `fluid.get` | Read fluids: settings (enabled, cell_size, cells, max_bodies, marks), every liquid usable (project kinds plus presets water, slime, lava, oil, mud, blood), and the objects that hold or pour fluid. id returns one liquid. | Yes |
| `fluid.remove` | Remove a project liquid by id (refused while objects use it). | No |
| `fluid.set` | Patch fluid simulation settings (JSON merge): enabled, cell_size 0.05..4 m and cells 16..384 (each fluid body is a cells × cells grid: detail vs reach vs cost), max_bodies 1..32, marks (fluids leave stains), blood (liquid wounded enemies and the player bleed; "" = none), blood_amount 0..5, max_drops 0..5000 (droplets in flight: barrel jets, spring streams, splashes, blood sprays), stylized (cel-shaded: banded colour, wavy shoreline rings, sparkles, outlined stains; false = soft natural shading), ground_detail 0..0.5 m (small fixed bumps in the ground fluids see, so water gathers into rivulets and streams instead of an even sheet; default 0.035). weather {rain_fill 0..20 mm/min (full rain raises open water), wind_push 0..3 (wind pushes liquid surfaces)} for liquids with weather on; sounds {volume 0..2, reach 5..80 m, stream, river, waterfall, lapping, lava, ooze, splash, big_splash: project sound id or null for the built-in} — built-in loops swell with the fluid near the listener (shallow flow babbles, deep fast flow roars, white water over drops crashes, still water laps, lava gurgles, thick liquids ooze) and things landing in fluid splash; --verify-run reports fluid_audio levels. Fast water pouring over a drop throws spray and churns white (waterfalls). Dynamic bodies in fluid float by size and mass in buoyant liquids, are dragged and held by thick ones, carried and spun by the flow, lit by lava and put out by water; landing in fluid splashes and shoves it out in a ring. Liquids change through fluid.upsert. Fluid flows over the terrain and anything static or kinematic, pools, soaks in and leaves marks. Spill it with scripts (spill(kind, x, y, z, volume[, radius])), objects (scene.update {fluid_container {fluid, volume m³, leak m³/s}} — needs a destructible to leak and break — and {fluid_source {fluid, rate m³/s, radius, active, total}} and {fluid_pool {fluid, radius m, grid_offset [x, z] m}}: a lake, pond or pit full when Play starts and previewed in the editor — the object's height is the surface and the connected low ground within radius fills) or tools (item cast {fluid, fluid_volume}). --verify-run reports fluids. | No |
| `fluid.upsert` | Create or patch a liquid by value.id (JSON merge; preset water, slime, lava, oil, mud or blood to start from one; a project kind with a preset's id replaces it): name, color/deep_color/opacity/gloss/glow/foam/wobble (look), viscosity 0..1 (0 runs like water, 1 barely creeps), film m (thinnest it spreads: slime and lava stay in blobs), absorb m/s (soaks in or hardens), mark_color/mark_opacity/mark_seconds (stains, 0 s = forever), slow 0..1 and damage per second of damage_kind to whoever stands in it, ignites (lights grass), flammable (fire runs along it), extinguishes (puts fire out), buoyant (floats and carries bodies), conducts (electric damage spreads through it), freezes (ice damage turns it solid for a while; fire melts it), erosion 0..1 (fast flow carves channels into the landscape and turns muddy; the soil settles where it slows; water 0.5), momentum 0..1 (speed the flow carries along: surges keep going past a slope, run up the far side and slosh; scaled down by viscosity; default 0.85), drag 0..2 (how hard its current shoves the player and enemies standing in it; default 1). weather (true: rain tops it up and draws rings on it, wind pushes it; lava, slime, oil and blood default to false), swimmable (the player swims in it when deep; water only by default). With the camera under any liquid's surface the underwater look takes that liquid's colour and murk. Scripts switch fluid_source objects with set_fluid_source(id, on[, rate]). Ground dug or raised while playing (shovel and earth staff items, dig/fill/crater scripts, blasts) is re-read by fluids at once, so water runs into new holes. | No |

## font

| Command | Description | Read-only |
| --- | --- | --- |
| `font.import` | Import a TTF or OTF font inside the workspace for the game UI. name sets its CSS font-family (default: the file name); the stylesheet then uses font-family: "name". | No |

## game

| Command | Description | Read-only |
| --- | --- | --- |
| `game.export` | Build the game as a standalone app players run without the editor: macOS .app, Windows .exe or Linux folder, with its project, verified assets, README, release-manifest.json and SHA256SUMS. Writes &lt;output_dir>/&lt;game>-&lt;platform> and replaces only a previous Feather build there; settings (name, version, bundle id, profile, zip) come from build.get/build.set. The host platform compiles its player from source (incremental) or uses the bundled one; other platforms need a player runtime (see game.platforms) or a prebuilt 'player'. Headless returns the report (path, player, project, files, bytes, seconds, archive, warnings); live editors return a job_id; poll job.inspect. | No |
| `game.platforms` | Which platforms this machine can build the game for right now: per platform whether a player is available, where it comes from (cargo, bundled, runtime) and how to get a missing one; also the runtimes folder, the build settings and the default output folder. | Yes |
| `game.setup_github` | Set up builds in the game's own GitHub repository (no engine source needed): writes .github/workflows/feather-build.yml at the repository root (the nearest folder with .git inside the workspace, else the project's folder) and ignores the build folders. The workflow downloads the prebuilt feather-cli and players of a pinned Feather release, builds Windows, macOS and Linux, publishes v\*.\*.\* tags as GitHub releases and uploads to Steam when steam.app_id is set (steam.set) and the STEAM_USERNAME / STEAM_CONFIG_VDF secrets exist. Refreshes a workflow Feather wrote; a foreign file needs overwrite. Returns the paths, needed secrets and next steps. | No |
| `game.simulate` | Run bounded renderer-free gameplay and physics from the committed authored project without changing it. Headless returns a report; live returns a background job_id whose output contains report. Pending edits require commit/discard; allowed during Play as an isolated authored snapshot. 1–600 frames, at most 256 input events, 2048 objects and 1000000 frame-object steps. | Yes |

## history

| Command | Description | Read-only |
| --- | --- | --- |
| `history.redo` | Redo the last undone transaction while advancing the revision. | No |
| `history.undo` | Undo the last authored transaction while advancing the revision. | No |

## horizon

| Command | Description | Read-only |
| --- | --- | --- |
| `horizon.get` | Read the horizon settings: enabled (on/off) — Draw the distant land beyond the streamed chunks; distance 300..12000 m — How far the land reaches from the camera; the camera sees this far; detail 32..320 — Cells across each ring of the distant land (rings double their spacing outward): more keeps finer ridges and gullies far away; shadows (on/off) — Distant hills cast shadows (costs shadow-map time); clear_fog (on/off) — Push clear-weather fog out to the horizon (it otherwise hides the edge of the view range); weather fog still closes in | Yes |
| `horizon.set` | Patch the horizon settings (JSON merge). How far the land reaches past the streamed chunks. Fields: enabled (on/off) — Draw the distant land beyond the streamed chunks; distance 300..12000 m — How far the land reaches from the camera; the camera sees this far; detail 32..320 — Cells across each ring of the distant land (rings double their spacing outward): more keeps finer ridges and gullies far away; shadows (on/off) — Distant hills cast shadows (costs shadow-map time); clear_fog (on/off) — Push clear-weather fog out to the horizon (it otherwise hides the edge of the view range); weather fog still closes in. | No |

## input

| Command | Description | Read-only |
| --- | --- | --- |
| `input.get` | Read input actions and gamepad settings. No arguments: every action {id, label, keys, gamepad, rebindable}, gamepad {enabled, deadzone, look_speed, look_curve, invert_y} and the valid gamepad button names. id: one action. | Yes |
| `input.remove` | Remove an input action by id. | No |
| `input.set` | Patch input settings with a JSON merge (objects merge, arrays replace). patch.gamepad {enabled, deadzone 0..0.9, look_speed 1..1080 deg/s at full right-stick deflection, look_curve 0.5..4 (higher = finer aim near centre), invert_y}; patch.actions replaces the whole action list. Use input.upsert to change one action. | No |
| `input.upsert` | Create or patch one input action by id. value {id (lowercase, digits, _), label, keys [up to 4 key or mouse names: W, Space, ShiftLeft, ControlLeft, Escape, ArrowUp, F1, 1, Mouse1, Mouse2, Mouse3], gamepad [up to 4 of South, East, West, North, LeftBumper, RightBumper, LeftTrigger, RightTrigger, Select, Start, LeftStick, RightStick, DPadUp, DPadDown, DPadLeft, DPadRight, Mode], rebindable}. Gameplay data that names an action's default key (combat.keys.jump = Space) follows the action, so gamepad buttons and player rebinds apply to it. Scripts read actions with action_down(id) / action_pressed(id). The left stick always moves and the right stick looks. | No |

## interaction

| Command | Description | Read-only |
| --- | --- | --- |
| `interaction.get` | Read carrying and throwing settings and the level's doors, switches and pickups (id, name, kind). | Yes |
| `interaction.set` | Patch carrying and throwing (JSON merge): carry (look at a dynamic body and press Interact to pick it up, again to set it down), carry_max_mass 0..10000 kg, hold_distance 0.5..6 m, throw_speed 0..60 m/s (heavier objects fly slower), throw_damage 0..10 blunt damage per kg·m/s to enemies hit, throw_action (input action, default throw = left mouse), crosshair (screen-centre reticle during play, on by default; lights up on anything you can pick up or use; restyle with template crosshair / CSS panel.crosshair-reticle). Doors, switches and pickups are scene components: scene.update {door {open_degrees, hinge, seconds, locked_with, consume_key, starts_open, sound}}, {switch {targets, prompt, once, starts_on, tilt, sound}}, {pickup {item, count, prompt}}. --verify-run reports interaction (doors_open, switches_on, picked, carrying, thrown, throw_hits). | No |

## inventory

| Command | Description | Read-only |
| --- | --- | --- |
| `inventory.get` | Read the inventory settings (enabled, key, start, capacity, limit, status_bar, categories, equip_slots) and the item ids. | Yes |
| `inventory.rename` | Rename an item category or equipment slot; every item using it follows. kind category\|slot, from, to. | No |
| `inventory.set` | Patch the inventory (JSON merge; arrays replace): enabled (the screen and HUD), key (opens the screen; default Tab), start {item id: count} the player begins with (replaces the whole map), capacity 0..10000 different items carried (0 = no limit), limit {property, max} caps a summed item property (weight) or null, status_bar (health without combat and statuses on screen), categories [names] the game's item categories in screen order, equip_slots [names] the game's equipment slots. Containers: scene.update {container {items {id: count}, prompt, title, capacity}} for chests; scripts give_to/take_from/move_item/bag_items/open_container for shops and stashes. Screens are templates inventory, container and status_bar (project.game templates). | No |

## item

| Command | Description | Read-only |
| --- | --- | --- |
| `item.batch` | Change many items in one undo step: upsert [{id, …item fields}] (JSON merge onto the existing item, or a new one), remove [ids], replace (true drops every item not upserted first — import a whole table). Validated together; one failure changes nothing. | No |
| `item.get` | Read the item library: items, presets (apple, potion, antidote, wood, gem, key, flame_tunic, rubber_cloak, snow_boots, iron_helm, fire_rod, torch, ice_rod, storm_rod, water_flask), used_by (pickups and locked doors using each item), kinds and equipment slots; id returns one item. | Yes |
| `item.remove` | Remove an item by id (refused while a pickup or locked door uses it; it leaves the starting items). | No |
| `item.upsert` | Create or patch an item by value.id (JSON merge; starts from preset when given): name, description, category (one of inventory.categories), stack 1..9999 (most per bag), value, icon (Phosphor name), color, properties {any game data: weight, rarity, damage…}; using it: heal 0..100000, cures [burning\|wet\|shocked\|chilled], consumed (used up); equipment: slot (one of inventory.equip_slots), resist {kind: damage multiplier 0..10 while worn; 0 = immune}, immune [statuses it keeps off]; tools: cast {kind, amount, radius, range, cooldown, ammo, effect, sound} — while worn the use_tool action (C) casts that element where the player aims (presets fire_rod, torch, ice_rod, storm_rod, water_flask, oil_flask); cast.fluid spills a liquid where it lands. rename_from renames and updates pickups, locked doors and starting items. Scripts: give_item, take_item, item_count, has_item, use_item, equip_item, unequip_item, equipped, inventory; hooks on_item, on_use_item, on_equip. | No |

## job

| Command | Description | Read-only |
| --- | --- | --- |
| `job.inspect` | Read completion, error and output for a live capture, export or simulation job. | Yes |

## landscape

| Command | Description | Read-only |
| --- | --- | --- |
| `landscape.brush` | Sculpt or paint sparse terrain samples with a radial brush. Raise/Lower/Smooth/Flatten shape the ground (Flatten requires target_height); Layer paints a texture layer (layer 1..3, 0 restores the base); Paint tints with color; Foliage erases scattered trees, bushes and grass and Restore brings them back; Erase removes edits. | No |
| `landscape.carve_object` | Carve the ground with an object's box now, baked into the level's ground (undoable): its collider box (or the ball inside it with a terrain_cutter of shape Ball), filled instead with terrain_cutter fill, rounded by soften. Put a box where a room, ramp, moat, cellar or tunnel mouth should be, then carve. remove: true deletes the object afterwards. During Play a terrain_cutter component carves by itself (when Start or Moving); scripts use carve_object(id). | No |
| `landscape.caves` | Carve a generated cave system into the authored ground (0.5 m voxels: winding tunnels with flat floors and rough rock walls, wide caverns, entrances that open on the surface; the ground on top keeps its grass and trees). All optional: seed (different seed, different caves), center [x, z], radius 8..400 (area the tunnels wander in, default 60), tunnels 1..48 (4), length 8..400 m each (70; tunnels × length ≤ 4000), size 1..8 (tunnel radius, 2.2), depth 3..60 (metres below the surface, 9), caverns 0..1 (how often a tunnel opens into a cavern, 0.5), entrances 0..tunnels (tunnels that start at the surface, 1), winding 0..1, roughness 0..1. Returns entrances, caverns and tunnel_points [[x, y, z]] (floor points to place loot, lights or enemies) and spawns (one per cavern, ready for project.settings spawn_position). Deterministic; undoable; dig, blast and flood them like any excavation. Scripts: generate_caves(#{...}). | No |
| `landscape.configure` | Patch landscape base (Meadow, Flat, Procedural), amplitude, height offset or decorations; preserve sculpt/paint samples. Procedural generates endless seeded land: generator {preset hills\|mountains\|plains\|highlands\|mesas\|islands (reshapes the same seed), seed, height 0..600 m, scale 20..8000 m (distance between hills), roughness 0..1, mountains 0..1 (sharp ridged ranges), plains 0..1 (flat lowlands), warp 0..1 (twisting shapes), terraces 0..1, erosion 0..1 (branching gullies and spurs weathered down every slope), ridge_softness 0..1 (rounds knife-edged ridge crests; 0 = original), clearing 0..1000 m (flat ground around the origin)}; giving generator switches the base to Procedural. Biomes, sculpting, caves and the horizon all build on it. | No |
| `landscape.crater` | Blast a crater into the authored ground (war-torn levels): position [x, z] (on the ground) or [x, y, z]; radius 0.25..30 m (default 3); depth 0..20 m (default 1.2); rim 0..1 (raised lip of thrown soil, default 0.5); scorch 0..1 (blackened ground without foliage, default 0.8). Craters render and collide at 0.5 m detail and stack. Undoable; landscape.reset clears them with the rest of the terrain edits. | No |
| `landscape.dig` | Dig into the authored ground. at [x, z] or [x, y, z]; to (optional end point: a trench or tunnel along the line); radius 0.25..30 m; mode surface (lowers the ground by depth 0..20 m with sloped sides; pile heaps the soil beside it, default true) or volume (carves a capsule of radius from at to to out of 0.5 m voxels: straight trench walls, foxholes, tunnels under a roof; y is the centre of the cut). fill: true adds soil instead (berms, filling holes). Undoable. | No |
| `landscape.inspect` | Read landscape base settings, sparse sculpt/paint samples and grid spacing. points [[x, z]…] (up to 1024) instead returns heights: the ground height in metres at each point (including craters and digs), for placing things at exact world heights (root positions are terrain-relative). | Yes |
| `landscape.layout` | Replace landscape shapes, the valley rim, texture layers and/or foliage layers (omitted fields are kept). Foliage scatters model assets by density per 100 m² with scale range, min_slope/max_slope (0 flat..1 vertical), avoid_paint, avoid_flattened, view_chunks, collider_radius, cast_shadows, wind (sway multiplier 0..3; null = automatic: 0 for rocks/stones, else 1), clustering 0..1 (groves and glades) and cluster_size m. Shapes apply in order: Flatten (level a disc to an absolute height), Hill (add/dig), Path (polyline road painting a layer 1..3, optionally levelling), Paint (disc of a layer), Clear (no foliage in a disc). Layers: up to 4; layer 0 is the base, slope auto-covers steep ground; texture_id comes from texture.import. | No |
| `landscape.reset` | Clear all landscape sculpt/paint samples as one undoable edit; preserve base settings. | No |
| `landscape.stroke` | Apply up to 256 radial brush dabs atomically as one undo operation. Empty strokes are no-ops; changed_samples counts changes across all dabs. | No |
| `landscape.style` | Dress the terrain in a stylized landscape style from the texture library: meadow (grass, mossy rock, dirt patches, sandy banks; oaks and birches), forest (moss and roots; dense oaks, pines, birches), autumn (golden grass; autumn trees), desert (sand and sandstone; palms, dead trees), snow (snow and pale rock; pines, no grass), alpine (mountain grass, blue-grey rock; pines, birches), flat (plain painted colours, no textures). Sets the four terrain layers (base, rock on slopes, ground patches, riverbanks), the grass look (grass false keeps yours) and a Trees layer of matching designs with library barks (forest false skips it). One undo step. | No |
| `landscape.texture_layer` | Put a library texture on a terrain layer (0 base everywhere, 1..3 on slopes/patches/painting): imports colour, normal and height maps, names the layer after it, sets the tint to white. Optional tiling (m per repeat), slope (0..1, null for none: cover steeper ground automatically), patches 0..1, height_blend 0..1 (with the height map: 0 plain fade .. 1 sharp, stones poke through first; default 0.6). E.g. layer 0 grass_02, layer 1 cliff_rocks_02 with slope 0.45, layer 2 ground_04 for paths. One undo step. | No |

## level

| Command | Description | Read-only |
| --- | --- | --- |
| `level.create` | Create a level. id: lowercase letters, digits, - or _. name: display name. Either base Flat\|Meadow for an empty level (it copies the open level's environment), or copy_from: an existing level id to duplicate. open: true also opens it for editing. | No |
| `level.list` | List the project's levels: {open (the level scene./landscape. commands edit), start (where the game begins), levels [{id, name, objects, open, start, spawn_position}]}. Levels share assets, materials, sounds, data, scripts, story, combat and input; each has its own objects, landscape, environment and spawn. | Yes |
| `level.open` | Open a level for editing: its objects, landscape, environment and spawn become the ones scene.\*, landscape.\*, project.environment and project.settings edit. The previously open level is stored unchanged. Undoable. At runtime scripts switch levels with load_level(id). | No |
| `level.remove` | Delete a level that is not open. A start level pointing at it falls back to the open level. | No |
| `level.update` | Rename a level (new_id and/or name) or make it the start level (start: true). | No |

## library

| Command | Description | Read-only |
| --- | --- | --- |
| `library.list` | Search the converted model library (Synty packs and other libraries) by words in the name, pack and category (building, prop, environment, item, weapon, character, fx). Returns sizes in metres and triangle counts. | Yes |
| `library.place` | Import a library model and place it in the scene: position is terrain-relative, rotation_y in degrees. Solid models get a static box collider fitted to their bounds unless collider is false. place:false only imports the asset (for foliage, spawning or animation libraries). One undo step. | No |

## light

| Command | Description | Read-only |
| --- | --- | --- |
| `light.add` | Place a ready-made light in one undo step: preset candle, torch, lantern, lamp (default), neon, spotlight, god_rays (volumetric spot), moonlight, key_light or rim_light (directional). position [x,y,z] terrain-relative (default 4 m in front of the spawn), rotation [x,y,z] degrees (directional lights aim with it; default tilted 40°), optional name. Returns the id and light; tune it with scene.update {light:{…}} (kind, color, temperature K, intensity lm/lux, range, spot_angle, inner_angle, source_radius, shadows, volumetric, flicker, night_only). | No |

## lighting

| Command | Description | Read-only |
| --- | --- | --- |
| `lighting.bake_probes` | Bake light probes headlessly with the CPU path tracer (no renderer needed) and record the results: ids (default every probe), stale_only (only unbaked, stale or missing ones). Traces model triangles, built-in props and the landscape near each probe (max_distance) with albedo from materials and textures, lit by object lights (point/spot/directional, range falloff, shadows for shadow-casting lights), the sun or moon at the bake hour and the ambient sky. Writes imported/probe-&lt;sha256>.fprobe files to the asset cache. Returns per probe {file, triangles, lights, average colour, peak luminance, enclosure, seconds, notes}. A 16×6×16 m room at the defaults takes about a second. | No |
| `lighting.get` | Read project.lighting: baked light probes (irradiance volumes and reflection probes), bake settings and per-probe status {id, kind, baked, stale (the scene near it changed since its bake: objects, lights, environment, landscape, bake settings), missing_file}. Also the hour bakes use and the sky light they see (baked_sky: key light direction and colour × lux, ambient colour × cd/m², linear). id returns one probe in full, including its bake {file, signature, average colour, peak luminance, enclosure 0 open sky..1 sealed room, triangles}. | Yes |
| `lighting.set` | Patch lighting settings (JSON deep merge): probes_enabled (draw baked probes; false = flat ambient everywhere, for comparison) and bake {rays 16..4096 per ambient-cube face (default 128), bounces 1..4 (default 2), max_distance 5..1000 m rays travel (default 80), hour -1 (the environment's time_of_day, default) or 0..24 the sky and sun are baked at, runtime_rebake (default true: during play, destruction inside a probe re-bakes it in the background without the destroyed objects)}. Probes themselves are edited with probe.add/update/remove. | No |

## localization

| Command | Description | Read-only |
| --- | --- | --- |
| `localization.get` | Read languages, the default language, strings and which keys each language is missing. language: resolve every string for that language (with fallback). prefix: only keys starting with it. | Yes |
| `localization.remove` | Delete string keys. | No |
| `localization.set` | Set the language list (codes like en, it, de, ja) and the default language used when a translation is missing. | No |
| `localization.upsert` | Add or change translations: strings {key: {language: text}}; an empty text removes that translation. Use keys in UI markup and story text as @{key}, and in scripts with tr(key) (tr(key, #{name: v}) fills {name}). Players pick a language in the pause menu; scripts call set_language(code). | No |

## material

| Command | Description | Read-only |
| --- | --- | --- |
| `material.add_node` | Add a typed node to a material graph (creating its Material Output graph when absent). operation comes from material.nodes. Returns pins and compiled WGSL. One undo step. | No |
| `material.connect` | Wire a typed output to a same-typed input. An input takes one wire, so a new wire replaces the old one. Cycles and malformed graphs are rejected atomically. One undo step. | No |
| `material.create` | Create a reusable material asset (colour, textures, PBR/toon settings and optional typed graph). graph_template starts from Toon Basic, Facade Windows (window grid on walls, lit at night), Glowing Crystal, Dissolve, Hologram, Stylized Water, Wind Foliage or Lava; explicit fields override it. Returns its id. | No |
| `material.disconnect` | Remove one material wire by edge id, or all wires on a node (optionally one pin). One undo step. | No |
| `material.from_model` | Make an editable material asset from a model's embedded glTF material (slot = glTF material name, see scene/asset material names): copies colour, roughness, metallic, emission, alpha and double-sided, and registers its textures as project textures. Assign it with scene.update material_slots [{slot, material_id}]. | No |
| `material.get` | Read project shading, material summaries, toon presets and graph templates. id returns one material in full plus its effective toon look and graph nodes decorated with typed pins, edges, compiled WGSL and per-node WGSL snippets. | Yes |
| `material.nodes` | List the typed material node palette, including each operation JSON, input/output pins, groups, Fresnel Glow and Wind Sway, plus all graph templates. Filter by query/group and page with limit/offset. | Yes |
| `material.remove` | Delete a material asset and clear object slots that used it. | No |
| `material.remove_node` | Remove a material node and its wires. Removing the sole Material Output is rejected atomically. One undo step. | No |
| `material.set` | Patch the project shading (project.shading): toon_by_default (draw imported models, Synty packs included, and material assets on shading Project with toon shading, without editing each material) and toon, the project's look: bands 1..6 lit steps, softness 0 hard..1 smooth, shadow_tint sRGB (white = no tint), rim_color, rim_width 0..1, rim_strength 0..4, specular_size 0..1, specular_strength 0..4. toon_preset loads a named look first (Storybook, Hard cel, Soft painterly, Warm sunset, Flat). A material's own shading Standard/Toon and toon look override it (material.update). Toon materials keep base colour textures, vertex colours, normal maps and shadows; they render forward (also beside the deferred renderer that reflections and GI switch on), so screen-space reflections and GI bounce do not land on toon surfaces; baked light probes (probe.add) do. | No |
| `material.set_node` | Replace a material node operation and/or move it. Wires to pins that no longer exist are dropped and listed. Returns pins and compiled WGSL. One undo step. | No |
| `material.update` | Patch a material asset; every object using it updates live. graph_template replaces its graph and matching surface defaults before explicit patch fields are applied. Supports full graph import plus granular material graph commands. | No |

## navigation

| Command | Description | Read-only |
| --- | --- | --- |
| `navigation.get` | Read navigation settings, the open level's flat walkability grid and layered navmesh statistics: total/open/blocked spans, upper floors and authored/resolved off-mesh links. map_columns (8..160) adds the flat ASCII map. floor (height in metres) adds a floor-specific layered ASCII map. Both are renderer-free and include terrain plus static non-sensor colliders; spawned structures add to navigation at runtime. | Yes |
| `navigation.query` | Find a walking path headlessly. Backward-compatible from/to [x,z] uses the flat grid and returns [[x,z]…]. Matching [x,y,z] inputs use the layered navmesh, ramps, steps and authored links and return 3D waypoints plus resolved endpoint floors. Returns reachable, layered, straight, length and endpoint walkability. Scripts use find_path(x1,z1,x2,z2) for flat routes; AI uses layered paths. | Yes |
| `navigation.set` | Patch navigation settings with a strict JSON merge: enabled; cell_size 0.5..4 m; max_slope 0.05..1; agent_radius 0..5 m; head_height 0.5..20 m; margin 0..500 m; bounds [min_x,min_z,max_x,max_z] (all zeros automatic); flow_radius/flow_interval; enemies; agents; multi_floor (layered routes for upper floors and ramps); step_height 0..2 m (highest step between floors); drop_height 0..20 m (highest ledge walked off, one way); links [{from:[x,y,z],to:[x,y,z],bidirectional}] for ladders, jumps and drops. Arrays replace. Undoable. | No |

## physics

| Command | Description | Read-only |
| --- | --- | --- |
| `physics.fit` | Size an object's collider to its model, read from the GLB (no renderer needed). Sets physics.half_extents and center to the model's bounds in the object's local space (its scale applies on top). collider: Box (default: keep the current kind), Sphere, Capsule, ConvexHull or Mesh; ConvexHull/Mesh are built from the meshes at runtime and the fitted box stands in until then. body: optional Static\|Dynamic\|Kinematic\|None. Built-in primitives are fitted from their known shape. Returns the fitted half_extents, center and model bounds. | No |
| `physics.get` | Physics overview: settings (gravity [x,y,z] m/s², substeps 1..8, wind [x,y,z] m/s, gust 0..1, affect_player), every body preset and template with what it does, and the open level's dynamic bodies, water volumes, force fields, joints and cloth. Per-object physics lives on scene objects: physics {body, collider, mass, friction, restitution, gravity_scale, linear_damping, angular_damping, ccd, lock_position, lock_rotation}, water, force_field, joint, cloth and boat {sail, thrust, turn_rate, max_speed, keel, helm, prompt} (the player takes the helm with interact; W/S sail and throttle, A/D rudder, jump anchor) — change them with scene.update. Verify motion headlessly with simulation.run. | Yes |
| `physics.preset` | Apply a one-click body preset to objects: none, static, prop (10 kg tumbling), crate (25 kg box, floats), heavy (2.5 t, sinks), ball (bouncy sphere with CCD), ice (frictionless), balloon (rises), projectile (fast, CCD), platform (kinematic), trigger (sensor), ragdoll_part. Sets body type, mass, friction, bounce, damping and gravity scale in one undo step. Groups and child objects cannot be dynamic. | No |
| `physics.set` | Patch the project physics settings with a JSON merge: gravity [x,y,z] (each -100..100 m/s², Earth [0,-9.81,0], Moon [0,-1.6,0]); substeps 1..8 (steadier stacks and chains); solver_iterations 1..16 (default 4; stiffer stacks vs cost); ccd_substeps 1..8; wind [x,y,z] m/s (sways vegetation, blows cloth and smoke, pushes bodies); gust 0..1; foliage_sway 0..3; wind_pushes_bodies; affect_player (force fields and water push and float the player); impacts (the whole list of {surface, sound, effect, min_speed, volume}, one per surface; physics.setup_impacts makes a starter set). Undoable. | No |
| `physics.setup_impacts` | Give physics impacts sound and particles in one step: adds the effects impact_dust, impact_sparks, impact_splinters and impact_splash (if missing; tune them with vfx.upsert) and an impact rule per surface (Default, Wood, Metal, Stone, Dirt, Glass, Water) using the project's own sounds matched by name. Existing rules are kept unless replace is true. Bodies pick a rule by physics.surface (scene.update); harder hits are louder; Water plays when a body splashes into a water volume. Edit rules with physics.set {impacts:[…]}. | No |
| `physics.simulate` | Editor Simulate: action start runs physics in the editor without the player or scripts (bodies fall and settle, joints swing, water floats things, cloth drapes); keep writes the moving bodies' simulated positions and rotations back to the scene as one undo step and stops; stop resets everything. Use it to place rubble, settle stacks or drape things naturally. Live editor only. | No |
| `physics.spawn` | Add a ready-made physics setup at position [x, y, z] (terrain-relative; default 6 m in front of the spawn): water (buoyant, swimmable pool with a crate), river (water with a current), ocean (an endless sea at that height reaching the horizon), ship (a sailing ship with a helm: put it on water), car (a car on raycast wheels: interact to drive; scripts drive it with vehicle_input), flag / banner / curtain (cloth), wind_zone, jump_pad, attractor (pulls in), tornado (vortex), hinge_door, chain (linked bodies), wrecking_ball (rope), crate_stack. Returns the new ids (main first) and selects the main object; tune them with scene.update. | No |

## prefab

| Command | Description | Read-only |
| --- | --- | --- |
| `prefab.apply` | Push the overrides of the linked instance owning object id into its prefab asset (changed fields, removed members, added objects; an added instance of another prefab becomes a nested prefab, rejected if that would make the prefab contain itself), then propagate to every other linked instance keeping their overrides. The root's transform stays the instance's own. At most 128 members. One undo step; returns prefab_id and instances_updated. | No |
| `prefab.capture` | Snapshot scene members into a reusable assembly, expanding each selected Group's complete subtree (at most 128 members). Internal parents and typed contact IDs follow placed copies. Optional replace_id replaces the template and propagates it to every linked instance (overrides are kept); a template cannot contain an instance of itself. Capturing a linked instance root creates a nested prefab. | No |
| `prefab.duplicate` | Copy a reusable assembly into a new library template. The copy reuses the same members, provenance and placed-copy behaviour; name defaults to "&lt;name> copy". | No |
| `prefab.export` | Write a portable template manifest with only its referenced GLBs beside it. Does not change the document or its revision. | No |
| `prefab.get` | Read one reusable assembly: roots store world offsets from the pivot; children store parent-local transforms. Also returns member_keys [{key, name, kind, nested_prefab}] (the stable keys prefab.update and overrides use), instances [{level, root_id, name}] across every level and nested_prefabs. | Yes |
| `prefab.import` | Import a portable .feather.prefab.json and its adjacent GLBs inside the workspace as one undoable template, deduplicating model content. | No |
| `prefab.instance_get` | Read the linked prefab instance that owns an object (the root or any member): root_id, prefab_id, prefab_name, wrapper_root, member_key of this object, members [{key, id, name}], added (object ids added under the instance), overrides [{member, path, value}] where path is a dotted field path (name, color, position, material.roughness, physics.body, script.source, blueprint.nodes, tags…), "$removed" (member key deleted from the instance) or "$added" (member = the added object id); the root's own position/rotation/scale are the instance transform, not overrides. Also nested_prefabs and instance_count. not_found when the object is not in an instance. | Yes |
| `prefab.instantiate` | Place an assembly at a terrain-relative XYZ origin with fresh IDs and rebased internal parents/contact filters. Terrain compensation applies to roots once; children keep parent-local transforms. One undo operation. linked (default true) places a linked instance: its root stores prefab {id, overrides} and every member its template key, so prefab.update / prefab.apply reach it. A prefab with several roots gets a wrapper Group root named after it (unless a root is a Dynamic/Kinematic body: then it is placed unlinked and note says why). linked false stamps plain copies. Returns object_ids, root_id, linked. | No |
| `prefab.list` | Read reusable assembly summaries without expanding their scripts or Blueprint graphs: id, name, member_count, instance_count (linked instances in every level plus nested copies inside other prefabs) and nested_prefabs (prefab ids this one contains). | Yes |
| `prefab.remove` | Remove a reusable template from the library. Its linked instances (in every level and nested in other prefabs) become plain objects in the same undo step; returns instances_unlinked. | No |
| `prefab.rename` | Rename a prefab in the project library. | No |
| `prefab.revert` | Revert overrides of the linked instance owning object id back to the prefab. No member and no path: revert every override on the instance. member (template key from prefab.instance_get, or an added object's id) without path: all of that member's overrides. path (e.g. color, physics, physics.body, $removed, $added; a prefix reverts nested fields too) reverts one override; member then defaults to id's own key. Reverting $removed restores the member; reverting $added deletes the added object. One undo step; returns reverted and the updated instance. | No |
| `prefab.unlink` | Turn the linked instance owning object id back into plain objects: the root loses its prefab link and members lose their keys. Objects and values are unchanged. One undo step; returns root_id and unlinked. | No |
| `prefab.update` | Edit a prefab asset and propagate the change to every linked instance in every level and every prefab that nests it (recursively), keeping each instance's overrides. Fields: name (1..200 chars), members [{key, patch}] where key comes from prefab.get member_keys and patch has the scene.update patch shape (component objects merge, null removes; no parent_id), remove_members [key] (removes the member and its subtree). Root positions are offsets from the pivot. One undo step; returns instances_updated. | No |

## probe

| Command | Description | Read-only |
| --- | --- | --- |
| `probe.add` | Place a light probe. kind volume: an irradiance volume, a box of ambient cubes whose baked bounce light and sky visibility replace the flat ambient and sky light inside it (dark interiors, caves, lamp-coloured bounce), blending out over its last metre; value {id (default volume_N), center [x,y,z] m world space, size [x,y,z] full box size 0.5..1000 m, yaw degrees, resolution [x,y,z] voxels 1..32 (about one per 1.5..3 m), intensity 0..16 multiplies the baked bounce (default 1), fill 0..1 share of sky and ambient light kept however enclosed (0 physically dark, default 0.7; graded stylized scenes crush lower values to black)}. kind reflection: a cubemap captured at center and used for reflections and diffuse light inside its box; value {id, center, size, yaw, resolution 32\|64\|128\|256 (default 128), intensity}. Ids are shared by both kinds. Bevy draws up to 8 of each per view. New probes are unbaked: run lighting.bake_probes. | No |
| `probe.remove` | Remove one light probe (volume or reflection) by id. | No |
| `probe.update` | Patch one probe by id (JSON deep merge; arrays replace). Same fields as probe.add for its kind; patch.id renames it. Moving or resizing a baked probe marks it stale until it is baked again. | No |

## project

| Command | Description | Read-only |
| --- | --- | --- |
| `project.audio` | Patch master volume, the music loop and day/night ambience loops (sound IDs from sound.import; null clears). Ambience cross-fades with the time of day. | No |
| `project.create` | Create a new project. It starts in the stylized meadow look (library textures and grass, see landscape.style) when the texture library has them; style picks another (meadow, forest, autumn, desert, snow, alpine, with its forest left to landscape.style) or none for plain colours. Unsaved data requires explicit discard_changes. | No |
| `project.environment` | Patch sky, time of day, day length, sun direction, fog, bloom, ambient occlusion, exposure, sky light, the water look (water: shallow/deep colours, clarity m, opacity, stylized, foam/foam_color/foam_width, caustics/caustics_scale, sparkle, cell_lines, ripples, waves/speed/scale, wave_height/wave_length/whitecaps/choppiness/foam_persistence (10 wave layers from long swells to chop; storms raise them), reflections, refraction, crest_glow, detail, wakes, spray, interactive (the live surface simulation around the camera: enabled, area m, resolution, stir, swirl, flow_seconds, foam, foam_seconds, ripples, ripple_speed, ripple_seconds, flow_look, carry, iterations; scripts water_stir/water_splash/water_foam; verify with --verify-run water_sim), underwater_color/visibility/distortion/caustics/rays/particles (the submerged look: fog colour and range, wobble, dancing caustic light over everything, light shafts from the surface, marine snow and rising bubbles)), the night look (night: moon colour/strength/height, night sky colour, ambient fill colour/strength) a painted skybox panorama (skybox: texture_id, rotation, horizon, brightness, lighting, match_colors; set one with sky.use / sky.import), sun shadows (shadows: cascades 1..4, first_cascade_far_bound m, max_distance m (0 = follow fog), map_size 512/1024/2048/4096, depth_bias, normal_bias), the physical sky (atmosphere: air, air_tint, haze, haze_glow, ozone, height_km, sun_size, sun_brightness, distance_haze, stars), volumetric clouds (clouds: enabled, coverage, density, altitude m, thickness m, scale, speed, shadows, color, detail, stylized), backdrop (placeholder mountains, off by default), a painted sky (sky_dome: enabled, zenith, horizon, blend, moon, moon_color, moon_size, moon_glow, moon_azimuth, moon_elevation; the moonlight follows the moon) and volumetric fog (volumetric: enabled, colour, density, scattering, absorption, asymmetry, light_intensity, ambient, steps, radius, height, floor; the sun/moon and lights with volumetric:true light it). Nested objects merge. Physical uses an HDR scattering atmosphere; Classic keeps the stylised flat sky. | No |
| `project.game` | Patch game settings: minutes_per_second (in-game clock rate; 0 leaves time to the environment), start_day, start_hour, mouse_look, mouse_capture (OnPlay \| OnClick \| Never), interact_distance, character_library_asset_id (shared clips for spawned characters), stylesheet (real CSS for the game UI, applied after the engine theme: tag/.class/#id/[attr] selectors, :hover/:nth-child/:has(), flexbox and grid, position: absolute with top/right/bottom/left, colours, borders, border-radius, padding, margin, font-size, font-weight, gradients, transition, @keyframes, variables; screens are UI markup whose root is a &lt;panel>) with top/right/bottom/left offsets). Scripts draw screens with ui_html(id, markup). interact_key names the interact key. templates override the built-in story/save screens. saves: autosave_minutes, autosave_each_day, autosave_slots (rotating auto, auto-2…), manual_slots, quicksave_key/quickload_key, thumbnails, pause_menu. | No |
| `project.inspect` | Read the complete project manifest, selection and history state. | Yes |
| `project.open` | Open a portable project inside the workspace: a split `&lt;Name>.feather` project folder (manifest + content/ parts) or a single-file project.feather.json. Unsaved data is preserved by default. | No |
| `project.player` | Patch the player: camera (FirstPerson/ThirdPerson), avatar model and animation library asset IDs (a library with other bone names is retargeted onto the avatar automatically), avatar scale, camera distance, walk/run/sprint/jump speeds, locomotion clip names, animation_graph, and attachments [{model_asset_id, bone (humanoid name such as rightHand, or the rig's own), offset [x,y,z] m, rotation [x,y,z]°, scale}] held by the third-person avatar (a rifle in the right hand; hand_ik on its graph puts the left hand on it). | No |
| `project.post_process` | Patch the post-process volume: tonemapping (TonyMcMapface, AcesFitted, AgX, BlenderFilmic, Reinhard, ReinhardLuminance, SomewhatBoring, None), exposure (stops), auto_exposure, temperature, tint, hue (degrees), saturation, contrast, gamma, shadows (lift), highlights (gain), bloom_threshold, vignette, chromatic_aberration, depth_of_field, focus_distance, aperture, bokeh, motion_blur, anti_aliasing (Msaa, Smaa, Taa, Fxaa, None), sharpening, reflections (screen-space, deferred), reflection_roughness, global_illumination (screen-space bounce light; deferred; off on Low quality), gi_intensity (0..4, 1 = physical), gi_distance (metres light bounces, 0.5..30), film_grain, grain_size px, vignette_softness, vignette_roundness, vignette_color (vignette, grain and the LUT are a GPU pass after tonemapping: they show in the player, screenshots and thumbnails), grading {shadows\|midtones\|highlights: {saturation, contrast, gamma, gain, lift}, midtones_range [start, end]} (per-range grading multiplied onto the globals), lut_texture (project texture id of an N²×N LUT strip; texture.import converts .cube files) and lut_intensity 0..1, outline 0..1, outline_thickness px, outline_color, outline_depth_threshold, outline_crease, outline_fade_distance m, bloom_intensity_min/max (how environment.bloom 0..1 maps to Bevy intensity), bloom_soft_knee, bloom_scatter, ssao_quality Low\|Medium\|High\|Ultra, ssao_thickness m, auto_exposure_min_ev/max_ev, auto_exposure_speed_brighten/darken (EV/s). Nested objects (grading) merge. preset loads a named look first: Neutral, Stylized (toon-friendly grading, ink outlines, soft bloom), Warm storybook, Vivid, Cinematic, Cold moonlight, Faded film, Dreamy, Noir. Bloom strength, SSAO on/off and sun shadows live in project.environment. | No |
| `project.save` | Validate and save the project and adjacent assets. Paths are relative to the workspace. A path ending in .feather saves an project folder: a small manifest plus content/ (levels/&lt;level>/objects\|landscape\|environment.json, scripts/&lt;name>.rhai, ui/screens/&lt;name>.html\|.css, data/&lt;section>.json, data/tables/&lt;name>.json, config/&lt;section>.json), rewriting only changed files; one .feather project per folder. Any other path (e.g. project.feather.json) saves one JSON file. Save a single-file project to &lt;Name>.feather to convert it. | No |
| `project.settings` | Patch the project name, terrain seed, streaming radius, player spawn position or spawn facing (spawn_yaw). spawn_position [x, y, z]: y is metres above the ground; a negative y is that far below the surface, standing on the cave or tunnel floor there (landscape.caves returns ready spawns). player_start false deletes the level's player start (the player then spawns at 0,0,0 on the ground); true, or setting a spawn, adds it back. | No |
| `project.validate` | Validate scene data, enabled scripts and Blueprint graphs without changing the document. | Yes |

## runtime

| Command | Description | Read-only |
| --- | --- | --- |
| `runtime.logs` | Read runtime score, script messages, errors and model loader diagnostics from the live editor. | Yes |
| `runtime.play` | Start or stop the real editor simulation. Authoring edits are locked during Play. from_camera starts at the editor camera's position facing where it looks (Play from here) instead of the project spawn. | No |
| `runtime.stats` | Live performance from the running editor: rolling frame-time percentiles (p50/p95/p99, worst, average fps, hitches over 3× median) for the last ~600 frames, peak memory, and counts of entities, meshes, lights, particles (vfx and combat), effects, decals, enemies, playing sounds, agents, rigid bodies and navigation cells. Start Play first to measure the game. feather-player --verify-run writes the same block as runtime_stats. | Yes |

## save

| Command | Description | Read-only |
| --- | --- | --- |
| `save.delete` | Delete a save slot with its backup and thumbnail. | No |
| `save.inspect` | Read one save: metadata plus the full body (clock, script state, runtime objects, terrain edits, player, authored changes, quest progress, dialogue memory, play time). Damaged saves fall back to their backup. | Yes |
| `save.list` | List the game's save slots (newest first) with label, real save time, in-game day/minute, play time and thumbnail flag. Saves live in the Feather data folder per project; autosaves rotate through auto, auto-2, … | Yes |

## scene

| Command | Description | Read-only |
| --- | --- | --- |
| `scene.add` | Add a Group, primitive or registered GLB as one undoable edit. Optional parent_id must name a Group; position is parent-local for children (default zero), terrain-relative for roots. | No |
| `scene.batch` | Atomically apply at most 64 authoring commands as one undo operation. IO, history and nested batches are excluded. | No |
| `scene.copy` | Read a versioned object clipboard snapshot without changing data, selection, revision or history. Includes a Group's complete subtree (at most 128 objects), components and world pose; works after the source is edited or deleted. Pass the returned clipboard string unchanged to scene.paste. Referenced assets/settings stay in the project. | Yes |
| `scene.duplicate` | Duplicate an authored subtree with fresh IDs, rebased internal parents and typed Blueprint contact references. Literal script references remain unchanged. One undo operation. | No |
| `scene.get` | Read one authored object by its stable ID. | Yes |
| `scene.list` | Read all authored objects and the current selection. | Yes |
| `scene.paste` | Paste the clipboard string returned by scene.copy as independent objects with fresh IDs. Default: one world metre right of the captured position, preserving world altitude. Optional position [x,y,z] metres is terrain-relative. A copied child becomes a root at its world pose; Group children retain local transforms. Internal parents, Blueprint contact filters, joint targets and switch targets follow the copies; external references and script text stay literal. Referenced assets/settings must exist in this project. Selects the first pasted object; one atomic, undoable edit. | No |
| `scene.remove` | Remove an authored object and its complete subtree as one undoable edit. | No |
| `scene.reparent` | Move an authored object/subtree under a Group, or detach with parent_id:null. preserve_world defaults to true; false retains authored local values. Reject cycles, missing/non-Group parents, and nonrepresentable transforms atomically. One undo operation; supported in scene.batch. | No |
| `scene.select` | Select an object, or clear selection with null; does not alter undo history. | No |
| `scene.update` | Patch an object; component fields merge and blueprint graphs replace. A fog patch fills a box with volumetric fog lit by the sun and volumetric lights ({size [half x,y,z] m, color, density 0..1, absorption, scattering, asymmetry -1..1, light_intensity, wispy (drifting noise), drift [x,y,z] m/s}; null removes it). A destructible patch makes it breakable ({material Concrete\|Brick\|Stone\|Wood\|Glass\|Metal\|Plaster\|Sandbag, health (0 = from material and size), pieces 2..64, pattern Chunks\|Splinters\|Shards\|Bricks\|null, structural, anchor Auto\|Always\|Never, explosive (blast radius m), debris, impact_speed (m/s: shatters when it hits the ground that fast; needs a Dynamic body)}; null removes it; destruction.make does many at once). A light patch adds a point/spot light (null removes it). tags labels the object for world scripts (with_tag); interact shows an E prompt when the player is near and fires on_interact(id). A sound patch attaches a looping or one-shot emitter (spatial range, Day/Night schedule; null removes). material_slots replaces the object's material overrides (slot null = every mesh, or a glTF material name). An animation patch plays a named clip on a Model, optionally from another GLB used as an animation library (null removes it). Child transforms are parent-local, root positions terrain-relative. Change parent_id through scene.reparent. Built-in material fields author the surface (Tree canopy, Cottage walls); Models retain embedded materials and reject non-default overrides. Null removes script/blueprint/asset references. | No |

## script

| Command | Description | Read-only |
| --- | --- | --- |
| `script.api` | The typed script API: every function scripts can call (name, group, params [{name, type}], returns, doc) and every hook the engine calls (name, params, doc). These are the visual editor's nodes. group / search filter it. | Yes |
| `script.check` | Strict static check of a world or object script: syntax, reserved words, unknown functions (with did-you-mean), wrong argument counts, misspelt or wrong-kind hooks and string-named callbacks that do not exist. Returns findings [{severity, code, message, line, column}] and ok (no errors). Source: source + kind (world\|object), world_script (name; other world scripts' shared helpers count) or object (id). world_script.set and script.validate reject scripts with errors. | Yes |
| `script.from_graph` | Write a graph (script.to_graph's format) back as Rhai. Returns the source; with world_script (name) or object (id) it also replaces that script (undoable). The printed code must compile. Text and graph are two views of one program: edit whichever is easier. | No |
| `script.recipes` | Short, working Rhai snippets for common gameplay (door, pickup, wave spawner, timed objective, ...) using timers, events, vec3 and object handles. id picks one. | Yes |
| `script.to_graph` | Read a Rhai script as a graph (the visual editor's model): items [function {name, params, comments, body} \| statement]. Statements (kind): comment, let, assign {target, op, value}, run {expr}, if {cond, then, otherwise}, while, loop, for {var, index, iter, body}, return, throw, break, continue, block, code {source} (kept verbatim when no node fits). Expressions (kind): int, float, bool, str, char, unit, var, this, call {name, args}, method {target, name, args}, prop, index, binary {op, left, right}, unary, array, map {entries [[key, value]]}, interp {parts}, select {cond, then, otherwise}. Source: source (text), world_script (name) or object (id). Also returns how many statements are nodes vs code. | Yes |
| `script.validate` | Validate Rhai syntax and start()/update(dt) entry points without executing code. | Yes |

## season

| Command | Description | Read-only |
| --- | --- | --- |
| `season.get` | Read the season settings: current (Spring, Summer, Autumn, Winter), automatic (move through the seasons with the in-game calendar during Play) and days_per_season. | Yes |
| `season.set` | Patch the season: current Spring\|Summer\|Autumn\|Winter, automatic, days_per_season 0.05..365. Generated broadleaf trees get fresh leaves and blossom in spring, are as designed in summer, turn orange/red/gold in autumn and go bare in winter; pines and palms stay green (snow-dusted in winter). Falling leaves (autumn leaves, spring petals) and grass (golden patches, frosted tips) follow. Scripts: set_season(name), season(). | No |

## sequence

| Command | Description | Read-only |
| --- | --- | --- |
| `sequence.get` | Read sequences, template names, or id's complete tracks. Legacy camera/events are exposed as effective tracks. | Yes |
| `sequence.key_remove` | Remove zero-based key index from track of sequence id. One undo step. | No |
| `sequence.key_upsert` | Append a typed key value to track of sequence id, or replace zero-based index. Keys sort by time after editing. Use sequence.get to discover the track's current keys; value must match its track kind. One undo step. | No |
| `sequence.sample` | Evaluate id at time (0..600 seconds) without playing or changing anything. Returns camera, object poses, visibility, clips, audio gains, subtitle, post look, hour/weather and active shots. | Yes |
| `sequence.template` | Create an orbit_reveal, dolly_push_in, two_shot_dialogue or flyover sequence. id must be new; center/other are world XYZ metres, radius metres (default 6), duration seconds (default 8); title and speaker_a/b optional. One undo step. | No |
| `sequence.track_remove` | Remove sequence id's track; rejects camera/shot references that would dangle. One undo step. | No |
| `sequence.track_upsert` | Create or replace a track of sequence id. track {id,name,muted,locked,kind {type,object?,keys}}; Camera/Cuts, Transform, Visibility, Animation, Audio, Event, Actions, Subtitles, Post, Environment or Shots. Times/durations seconds, positions metres, rotations degrees, FOV degrees; key ease supports Curve [x1,y1,x2,y2]. One undo step; invalid references are rejected atomically. | No |

## sky

| Command | Description | Read-only |
| --- | --- | --- |
| `sky.import` | Import a sky panorama inside the workspace (a 2:1 PNG, a folder containing one, or a skybox .zip such as freestylized.com's) and make it the skybox (environment.skybox: drawn infinitely far behind everything, lighting the scene in its colours; fog, horizon, water and ambient colours follow it while match_colors is on). align_sun (default true) turns a painted sun to where the scene's sun is. Returns the texture id, the skybox settings and the measured colours (horizon, sky, brightness, sun azimuth/elevation). One undo step. | No |
| `sky.library` | List the skies in the local sky library (folders with a 2:1 equirectangular panorama PNG; scripts/fetch_skies.py downloads the free 2K skyboxes from freestylized.com into it). Returns name, panorama path, size and source/terms. | Yes |
| `sky.use` | Use a sky from the local sky library by name (see sky.library) as the skybox, like sky.import. One undo step. | No |

## smoke

| Command | Description | Read-only |
| --- | --- | --- |
| `smoke.get` | Read interactive fog and smoke settings (enabled, voxel, max_cells, max_fields, stir, carve, refill_seconds, blasts_smoke, fire_smoke) and the objects with fog volumes (interactive or not). | Yes |
| `smoke.set` | Patch interactive fog and smoke (JSON merge): enabled; voxel 0.15..2 m (grid spacing of runtime smoke); max_cells 8..96 per side of one field; max_fields 1..16 runtime smoke fields at once; stir 0..3 (how hard the player, enemies and dynamic bodies drag the air, leaving swirling eddies); carve 0..1 (how much smoke they push out of the space they fill); refill_seconds 0.2..120 (a hole in fog fills back in); blasts_smoke (explosions raise interactive smoke instead of plain dust); fire_smoke (big fires send up smoke columns). Fog volumes are objects with scene.update {fog {size, color, density, …, wispy, drift, interactive}}; interactive ones are simulated. Runtime smoke: scripts smoke(x, y, z, radius, amount[, seconds]), clear_smoke, stir_smoke (gusts, fans), smoke_density(x, y, z) for stealth checks; tool items with cast.smoke (smoke_bomb preset). --verify-run reports smoke. | No |

## sound

| Command | Description | Read-only |
| --- | --- | --- |
| `sound.import` | Import an OGG, WAV or MP3 inside the workspace as a project sound for emitters, music, ambience and scripts. | No |

## steam

| Command | Description | Read-only |
| --- | --- | --- |
| `steam.get` | Read the steam settings: app_id 0..4294967295 — Steamworks App ID. 0 turns Steam uploads off; depot_windows 0..4294967295 — Depot that receives the Windows build (0: not shipped on Windows); depot_macos 0..4294967295 — Depot that receives the macOS build (0: not shipped on macOS); depot_linux 0..4294967295 — Depot that receives the Linux build (0: not shipped on Linux); branch — Branch an upload goes live on, e.g. beta. Empty only uploads; set it live in Steamworks | Yes |
| `steam.set` | Patch the steam settings (JSON merge). Where the game is published on Steam. Uploads run from the game's GitHub workflow. Fields: app_id 0..4294967295 — Steamworks App ID. 0 turns Steam uploads off; depot_windows 0..4294967295 — Depot that receives the Windows build (0: not shipped on Windows); depot_macos 0..4294967295 — Depot that receives the macOS build (0: not shipped on macOS); depot_linux 0..4294967295 — Depot that receives the Linux build (0: not shipped on Linux); branch — Branch an upload goes live on, e.g. beta. Empty only uploads; set it live in Steamworks. | No |

## story

| Command | Description | Read-only |
| --- | --- | --- |
| `story.get` | Read story content: with kind and id, the full item; with neither, a summary of all dialogues, quests and cinematics plus the settings. | Yes |
| `story.remove` | Delete a dialogue, quest or cinematic by id. | No |
| `story.set` | Create or replace a story item. kind: dialogue \| quest \| cinematic; value: the full item (see story.get for the shape). Dialogue {id, title, start, camera Free\|Speaker\|OverShoulder\|Wide, nodes:[{id, speaker ('npc'\|'player'\|object name\|display name\|'' narrator), text ({key} inserts script state, {npc} the partner's name; empty text = silent logic step that branches on the first passing choice), condition (Rhai bool; false skips to next), action (Rhai run on enter), next, choices:[{text, next ('' ends), condition, action, once, show_disabled, hint}], voice (sound name), animation (clip), camera, auto_advance (s), editor_position}]}. Quest {id, title, description, category, start, auto_start (Rhai bool), stages:[{id, objective, details, complete_when, fail_when, progress (Rhai number), goal, on_enter, on_complete, next ('' completes), target (object name for the marker)}], on_complete, on_fail, reward, hidden}. Cinematic {id, title, duration, anchor (object name; keys and moves become relative to it), letterbox, skippable, hide_hud, blend_out, camera:[{time, position, target, fov, ease Linear\|Smooth\|EaseIn\|EaseOut\|Cut}], events:[{time, action:{type: Subtitle{speaker,text,duration} \| Animation{object,clip,looping} \| Move{object,to,speed} \| Place{object,to,yaw} \| Show{object,visible} \| Sound{sound,volume} \| Fade{opacity,duration,color} \| Shake{strength,duration} \| TimeOfDay{hour} \| Dialogue{id} \| Script{code}}}], on_end}. Rhai snippets share the world scripts' helpers. rename_from renames an existing id. Scripts use start_dialogue(id[, npc]), start_quest/set_quest_stage/complete_quest/fail_quest/quest_status, play_cinematic(id); hooks on_dialogue_end(id), on_quest(id, status, stage), on_cinematic_end(id). | No |
| `story.settings` | Patch story settings: text_speed (chars/s, 0 instant), dialogue_camera, pause_clock_in_dialogue, quest_tracker, quest_markers, quest_check_interval (s), journal_key, skip_key, advance_keys, letterbox (0..0.3 of screen height), subtitles. | No |

## texture

| Command | Description | Read-only |
| --- | --- | --- |
| `texture.import` | Import a PNG inside the workspace as a project texture (terrain layers, materials, skies). A .cube 3D colour LUT is converted to an N²×N PNG strip for project.post_process lut_texture. Identical images share one stored file. | No |
| `texture.library` | List the local stylized texture library (scripts/fetch_textures.py downloads ~240 free PBR sets from freestylized.com): name, category (bark, grass, ground, sand, rock, moss, snow, wood, stone, brick, roof, tiles, metal, concrete, other), size, whether it has normal and roughness maps. Filter by category or query. | Yes |
| `texture.library_add` | Add your own texture to the library: path to a folder of PNG maps (named \*_color\*, \*_normal\* (OpenGL), \*_roughness\*, \*_ao\*, \*_height\*) or a single PNG (its siblings sharing the name prefix come along; a lone unnamed PNG is the colour). name defaults to the folder or file name. Then use it like any library texture (landscape.texture_layer, tree.bark, texture.library_import). | No |
| `texture.library_import` | Copy a library texture's colour and OpenGL normal map into the project as project textures (named after it); returns their ids for materials and terrain layers. One undo step. | No |

## traversal

| Command | Description | Read-only |
| --- | --- | --- |
| `traversal.get` | Read the traversal settings: swing (on/off) — Rope swinging from anything solid up and ahead (hold Grapple in the air); swing_range 5..120 m — How far up and ahead a rope can catch; swing_pump 0..40 m/s² — Pumping the swing (W) and the rope reeling in, m/s²; swing_release 0.5..2 — Speed kept (and added) when letting go of a swing, ×; swing_sky (on/off) — With nothing to catch, the rope still swings from the sky (an open-world superhero); zip (on/off) — Grapple zip to the aimed point (Ability); zip_range 5..150 m; zip_speed 5..80 m/s; wall_run (on/off) — Run along walls (hold Sprint in the air beside one; Jump kicks off); wall_run_speed 2..30 m/s; wall_run_seconds 0.2..6 s; wall_jump 2..25 m/s; super_jump (on/off) — Charged jump (hold Crouch, then Jump); super_jump_height 2..120 m — Height of a fully charged jump; super_jump_charge 0.1..4 s; glide (on/off) — Glide while falling (hold Jump); glide_sink 0.2..10 m/s — How fast a glide sinks; glide_speed 2..60 m/s; flight (on/off) — Free flight (double-tap Jump in the air); flight_speed 2..120 m/s; flight_boost 1..5 — Flight speed while Sprint is held, ×; air_control 0..1 — How much the player steers their momentum in the air after an ability (0..1) | Yes |
| `traversal.set` | Patch the traversal settings (JSON merge). Which abilities the player has and how they feel. Fields: swing (on/off) — Rope swinging from anything solid up and ahead (hold Grapple in the air); swing_range 5..120 m — How far up and ahead a rope can catch; swing_pump 0..40 m/s² — Pumping the swing (W) and the rope reeling in, m/s²; swing_release 0.5..2 — Speed kept (and added) when letting go of a swing, ×; swing_sky (on/off) — With nothing to catch, the rope still swings from the sky (an open-world superhero); zip (on/off) — Grapple zip to the aimed point (Ability); zip_range 5..150 m; zip_speed 5..80 m/s; wall_run (on/off) — Run along walls (hold Sprint in the air beside one; Jump kicks off); wall_run_speed 2..30 m/s; wall_run_seconds 0.2..6 s; wall_jump 2..25 m/s; super_jump (on/off) — Charged jump (hold Crouch, then Jump); super_jump_height 2..120 m — Height of a fully charged jump; super_jump_charge 0.1..4 s; glide (on/off) — Glide while falling (hold Jump); glide_sink 0.2..10 m/s — How fast a glide sinks; glide_speed 2..60 m/s; flight (on/off) — Free flight (double-tap Jump in the air); flight_speed 2..120 m/s; flight_boost 1..5 — Flight speed while Sprint is held, ×; air_control 0..1 — How much the player steers their momentum in the air after an ability (0..1). | No |

## tree

| Command | Description | Read-only |
| --- | --- | --- |
| `tree.bark` | Give a tree design a library bark (colour + normal map, imported into the project), e.g. bark_09 (swirly oak), bark_10 (birch), bark_06 (pine), bark_05 (cherry), bark_11 (palm), bark_02 (willow), bark_01 (dead); null goes back to the built-in painted bark. bark_color then tints it. | No |
| `tree.forest` | Create or update a foliage layer (default name Trees) that scatters tree designs over the terrain, with trunk colliders, wind sway, LOD and damage: trees [design ids], density per 100 m² (default 1.5), scale [min, max], max_slope 0..1, view_chunks 1..8, placement Everywhere (default; paint areas out with landscape.brush FoliageErase) or Painted (trees only where landscape.brush FoliagePaint paints them in), clustering 0..1 (0 even; towards 1 groves with glades between) and cluster_size m 4..500 (grove size). remove true deletes the layer. For a ready layered forest (trees, giants, understory) use tree.forest_kit. | No |
| `tree.forest_kit` | Plant a ready-made stylized forest in one step: woodland (oak/maple/birch groves with glades, a few ancient giants, bushes, berry bushes and saplings), conifer (spruce, pine, cypress stands), autumn, savanna (lone acacias), orchard (apple and cherry rows), ancient (mossy giants, willows, thick undergrowth), tropical (palms). Adds the tree designs it needs from presets (with library barks; existing designs of the same id are kept and used as they are) and replaces the kit foliage layers Trees, Giants and Understory (clustered into groves; tweak with landscape.set foliage or tree.forest). density multiplies every layer (0.1..4, default 1); seed reshuffles placement. One undo step. | No |
| `tree.get` | Read the stylized tree designs (id, name, canopy, height, breakable), the presets tree.upsert can start from, and which foliage layers scatter which trees. id returns one design in full. | Yes |
| `tree.inspect` | Generate a tree (a project design or a preset) without saving and report its size and triangle counts per part (trunk, canopy, leaf cards, far trunk). variant 0..3. | Yes |
| `tree.remove` | Delete a tree design (not while a foliage layer scatters it). | No |
| `tree.upsert` | Create or patch a stylized tree design (painterly stylised look; each design yields 4 differently-shaped variants). preset (oak, birch, pine, palm, willow, cherry, autumn, dead, bush, maple, spruce, cypress, acacia, apple, ancient, sapling, berry_bush) starts from a ready look; value patches it: {id, name, seed, height m, trunk_radius m, taper 0..0.95, lean 0..1, bend 0..1, roots 0..1, bark_color sRGB, branches 0..12, branch_angle deg 0..120, branch_length 0..1.5 (share of height), branch_start 0..0.95, upturn 0..1, canopy Puffy\|Pine\|Palm\|Willow\|Bare\|Cone (clumps stacked into a narrow cone: cypress, poplar)\|Umbrella (flat wide crown: acacia), clumps 1..16 (leaf clumps, pine tiers, cone levels or palm fronds), clump_size m, lumpiness 0..1, fluff 0..2 (leafy cut-out cards), leaf_shape Round\|Pointy\|Needles, leaf_color and leaf_shade sRGB (sunlit top, shaded underside), variation 0..1, blossom 0..1 and blossom_color, sway 0..3 (wind), health (bullets ~20, melee ~40, grenade ~200), breakable, wind_limit m/s (storms above it break it; 60 never), crown_width 0.3..2.5 (branch reach, tier and cone width), crown_flatten 0..0.9 (squash clumps into pads), size_variety 0..0.6 (how different the 4 variants' heights are), moss 0..1 and moss_color (on the up-facing bark and roots), fruit 0..1 and fruit_color (fruit or berries in the canopy; blossom in spring), gnarl 0..1 (crooked, knotted trunk and branches), twigs 0..4 (side branches forking off each branch; every branch end holds a leaf clump, so more twigs = more, smaller clumps and a fuller crown), trunk_fork 0..1 (chance the trunk splits into two leaders)}. Trees sway with the world wind and weather; when broken they topple as physics bodies leaving a stump. rename_from renames and updates foliage layers. Scatter them with tree.forest, or plant a whole layered forest with tree.forest_kit. | No |

## ui

| Command | Description | Read-only |
| --- | --- | --- |
| `ui.get` | Game UI screens. Without name: the list (name, description, layer, sizes). With name: the screen {html, css, description, layer, sample}. | Yes |
| `ui.remove` | Delete a game UI screen (undoable). | No |
| `ui.render` | Render a screen with data (default: its sample) without changing anything: returns the markup, its buttons (onclick, id, disabled) and, when it uses widgets, widgets (names), expanded (the markup the game draws) and widget_warnings (unknown props, wrong types). Verifies bindings headlessly. Give name (a project screen) or html (a draft). | Yes |
| `ui.upsert` | Create or change a game UI screen (undoable). html is UI markup (tags panel, div, row, col, h1-h3, p, span, b, button, bar value/max/label, img src, icon name, ul/li, table; class, id, style attributes) with Mustache bindings {{field}}, {{{raw}}}, {{#list}}...{{/list}}, {{^list}}empty{{/list}}, {{a.b}}, {{.}}. &lt;button onclick="buy" data-item="{{id}}"> emits the script event ui:buy with #{screen, item} (on("ui:buy", \|e\| ...)) besides on_ui(screen, button). css joins the game stylesheet (real CSS via bevy_flair: flex/grid, gradients, transition, @keyframes, var(), :hover; scope with #&lt;name>). layer orders screens (-100..100, higher on top). sample is example data used by previews and ui.render. Scripts: ui_show(name[, data]), ui_set(name, key, value), ui_hide(name), ui_toggle(name), ui_shown(name), ui_data(name). Fields not given keep their value; the sample must render. | No |

## ui_widget

| Command | Description | Read-only |
| --- | --- | --- |
| `ui_widget.get` | Reusable game UI widgets (components used in any screen, template or ui_html as custom tags, like reusable widgets or HTML custom elements). Without name: every widget, built-in and project, with description, props [{name, type (string\|number\|bool\|list\|object), default, description, options}], slots and an example usage. With name: also html, css, rendered_example (expanded markup), warnings and used_by (screens/widgets/templates using it). Built-ins: stat-bar, icon-button, item-slot, modal-dialog, key-hint, tab-bar, notice-card, progress-ring. | Yes |
| `ui_widget.remove` | Delete a project UI widget (undoable). Rejected while screens, widgets or templates use it (see ui_widget.get used_by), unless it overrides a built-in: then usages go back to the built-in. | No |
| `ui_widget.render` | Expand widgets without changing anything. Give name with props (JSON values: text, numbers, bools, lists/objects) and children (markup for the slots), or name alone (its example), or markup (any UI markup using widgets). Returns usage, markup (expanded), widgets used, warnings (unknown props, wrong types, options) and buttons. | Yes |
| `ui_widget.upsert` | Create or change a project UI widget (undoable). name: lowercase words joined by hyphens (stat-bar; a hyphen is required); a built-in's name makes an override that starts from the built-in. html: markup with ONE root element; it reads props with Mustache ({{label}}, {{#flag}}…{{/flag}}, {{#list}}…{{/list}}) and helpers {{pct value max}}, {{angle value max from to}} (gauge degrees), {{round x}}, {{json x}}, {{#eq a b}}…{{/eq}}, {{#lt a b}}, {{#gt a b}} (operands: names, numbers, true/false, 'text', or a ratio hp/max). &lt;slot/> takes the usage's children, &lt;slot name="footer"/> the children with slot="footer" (a slot's own children are the fallback); {{#slots.footer}}…{{/slots.footer}} shows a part only when that slot is filled. The root gets the widget name as a class (scope CSS with .name …) and the usage's class, style, id, key, exit and data-\* attributes. Buttons take actions from props (onclick="{{onclick}}"), so the screen decides the ui:&lt;action> signal. css joins the game stylesheet after the UI kit and before the project stylesheet and screens. props: {name: {default, description, options}}; the default's JSON type is the prop's type (usage attributes are converted: numbers, bools (a bare attribute is true), lists/objects as JSON, e.g. items="{{json inventory}}"); options limits a text prop. Prop names: lowercase letters, digits, _ (not class, style, id, key, exit, slot, slots). example: a usage shown in previews (must expand). rename_from renames a project widget and updates its tags in screens, widgets and templates. Widgets may use other widgets (12 deep). Fields not given keep their value. Returns rendered_example and warnings. | No |

## variables

| Command | Description | Read-only |
| --- | --- | --- |
| `variables.get` | Read the game variables (tunable values shown as controls in Window → Game variables): id, label, category, kind (Number, Integer, Toggle, Text, Color), value, default, min, max, step, unit, description, and used_by (the scripts and Blueprints reading each). With id: one variable. Blueprints read them with a tune_number / tune_bool / tune_text Call node (id pin). World scripts read values live with tune("id"). | Yes |
| `variables.remove` | Delete a game variable. Refused with variable_in_use (listing the world scripts, object scripts and Blueprints that read it) unless force: true; forced, those reads fail at runtime (tune("id", fallback) does not). | No |
| `variables.reset` | Reset one game variable (id) or all of them to their defaults. | No |
| `variables.set` | Change one game variable's value (checked against its kind and min..max). Applies at once, even during Play. | No |
| `variables.upsert` | Create or patch a game variable by id (JSON merge; rename with rename_from, which also updates every script and Blueprint reading it): {id, label, category, kind Number\|Integer\|Toggle\|Text\|Color, value, default (reset target; defaults to the first value), min, max, step, unit, description}. The editor shows it as a slider/toggle/field in Window → Game variables; scripts read tune("id"). | No |

## vfx

| Command | Description | Read-only |
| --- | --- | --- |
| `vfx.get` | Read particle effects: no arguments lists effects (id, emitters, duration, peak_particles), the global max_particles budget and which objects play which effect; id returns one full effect with its peak particle estimate. | Yes |
| `vfx.remove` | Delete an effect that no object uses. | No |
| `vfx.set` | Set budgets: max_particles (10..50000) alive at once across all effects; max_decals (0..5000) script decals before the oldest are recycled. | No |
| `vfx.upsert` | Create or patch a particle effect by id (JSON merge; arrays replace). value {id, name, duration (s; 0 = until stopped), sound (sound or cue at start), light (null or {color, intensity, range, flicker 0..1}), emitters [up to 8: {shape Point\|Sphere\|Disc\|Box, size [x,y,z] (radius in x), offset, mesh Cube\|Sphere\|Billboard, blend Opaque\|Additive\|Alpha, burst (count at start), rate (per s), lifetime [min,max] s, speed [min,max], direction [x,y,z], spread 0..1, gravity (negative rises), drag, particle_size [birth, death] m, color_start, color_end [r,g,b 0..1], glow (emissive), spin, settle (stop on terrain)}]}. Objects play effects with scene.update vfx {effect, autoplay, offset}; scripts with spawn_vfx(effect, x, y, z) / stop_vfx(handle). Returns peak_particles. | No |

## viewport

| Command | Description | Read-only |
| --- | --- | --- |
| `viewport.capture` | Capture a PNG in the workspace from the real native viewport/editor. Returns a job_id. | No |
| `viewport.focus` | Frame an authored object in the live editor camera with optional padding. Queues a request; inspect viewport.view.get for completion. Does not change the project or its revision. | No |
| `viewport.frame_scene` | Frame all authored scene objects in the live editor with optional padding. Queues a request; inspect viewport.view.get for completion. Does not change the project or its revision. | No |
| `viewport.image` | Read the actual PNG from a completed viewport capture (at most 16 MiB). MCP returns image content; JSON CLI returns base64 PNG data. | Yes |
| `viewport.sequence` | Scrub a sequence in the live editor and pilot the viewport at time (0..600 seconds). id names a sequence; enabled defaults true, false releases the preview and restores object poses. Returns the evaluated frame. Does not change the project/revision; stop Play first. | No |
| `viewport.view.get` | Read the current live editor orbit camera and pending request IDs. Optionally inspect one framing/view request's completion or failure using request_id. Does not change the project or its revision. | Yes |
| `viewport.view.set` | Set the live editor orbit view using a world-space target, distance and angles in degrees. Queues a request; inspect viewport.view.get for completion. Does not change the project or its revision. | No |

## weather

| Command | Description | Read-only |
| --- | --- | --- |
| `weather.get` | Read the weather: enabled, current (starting weather), automatic forecast {duration_minutes [min, max], transition_seconds, forecast [{weather, weight}]}, wetting/drying minutes, puddles, lightning_damage, volume, sounds, and a summary of each weather. id returns one weather in full. | Yes |
| `weather.remove` | Delete a weather (not the starting one); it leaves the forecast too. | No |
| `weather.set` | Patch weather settings with a JSON merge (arrays replace): enabled, current (a weather id: clear, cloudy, overcast, rain, storm, thunderstorm, snow, blizzard, fog, sandstorm, tornado or a project one), automatic, duration_minutes [shortest, longest] 0.1..240, transition_seconds 0..600, forecast [{weather, weight 0..100}], wetting_minutes, drying_minutes, puddles, lightning_damage (strikes blast destructibles), volume 0..2, sounds {rain, heavy_rain, wind, storm, tornado, thunder_near, thunder_far: project sound id or null for the built-in}. When on, the weather drives clouds, sky light, fog, physics wind, waves, rain/snow, wet surfaces and puddles, lightning and tornadoes. During Play scripts change it with set_weather(id, seconds). | No |
| `weather.upsert` | Create or patch one weather by id (new ones start from mild defaults): {id, name, clouds 0..1, cloud_darkness 0..1, wind m/s 0..60, gust 0..1, rain 0..1, snow 0..1, fog 0..1, fog_color sRGB [r, g, b], light 0..1 (sunlight through), waves 0..3, wetness 0..1 (puddles above ~0.6), lightning strikes/min 0..30, tornado bool}. rename_from renames and updates current and the forecast. | No |

## world_script

| Command | Description | Read-only |
| --- | --- | --- |
| `world_script.remove` | Delete a world script. | No |
| `world_script.rename` | Rename a world script. | No |
| `world_script.set` | Create or update a world (game-mode) Rhai script. The script must compile and pass script.check (unknown functions, wrong argument counts, misspelt hooks are rejected; warnings are returned). Hooks: start(), update(dt), on_minute(hour, minute), on_hour(hour), on_day(day), on_key(key), on_interact(id), on_ui(panel, button), on_place(asset, x, z, yaw, radius), on_place_cancel(asset), on_load(), on_level(level), on_quest(id, status, stage), on_dialogue_end(id), on_cinematic_end(id), on_enemy_killed(id, kind, x, y, z, value), on_player_hurt(amount), on_player_died(), on_pickup(kind, amount), on_anim_event(id, name), on_destroyed(id), on_item(id, count), on_use_item(id), on_equip(slot, id), on_damaged(target, amount, kind), on_status(target, status), on_event(name, data). Essentials: after(s, \|\| ...) / every(s, fn) timers, emit(name, #{...}) / on(name, \|data\| ...) events, vec3(x, y, z) maths, obj(name) / tagged(tag) handles with .position .name .visible .tags .has_tag() .destroy(), get/set(key) state. API groups: Timers & events, Vectors, Object handles, Data & state, Time, Objects, Physics, Weather, Ambience, Items & inventory, Interaction & carrying, Fog & smoke, Snow & sand, Water surface, Fluids, Player body, Live tuning, Elements & damage, Fire, Trees, Boats, Destruction, Characters, AI, Player, Structures & build mode, Terrain, UI screens, Biomes, UI & sound, Localization, Decals, Effects, Navigation, Levels, Input actions, Input, chance & saves, Combat, Player, Weapons, HUD & feel, Profile, Story, Saves, Player settings & quitting, Animation, Procedural levels. script.api lists every function with typed params; script.recipes has working examples. | No |
