@nodetool-ai/agents 0.8.4-a24.3 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/.nodetool-build-stamp +1 -1
  2. package/dist/_skills/LICENSE-iart-motion-design-skills +21 -0
  3. package/dist/_skills/README.md +211 -0
  4. package/dist/_skills/api-agents/SKILL.md +58 -0
  5. package/dist/_skills/api-apps/SKILL.md +54 -0
  6. package/dist/_skills/api-assets/SKILL.md +66 -0
  7. package/dist/_skills/api-collections/SKILL.md +33 -0
  8. package/dist/_skills/api-games/SKILL.md +81 -0
  9. package/dist/_skills/api-media/SKILL.md +131 -0
  10. package/dist/_skills/api-memory/SKILL.md +72 -0
  11. package/dist/_skills/api-models/SKILL.md +78 -0
  12. package/dist/_skills/api-scripts/SKILL.md +79 -0
  13. package/dist/_skills/api-settings/SKILL.md +49 -0
  14. package/dist/_skills/api-sketches/SKILL.md +61 -0
  15. package/dist/_skills/api-storyboards/SKILL.md +94 -0
  16. package/dist/_skills/api-timelines/SKILL.md +151 -0
  17. package/dist/_skills/api-web/SKILL.md +57 -0
  18. package/dist/_skills/api-workflows/SKILL.md +123 -0
  19. package/dist/_skills/beat-sync-editing/SKILL.md +208 -0
  20. package/dist/_skills/caption-titles/SKILL.md +94 -0
  21. package/dist/_skills/color-motion/SKILL.md +185 -0
  22. package/dist/_skills/commercial-beat-sheet/SKILL.md +338 -0
  23. package/dist/_skills/elevenlabs-audio-prompting/SKILL.md +163 -0
  24. package/dist/_skills/explainer-storyboard/SKILL.md +63 -0
  25. package/dist/_skills/flux-2-klein-prompting/SKILL.md +107 -0
  26. package/dist/_skills/frame-composition/SKILL.md +291 -0
  27. package/dist/_skills/game-direction/SKILL.md +99 -0
  28. package/dist/_skills/gpt-image-2-prompting/SKILL.md +161 -0
  29. package/dist/_skills/hailuo-prompting/SKILL.md +107 -0
  30. package/dist/_skills/kling-video-prompting/SKILL.md +188 -0
  31. package/dist/_skills/launch-commercial/SKILL.md +267 -0
  32. package/dist/_skills/launch-kit/SKILL.md +120 -0
  33. package/dist/_skills/logo-reveal/SKILL.md +225 -0
  34. package/dist/_skills/manimgx/SKILL.md +123 -0
  35. package/dist/_skills/minimax-h3-prompting/SKILL.md +134 -0
  36. package/dist/_skills/motion-ad/SKILL.md +281 -0
  37. package/dist/_skills/motion-background/SKILL.md +145 -0
  38. package/dist/_skills/motion-curves/SKILL.md +302 -0
  39. package/dist/_skills/motion-direction/SKILL.md +259 -0
  40. package/dist/_skills/motion-graphics/SKILL.md +217 -0
  41. package/dist/_skills/motion-principles/SKILL.md +255 -0
  42. package/dist/_skills/music-video-treatment/SKILL.md +40 -0
  43. package/dist/_skills/nano-banana-pro-prompting/SKILL.md +133 -0
  44. package/dist/_skills/native-game/SKILL.md +310 -0
  45. package/dist/_skills/native-game-reference/SKILL.md +879 -0
  46. package/dist/_skills/nodetool-3d-scene/SKILL.md +110 -0
  47. package/dist/_skills/nodetool-api-reference/SKILL.md +338 -0
  48. package/dist/_skills/nodetool-app-builder/SKILL.md +291 -0
  49. package/dist/_skills/nodetool-browser-agent/SKILL.md +119 -0
  50. package/dist/_skills/nodetool-chat-cli/SKILL.md +184 -0
  51. package/dist/_skills/nodetool-custom-node-developer/SKILL.md +294 -0
  52. package/dist/_skills/nodetool-deployment/SKILL.md +195 -0
  53. package/dist/_skills/nodetool-js-scripting/SKILL.md +223 -0
  54. package/dist/_skills/nodetool-model-provider-config/SKILL.md +348 -0
  55. package/dist/_skills/nodetool-rag-indexing/SKILL.md +153 -0
  56. package/dist/_skills/nodetool-sketch/SKILL.md +138 -0
  57. package/dist/_skills/nodetool-skill-author/SKILL.md +125 -0
  58. package/dist/_skills/nodetool-troubleshooter/SKILL.md +370 -0
  59. package/dist/_skills/nodetool-video-post/SKILL.md +149 -0
  60. package/dist/_skills/nodetool-workflow-builder/SKILL.md +295 -0
  61. package/dist/_skills/product-commercial/SKILL.md +132 -0
  62. package/dist/_skills/qwen-image-prompting/SKILL.md +122 -0
  63. package/dist/_skills/script-video/SKILL.md +106 -0
  64. package/dist/_skills/seedance-2-prompting/SKILL.md +194 -0
  65. package/dist/_skills/seedream-prompting/SKILL.md +117 -0
  66. package/dist/_skills/short-film/SKILL.md +123 -0
  67. package/dist/_skills/stable-audio-prompting/SKILL.md +130 -0
  68. package/dist/_skills/storyboard-core/SKILL.md +716 -0
  69. package/dist/_skills/timeline-edit-ops/SKILL.md +736 -0
  70. package/dist/_skills/trailer-template/SKILL.md +291 -0
  71. package/dist/_skills/ugc-video/SKILL.md +186 -0
  72. package/dist/_skills/veo-3-prompting/SKILL.md +140 -0
  73. package/dist/_skills/video-audio-continuity/SKILL.md +90 -0
  74. package/dist/_skills/video-clone/SKILL.md +96 -0
  75. package/dist/_skills/video-workflow/SKILL.md +119 -0
  76. package/dist/_skills/wan-2-6-prompting/SKILL.md +107 -0
  77. package/dist/app-build/bridge.d.ts.map +1 -1
  78. package/dist/app-build/bridge.js +44 -17
  79. package/dist/app-build/bridge.js.map +1 -1
  80. package/dist/capabilities/apps.d.ts.map +1 -1
  81. package/dist/capabilities/apps.js +55 -13
  82. package/dist/capabilities/apps.js.map +1 -1
  83. package/dist/capabilities/apps.specs.d.ts.map +1 -1
  84. package/dist/capabilities/apps.specs.js +5 -3
  85. package/dist/capabilities/apps.specs.js.map +1 -1
  86. package/dist/capabilities/game-asset-browser.d.ts.map +1 -1
  87. package/dist/capabilities/game-asset-browser.js +6 -1
  88. package/dist/capabilities/game-asset-browser.js.map +1 -1
  89. package/dist/capabilities/game.d.ts +1 -1
  90. package/dist/capabilities/game.d.ts.map +1 -1
  91. package/dist/capabilities/game.js +71 -56
  92. package/dist/capabilities/game.js.map +1 -1
  93. package/dist/capabilities/game.specs.js +1 -1
  94. package/dist/capabilities/game.specs.js.map +1 -1
  95. package/dist/capabilities/game3d.d.ts.map +1 -1
  96. package/dist/capabilities/game3d.js +14 -19
  97. package/dist/capabilities/game3d.js.map +1 -1
  98. package/dist/capabilities/gameAssertions.d.ts +52 -0
  99. package/dist/capabilities/gameAssertions.d.ts.map +1 -0
  100. package/dist/capabilities/gameAssertions.js +32 -0
  101. package/dist/capabilities/gameAssertions.js.map +1 -0
  102. package/dist/capabilities/models.d.ts.map +1 -1
  103. package/dist/capabilities/models.js +9 -0
  104. package/dist/capabilities/models.js.map +1 -1
  105. package/dist/capabilities/storyboards.d.ts.map +1 -1
  106. package/dist/capabilities/storyboards.js +36 -11
  107. package/dist/capabilities/storyboards.js.map +1 -1
  108. package/dist/capabilities/storyboards.specs.d.ts.map +1 -1
  109. package/dist/capabilities/storyboards.specs.js +11 -1
  110. package/dist/capabilities/storyboards.specs.js.map +1 -1
  111. package/dist/capabilities/workflows.d.ts.map +1 -1
  112. package/dist/capabilities/workflows.js +1 -0
  113. package/dist/capabilities/workflows.js.map +1 -1
  114. package/dist/capabilities/workflows.specs.d.ts.map +1 -1
  115. package/dist/capabilities/workflows.specs.js +4 -3
  116. package/dist/capabilities/workflows.specs.js.map +1 -1
  117. package/dist/evals/surfaces/game.d.ts.map +1 -1
  118. package/dist/evals/surfaces/game.js +29 -1
  119. package/dist/evals/surfaces/game.js.map +1 -1
  120. package/dist/game-authoring.d.ts +120 -0
  121. package/dist/game-authoring.d.ts.map +1 -1
  122. package/dist/game-build-integrity.d.ts +12 -0
  123. package/dist/game-build-integrity.d.ts.map +1 -0
  124. package/dist/game-build-integrity.js +78 -0
  125. package/dist/game-build-integrity.js.map +1 -0
  126. package/dist/game-examples.d.ts +44 -3
  127. package/dist/game-examples.d.ts.map +1 -1
  128. package/dist/game-examples.js +60 -10
  129. package/dist/game-examples.js.map +1 -1
  130. package/dist/system-skills.d.ts +1 -1
  131. package/dist/system-skills.js +1 -1
  132. package/dist/tools/builtin-tools.d.ts.map +1 -1
  133. package/dist/tools/builtin-tools.js +2 -0
  134. package/dist/tools/builtin-tools.js.map +1 -1
  135. package/package.json +19 -19
@@ -0,0 +1,879 @@
1
+ ---
2
+ name: native-game-reference
3
+ description: "Field reference for native game documents: scripts, lifecycle hooks, events, tags and props, 2D visuals, particles, lighting, effects, 3D rendering, physics layers, audio mix, animation graphs, input bindings, HUD widgets, performance and editor panels. Load after native-game when an edit needs one of these fields."
4
+ ---
5
+
6
+ # Native game field reference
7
+
8
+ The fields and runtime rules behind `native-game`. Load `native-game` first for
9
+ the build, playtest and asset loop. The `edit_native_game` op description also
10
+ lists the op shapes for prefabs, the mixer, script params, input bindings, the
11
+ HUD tree, animation graphs, performance, spatial audio and collision layers.
12
+ Sections here say what the fields do and how they interact.
13
+
14
+ ## Schema versions
15
+
16
+ New 2D games start at `schemaVersion: 4` with `engineVersion: "3"`. Older 2D
17
+ documents use schema 1 or 2 with engine `"1"`. Fonts, prepared assets, scene
18
+ music, lighting, backgrounds, gravity, `visualAnimation` and a render effect
19
+ chain need schema 2 or later. Tags, props and script params need schema 4.
20
+ Validation rejects these fields on an earlier schema. 2D `schemaVersion: 5` with
21
+ `engineVersion: "4"` is reserved for Rapier 2D physics, which this runtime does
22
+ not provide: validation and sessions refuse it with `engine_unavailable`. Do not
23
+ author schema 5. A schema must use its own engine version. 3D games use
24
+ `schemaVersion: 3` with `engineVersion: "2"`.
25
+
26
+ ## Entity tags and properties
27
+
28
+ Both dimensions support optional `tags` and `props` on an entity. Use
29
+ `update_entity` with `set: {tags: ["hero"], props: {health: 10}}`. Each field
30
+ replaces its complete value. A top-level `null` removes the stored field.
31
+ Nested `null` is JSON data. Tags are unique, with at most 64 strings of
32
+ 1–128 characters. Properties hold finite JSON, with at most 64 top-level keys,
33
+ 16 levels of nesting and 64 KiB of UTF-8 JSON. Keys contain 1–128 characters.
34
+ `__proto__`, `constructor` and `prototype` are rejected at every nesting level.
35
+
36
+ Scripts read `entity.tags`, `entity.props`, `entity.active` and
37
+ `entity.rotation`, and the same fields on existing `world` entries and
38
+ `world.get(id)` results. `world.query({tag})` returns the ids carrying a tag.
39
+ Rotation is radians in 2D and the committed world quaternion `[x, y, z, w]` in 3D.
40
+ Use `setProp {key, value}` and `removeProp {key}` to change the caller's
41
+ runtime properties. Setting `null` keeps that key. Removal deletes it. The
42
+ runtime checks every property command in a tick before it changes any state,
43
+ and an error names the entity and tick. Each entity's props count once toward
44
+ the 64 KiB script input limit. Runtime properties survive snapshot restore and
45
+ remain independent of the authored document and other prefab instances.
46
+ Mutating an input object does not change runtime state.
47
+
48
+ ## 2D scripts
49
+
50
+ A `script` behavior is a function expression, or an object of lifecycle hooks
51
+ (see Lifecycle hooks and timers). A function receives `{tick, pressed,
52
+ justPressed, events, entity, world, state, random}` and returns `{state,
53
+ commands}`. `events` are the previous tick's events. `world` lists every active
54
+ entity with a collider or camera as `{id, source, x, y}`. `entity` holds the
55
+ caller's `id`, `source`, position, velocity, and `touching {down, up, left,
56
+ right}`, which names the sides of its collider that rest against a solid at the
57
+ start of the tick. A spawned instance has the id `<prefab>#<n>`, its `source`
58
+ is the prefab id, and it runs the prefab's script with its own state.
59
+
60
+ Functions, lifecycle hooks, and timer methods must be synchronous. Async functions
61
+ and generators fail preparation. Returning a promise, thenable, or iterator fails
62
+ the call. Use tick timers for delayed work.
63
+
64
+ Store persistent values in returned `state`. A function that the engine proves
65
+ keeps no hidden state is compiled once and reused. Other sources evaluate in a
66
+ fresh context each call, so closures and globals do not persist between calls.
67
+ Source initialization uses the seeded random generator. `maxTickMs` limits each
68
+ call, subject to the total script budget for the tick. Source and input/output
69
+ limits count UTF-8 bytes.
70
+
71
+ During a function call, global `world.get(id)` returns an entity or `undefined`,
72
+ and `world.query({source?, tag?, near?, radius?, limit?})` returns matching IDs
73
+ in runtime entity order. Both read the same immutable tick-start snapshot,
74
+ before movement behaviors or script commands run. Returned values are private
75
+ copies. Queries cover every active entity. Legacy `input.world` keeps its
76
+ existing shape and population, including only collider or camera entities in 2D.
77
+ The 2D query record is `{id, source, x, y, velocityX, velocityY, grounded}`.
78
+ The 3D record is `{id, source, position, velocity, grounded}`, with XYZ vectors.
79
+ 3D records and schema 4 2D records add `tags`, `props`, `rotation` and `active`.
80
+ A `tag` filter matches only entities that carry the tag, so it matches nothing
81
+ on schema 1 or 2. Pass `near: {x, y, z?}` and `radius` together. The radius is
82
+ nonnegative and includes entities on its boundary, measured between centers,
83
+ with omitted `z` treated as zero. `source` matches the record's original entity
84
+ ID, not a 3D prefab asset ID. `limit` is an integer from 0 to 1024, defaulting
85
+ to 1024. A call allows 64 combined `get`/`query` attempts. Exceeding the count
86
+ fails the call even if guest code catches it. Query arguments allow 4096 JSON
87
+ characters, `get` IDs 1024 characters, and responses 64 KiB. These APIs are
88
+ unavailable during source initialization.
89
+
90
+ The commands are `setVelocity`, `setPosition`, `setVisual {tint?, opacity?,
91
+ rotation?, scaleX?, scaleY?, flipX?}`, `playAnimation {clip}`, `spawn {prefabId,
92
+ x?, y?, velocityX?, velocityY?}`, `despawn`, `emit`, `sceneTransition`, and
93
+ `hud {id, text, x, y, size?, color?, align?}`. HUD coordinates are canvas
94
+ pixels. Empty text removes a label. A label with the id `score` or `win`
95
+ replaces the built-in label. Set `fontId` to a logical font binding for a HUD
96
+ label. Font assets use `mediaKind: "font"`, `fontFormat: "ttf" | "otf"`, and
97
+ `required: true` by default. Required fonts load before the first frame or fail
98
+ play and capture. An optional font (`required: false`) reports a fallback before
99
+ play. Export embeds the font bytes for offline use.
100
+
101
+ `log {message, level?}` writes a line to the editor console in 2D and 3D.
102
+ `level` is `"log"` (the default), `"warning"` or `"error"`. A log line never
103
+ changes the simulation, snapshots or replay, and it does not count toward
104
+ `maxCommands`. A tick keeps 32 lines and reports the rest as one dropped-lines
105
+ warning. Messages longer than 1000 characters are truncated. Log text still
106
+ counts toward the 64 KiB output limit of a script call. Lines logged before a
107
+ script failure appear in the console ahead of the error.
108
+
109
+ ### Script time limits
110
+
111
+ `maxTickMs` accepts 1 through 20 ms, defaults to 8 ms, and cannot raise the
112
+ host's 20 ms ceiling. It counts the CPU time of the thread running the script, from just
113
+ before the call (or before compiling its source) until its output is checked.
114
+ Waiting for a processor does not count. A call ends only when both its CPU time
115
+ and its wall time reach the limit. Browsers have no thread CPU clock, so there
116
+ the limit counts wall time. The 50 ms script budget for a whole tick and the
117
+ 100 ms limit for evaluating a source during preparation count wall time on
118
+ every host. A session's first scripted tick has 1,000 ms instead of 50 ms,
119
+ because it runs each script for the first time. A script that loops forever still ends at its limit.
120
+
121
+ ### Script parameters
122
+
123
+ Declare a script's tunables as `params` on its script behavior instead of
124
+ constants in the source. The inspector edits them and scripts read the values
125
+ on `input.params`. Set them with `set_script_params {entity_id, scene_id?,
126
+ index, params?, values?}`. `params` replaces the declarations and `null`
127
+ removes them with their values. `values` merges stored values, and a `null`
128
+ value returns one to its default. 2D games need schema 4. 3D games accept
129
+ params on schema 3.
130
+
131
+ ```json
132
+ {"op": "set_script_params", "entity_id": "player", "index": 0,
133
+ "params": {"speed": {"type": "number", "default": 3, "minimum": 0, "maximum": 10},
134
+ "target": {"type": "entity"}, "hit": {"type": "asset", "kind": "audio"}},
135
+ "values": {"speed": 5, "target": "goal"}}
136
+ ```
137
+
138
+ Types are `number` (`minimum`, `maximum`, `integer`), `boolean`, `color`
139
+ (`#rrggbb`), `enum` (`options`, default the first option), `entity`, `asset`
140
+ (optional `kind`) and `vector` (`dimensions` 2 or 3). A behavior declares at
141
+ most 32 params with identifier names. `input.params` holds the stored value,
142
+ else the default. An `entity` or `asset` param without either is `null`.
143
+ Validation rejects an entity reference outside the behavior's scene, a missing
144
+ asset slot, an asset of the wrong `kind`, and in 3D an entity reference inside
145
+ a prefab or an undeclared prefab asset. A behavior without `params` has no
146
+ `input.params` key. Params count once per behavior definition toward the
147
+ 64 KiB script input limit.
148
+
149
+ ### Lifecycle hooks and timers
150
+
151
+ A script source may be an object of hooks instead of a function. It works in
152
+ 2D and 3D with no schema change.
153
+
154
+ ```js
155
+ ({
156
+ onStart(input) { every(30, "fire"); return { state: { shots: 0 } }; },
157
+ fire(input) { return { state: { shots: input.state.shots + 1 },
158
+ commands: [{ kind: "spawn", prefabId: "bolt" }] }; },
159
+ onTriggerEnter(input, contact) { return { commands: [{ kind: "emit", event: "hit:" + contact.otherId }] }; },
160
+ onDestroy(input) { return { commands: [{ kind: "emit", event: "gone" }] }; }
161
+ })
162
+ ```
163
+
164
+ Hooks receive the same `input` as a function script and return
165
+ `{state?, commands?}` or nothing. Each hook sees the state returned by the
166
+ one before it. Within a tick they run in this order:
167
+
168
+ 1. `onStart`, on the behavior's first call.
169
+ 2. `onSceneEnter`, on the first tick of the scene.
170
+ 3. `onTriggerEnter`, `onTriggerExit` and `onContact`, once per contact of
171
+ the entity in the previous tick's events, in event order. A contact where
172
+ either collider is a sensor is a trigger. Triggers fire on `enter` and
173
+ `exit`. `onContact` receives every phase of a solid contact. The second
174
+ argument is `{otherId, phase, sensor}`.
175
+ 4. `onEvent`, once per trigger event of the previous tick that reaches the
176
+ entity, in event order. See Typed events.
177
+ 5. Due timers, in the order they were scheduled.
178
+ 6. `onFixedUpdate` or `onUpdate`. They are aliases. Define one of them.
179
+
180
+ `onDestroy` runs alone in the tick after the entity despawns. It may return
181
+ only `hud`, `emit`, `spawn`, `despawn`, `sceneTransition`, `log`, `save` and `load` commands. A scene
182
+ transition discards behavior state without calling `onDestroy`.
183
+
184
+ `after(ticks, name)` calls the object's method `name` once, `ticks` ticks
185
+ later. `every(ticks, name)` calls it every `ticks` ticks. `ticks` is an
186
+ integer from 1 to 1000000, `name` must be a method that is not a hook, and a
187
+ behavior holds at most 32 timers. Scheduling an existing name replaces that
188
+ timer. Timers live in the behavior's snapshot state, so they replay from any
189
+ snapshot. Session preparation rejects an unknown `on*` key, a hook that is not
190
+ a function, and an object that defines both `onUpdate` and `onFixedUpdate`.
191
+ Lifecycle objects run in a fresh context each call.
192
+
193
+ ### Typed events
194
+
195
+ `emit` takes an optional `payload` and `target` in 2D and 3D, with no schema
196
+ change. The payload is any JSON value up to 1 KiB serialized and 16 levels
197
+ deep. The target is `{entityId}` for one entity or `{tag}` for every entity
198
+ with that tag. Omit `target` to broadcast.
199
+
200
+ ```js
201
+ // Sender
202
+ ({ onTriggerEnter(input, contact) { return { commands: [{ kind: "emit", event: "alarm",
203
+ payload: { by: contact.otherId, level: 2 }, target: { tag: "guard" } }] }; } })
204
+ // Receiver, on an entity tagged "guard"
205
+ ({ onEvent(input, event) { if (event.event === "alarm") { return { state: { level: event.payload.level } }; } } })
206
+ ```
207
+
208
+ The event lands in the stream as `{kind: "trigger", event, entityId,
209
+ payload?, target?}`, where `entityId` is the sender, and reaches scripts on
210
+ the next tick. `onEvent(input, event)` receives that object for each trigger
211
+ event that reaches its entity, broadcasts included, and the sender receives
212
+ its own broadcasts. Built-in `trigger` behaviors emit untargeted events, so
213
+ `onEvent` sees them too. The target also limits built-in listeners: a `spawn`
214
+ or `sceneTransition` behavior with `onEvent` and an `audioSource` with
215
+ `onEvent` react only to events that reach their entity. `input.events` still
216
+ holds every event of the previous tick, targeted or not. A tag target reaches
217
+ nothing on 2D schemas before 4. A plain `{kind: "emit", event}` produces the
218
+ same event as before. Payloads count toward the 64 KiB script input limit once
219
+ per tick.
220
+
221
+ ### Player saves
222
+
223
+ Scripts save and load in 2D and 3D with two commands:
224
+
225
+ - `save {slot, data, scope?, snapshot?}` writes `data` to a slot after the tick.
226
+ - `load {slot, scope?}` reads a slot.
227
+
228
+ `scope` is `"progress"` (the default) or `"settings"`. The two scopes are
229
+ separate stores, so a progress slot and a settings slot can share a name and
230
+ removing one never touches the other. `snapshot: true` also stores the
231
+ end-of-tick session snapshot with a progress save. A settings save with a
232
+ snapshot fails the tick. The player keeps one snapshot save pending at a time,
233
+ and the editor stores the data without the snapshot.
234
+
235
+ The player runs saves and loads in order, with at most 8 waiting. A request
236
+ past that limit fails without stopping the game, and a failed load answers
237
+ with null data.
238
+
239
+ A slot name is 1 to 32 letters, digits, `_` or `-`. `data` must be a JSON
240
+ object of at most 16 KiB serialized, 256 keys counted at every depth and 16
241
+ levels deep. Each scope holds at most 16 slots per game. A tick accepts at most
242
+ 4 save and load commands across all scripts, and more fail the tick. These
243
+ commands do not count toward `maxCommands` and never enter snapshots.
244
+
245
+ A load answers on a later tick through `input.loaded`, a list of `{scope, slot,
246
+ data}` where `data` is null for an empty slot. A frame carries at most 2
247
+ results. Keep what the game needs in script state:
248
+
249
+ ```js
250
+ ({ onStart() { return { commands: [{ kind: "load", slot: "main" }] }; },
251
+ onUpdate(input) {
252
+ const result = input.loaded?.find((entry) => entry.slot === "main");
253
+ return { state: result ? { best: result.data?.best ?? 0 } : input.state };
254
+ } })
255
+ ```
256
+
257
+ The loaded data is part of the input frame, so a replay of recorded frames
258
+ reproduces it without storage. The standalone player stores saves in IndexedDB.
259
+ The editor stores them in localStorage under the game id. `nodetool game
260
+ simulate` and agent playtests keep them in memory. `simulate --saves <file>`
261
+ seeds slots from `{progress?, settings?}`, and its JSON report lists the slots
262
+ after the run.
263
+
264
+ ## 2D visuals
265
+
266
+ For generated art in a workflow, pass one style entity with a reference image.
267
+ The guided graph sends that image through an image-to-image generation input
268
+ for every visual slot. `StageGameAssets.preparation` accepts per-slot alpha
269
+ trimming, target size with a crop policy, pivot, nearest or linear sampling,
270
+ and mirror tiling for single images. Trimming records crop offsets so placement
271
+ keeps its anchor. `StageGameAssets.fonts` stages TrueType or OpenType assets by
272
+ logical font ID.
273
+
274
+ 1. Use `sprite.blend: "additive"` for light drawn on black, such as glows and
275
+ sparks. Set `sampling: "linear"` on painted or soft assets.
276
+ 2. Use `lifetime {ticks, fade, endScale}` for short-lived entities. A kinematic
277
+ body without a collider moves but never collides, which keeps them cheap.
278
+ Prefer the `particles` component for sparks, smoke and dust.
279
+ 3. Use `animator` frames on a sprite sheet. Playback starts when the entity
280
+ spawns. Named `animator.clips` hold other frame sets. `playAnimation` starts
281
+ a clip from its first frame, and a repeated request for the current clip
282
+ continues it. Set `sprite.flipX` or the `setVisual` flag to mirror art, or
283
+ `sprite.faceMotion: "left" | "right"` to turn art that faces that way toward
284
+ the body's horizontal motion.
285
+ 4. Give `audioSource.onEvent` an event kind or the name of an emitted trigger.
286
+ Set `audioSource.volume` from `0` to `1` for effect loudness. Effects receive
287
+ distinct voice ids, so repeated triggers can overlap within the 32-voice cap.
288
+ 5. Make hazards sensors. Only a non-sensor body collects a `collectible`.
289
+
290
+ Set `scene.music` to an installed audio `assetId` for looping music. `volume`
291
+ ranges from `0` to `1`, and `fadeInTicks` and `fadeOutTicks` range from `0` to
292
+ `600`. Music starts at the scene's entry tick. Pause suspends audio, reset
293
+ restarts the entry scene once, and loading resumes the loop at the saved
294
+ logical tick. Set `music.streaming: {loopStartSeconds, loopEndSeconds?}` for
295
+ long tracks in 2D or 3D. This streams through the music bus without decoding
296
+ the whole asset. Start defaults to 0 seconds, end defaults to the track's end,
297
+ and an explicit end must exceed start. Both are bounded by 86400 seconds.
298
+ The intro plays once. Custom loop boundaries follow browser media events.
299
+ Existing fades crossfade outgoing and incoming scene tracks. Use `update_scene`
300
+ to replace the whole `music` object. Omit `streaming` to retain decoded
301
+ playback. Browser playback starts after a user gesture. A missing or
302
+ unplayable audio asset is reported in the player.
303
+
304
+ `visualAnimation` on a sprite animates `rotation`, `scaleX`, `scaleY`,
305
+ `opacity`, or `tint` from `from` to `to` over `durationTicks` in its `tracks`.
306
+ `delayTicks`, `repeat`, `pingPong`, and `easing` control timing. `rotationRate`
307
+ adds radians per tick for continuous spins and cannot share a rotation track.
308
+ Each property has at most one track. Tracks start at scene entry or spawn time.
309
+ The visual order is authored transform and sprite values, tracks, script
310
+ `setVisual`, then lifetime fade and scale. Tracks do not change collision.
311
+
312
+ Scene `backgrounds` define image layers independent of entities. Each layer
313
+ sets `assetId`, world-unit `width` and `height`, `origin` at the tile center,
314
+ `layer`, per-axis `parallax`, `scrollRate` in world units per second, and `mode`
315
+ (`none`, `repeat`, `repeatX`, or `mirror`). `repeatX` tiles only sideways, which
316
+ suits a strip of scenery. Parallax `0` stays fixed to the screen and `1`
317
+ stays fixed in the world. Optional `frame` selects an atlas rectangle.
318
+
319
+ Scene `lighting` holds an `ambient` color and intensity plus up to 32 `points`
320
+ with world position, color, intensity, radius, and falloff. Lighting multiplies
321
+ world color before effects. HUD stays unlit. Set `sprite.unlit: true` for
322
+ emissive art such as glows. Canvas2D capture paints these sprites above the lit
323
+ world. An entity `light2d {color, intensity, radius, falloff, offset?}` moves
324
+ with its entity. Each frame adds the entity lights nearest the camera to the
325
+ scene's fixed points, up to 32 lights in total.
326
+
327
+ `renderEffects` is an ordered chain of at most eight effects.
328
+ `brightnessContrast` uses `brightness` from `-1` to `1` and `contrast` from `0`
329
+ to `4`. `bloom` uses `threshold` from `0` to `1`, `softness` from `0` to `0.5`,
330
+ `radius` from `0` to `64` pixels, and `intensity` from `0` to `4`. `lut` refers
331
+ to an opaque packed 2D cube image with dimensions `size² × size` and an
332
+ optional intensity and color domain. Effects run in listed order. Set
333
+ `hudEffectOrder` to `afterEffects` to keep text crisp, or `beforeEffects` to
334
+ process HUD with the world. A bloom chain defaults to `afterEffects`.
335
+ `required: true` makes unsupported GPU effects fail before play or capture.
336
+ Optional effects are omitted with a diagnostic on Canvas 2D.
337
+
338
+ Collider `category` and `mask` are unsigned 32-bit bitsets in 2D. They default
339
+ to `1` and `4294967295`. A pair interacts only when each mask includes a
340
+ category bit from the other collider. Give bullets masks that exclude other
341
+ bullets when bullet-to-bullet interaction is unnecessary.
342
+
343
+ Contacts report `phase: "enter" | "stay" | "exit"` and `normalX`/`normalY`.
344
+ Check both `entityId` and `otherId`, since each pair emits one contact per tick.
345
+ Built-in triggers fire on entry. Use `stay` contacts for repeated overlap logic.
346
+ Swept collisions detect fast movement through thin static walls and sensors.
347
+ Scene entry starts authored entities' animation and lifetime clocks, and saves
348
+ preserve those clocks and active contact pairs.
349
+
350
+ ## Particles
351
+
352
+ The particle inspector offers Fire, Smoke, Sparks, Dust, Magic, Rain, Snow and
353
+ Explosion presets in both dimensions. Applying one replaces that entity's
354
+ `particles` component and preserves its other components. Select an emitter
355
+ to edit its fields, lifetime curves and color gradient. Renaming an emitter
356
+ repairs its death-emitter references. Removing one removes references to it.
357
+ The isolated preview uses a fixed seed and restarts when edited. Pause and
358
+ Restart affect the preview only. The 2D preview uses Canvas2D, including sprite
359
+ sheets and blending. The 3D preview uses WebGL2, including stretched and mesh
360
+ particles and depth softness. It does not run gameplay scripts or change the
361
+ play session.
362
+
363
+ Agents and CLI document edits use the same `update_entity` operation as the
364
+ inspector. Set the complete component with
365
+ `{op: "update_entity", entity_id: "effect", set: {particles: {emitters: [...]}}}`.
366
+ There is no preset-name operation. Node.js authoring can also import
367
+ `getGameParticlePreset(name, dimension)` from `@nodetool-ai/protocol` to get
368
+ fresh canonical data. In a code action, use the JSON recipes below with the
369
+ existing game edit API instead of importing that host package.
370
+
371
+ ### Particle preset recipes
372
+
373
+ Each payload is a complete `particles` component. Omitted fields take schema
374
+ defaults. These canonical recipes use 3D draw settings. For a 2D entity, remove
375
+ `render` from each emitter and set `shape.size.z` to `0` for box shapes.
376
+ Keep other values unchanged. Both dimensions use world units and +Y upward.
377
+ Place rain and snow emitters above the area they cover. Explosion is a finite
378
+ burst, which can be restarted with `emitParticles` without a count.
379
+
380
+ #### fire
381
+
382
+ ```json
383
+ {"emitters":[{"id":"flame","rate":48,"shape":{"kind":"cone","angle":15,"radius":0.15},"lifetime":{"min":0.6,"max":1.2},"speed":{"min":1.2,"max":2.2},"size":{"min":0.15,"max":0.4},"color":"#ff932d","sizeOverLifetime":[{"t":0,"value":0.5},{"t":0.3,"value":1},{"t":1,"value":0.1}],"opacityOverLifetime":[{"t":0,"value":0},{"t":0.12,"value":1},{"t":1,"value":0}],"colorOverLifetime":[{"t":0,"color":"#fff4a0"},{"t":0.5,"color":"#ff8326"},{"t":1,"color":"#a91c14"}],"gravity":{"x":0,"y":1.4,"z":0},"blend":"additive","unlit":true}]}
384
+ ```
385
+
386
+ #### smoke
387
+
388
+ ```json
389
+ {"emitters":[{"id":"smoke","rate":14,"shape":{"kind":"cone","angle":20,"radius":0.2},"lifetime":{"min":2,"max":3.5},"speed":{"min":0.3,"max":0.7},"size":{"min":0.3,"max":0.6},"color":"#9d9fa8","opacity":0.45,"sizeOverLifetime":[{"t":0,"value":0.4},{"t":1,"value":2.5}],"opacityOverLifetime":[{"t":0,"value":0},{"t":0.12,"value":1},{"t":1,"value":0}],"colorOverLifetime":[{"t":0,"color":"#52545e"},{"t":1,"color":"#b9bac2"}],"gravity":{"x":0.08,"y":0.35,"z":0},"drag":0.3,"unlit":true}]}
390
+ ```
391
+
392
+ #### sparks
393
+
394
+ ```json
395
+ {"emitters":[{"id":"sparks","rate":24,"shape":{"kind":"sphere","radius":0.05,"fromSurface":false},"lifetime":{"min":0.3,"max":0.9},"speed":{"min":2,"max":5},"size":{"min":0.025,"max":0.06},"color":"#ffd37c","opacityOverLifetime":[{"t":0,"value":0},{"t":0.12,"value":1},{"t":1,"value":0}],"colorOverLifetime":[{"t":0,"color":"#fff4c1"},{"t":1,"color":"#e7551d"}],"gravity":{"x":0,"y":-4,"z":0},"blend":"additive","unlit":true,"render":{"kind":"stretched","length":2,"speedScale":0.04}}]}
396
+ ```
397
+
398
+ #### dust
399
+
400
+ ```json
401
+ {"emitters":[{"id":"dust","rate":8,"shape":{"kind":"box","size":{"x":4,"y":2,"z":2}},"lifetime":{"min":3,"max":6},"speed":{"min":0.02,"max":0.08},"size":{"min":0.02,"max":0.05},"color":"#d4c4a0","opacity":0.45,"opacityOverLifetime":[{"t":0,"value":0},{"t":0.12,"value":1},{"t":1,"value":0}],"gravity":{"x":0.01,"y":0.01,"z":0},"unlit":true}]}
402
+ ```
403
+
404
+ #### magic
405
+
406
+ ```json
407
+ {"emitters":[{"id":"magic","rate":30,"shape":{"kind":"sphere","radius":0.7,"fromSurface":true},"lifetime":{"min":0.8,"max":1.8},"speed":{"min":0.1,"max":0.5},"size":{"min":0.04,"max":0.12},"angularVelocity":{"min":-3.141592653589793,"max":3.141592653589793},"color":"#9d79ff","sizeOverLifetime":[{"t":0,"value":0},{"t":0.3,"value":1.5},{"t":1,"value":0}],"opacityOverLifetime":[{"t":0,"value":0},{"t":0.12,"value":1},{"t":1,"value":0}],"colorOverLifetime":[{"t":0,"color":"#63f4ff"},{"t":0.5,"color":"#9970ff"},{"t":1,"color":"#fa91e6"}],"blend":"additive","unlit":true}]}
408
+ ```
409
+
410
+ #### rain
411
+
412
+ ```json
413
+ {"emitters":[{"id":"rain","rate":120,"maxParticles":512,"shape":{"kind":"box","size":{"x":6,"y":0.2,"z":3}},"lifetime":{"min":0.7,"max":1.1},"speed":0,"size":0.025,"color":"#a2c9e8","opacity":0.65,"opacityOverLifetime":[{"t":0,"value":0},{"t":0.12,"value":1},{"t":1,"value":0}],"gravity":{"x":0.4,"y":-18,"z":0},"unlit":true,"render":{"kind":"stretched","length":2,"speedScale":0.06}}]}
414
+ ```
415
+
416
+ #### snow
417
+
418
+ ```json
419
+ {"emitters":[{"id":"snow","rate":28,"shape":{"kind":"box","size":{"x":6,"y":0.2,"z":3}},"lifetime":{"min":4,"max":6},"speed":0,"size":{"min":0.04,"max":0.1},"angularVelocity":{"min":-0.7853981633974483,"max":0.7853981633974483},"color":"#e8f5ff","opacity":0.9,"opacityOverLifetime":[{"t":0,"value":0},{"t":0.12,"value":1},{"t":1,"value":0}],"gravity":{"x":0.025,"y":-0.25,"z":0},"unlit":true}]}
420
+ ```
421
+
422
+ #### explosion
423
+
424
+ ```json
425
+ {"emitters":[{"id":"explosion","duration":0.1,"loop":false,"rate":0,"bursts":[{"time":0,"count":96,"cycles":1,"interval":0.1}],"shape":{"kind":"sphere","radius":0.1,"fromSurface":false},"lifetime":{"min":0.4,"max":1},"speed":{"min":2,"max":6},"size":{"min":0.1,"max":0.3},"color":"#ff9b30","sizeOverLifetime":[{"t":0,"value":1.5},{"t":1,"value":0}],"opacityOverLifetime":[{"t":0,"value":0},{"t":0.12,"value":1},{"t":1,"value":0}],"colorOverLifetime":[{"t":0,"color":"#fff6c5"},{"t":0.4,"color":"#ff992c"},{"t":1,"color":"#b62c19"}],"drag":2,"blend":"additive","unlit":true}]}
426
+ ```
427
+
428
+ ### Particle component fields
429
+
430
+ Add particle effects with the `particles` component on any 2D (schema 2 or
431
+ later) or 3D entity. It holds up to 8 `emitters`, each with a unique `id`. An
432
+ emitter sets `rate` (particles per second), `bursts` (`time`, `count`,
433
+ `cycles`, `interval`), `duration`, `loop`, `playOnStart`, `maxParticles` and a
434
+ `shape`: `point`, `circle`, `sphere`, `cone`, `box` or `edge`. Cone, box and
435
+ edge emit along local +Y. Point, circle and sphere emit outward. Per-particle
436
+ values `lifetime`, `speed`, `size`, `rotation` and `angularVelocity` take a
437
+ number or `{ min, max }`. `color` takes `#rrggbb` or `{ min, max }`. The
438
+ `sizeOverLifetime`, `speedOverLifetime` and `opacityOverLifetime` curves are
439
+ lists of `{ t, value }` keys sorted by `t` from 0 to 1. `colorOverLifetime` uses
440
+ `{ t, color }` keys. Add `gravity` (`{ x, y, z }` in units per second squared)
441
+ and `drag`. `space: "local"` makes particles follow the entity.
442
+ `onDeath: [{ emitter, count }]` fires another emitter of the same component
443
+ where each particle dies. Give that emitter `playOnStart: false` and `rate: 0`.
444
+
445
+ Scripts trigger emitters with `{ kind: "emitParticles", emitter?, count? }` on
446
+ their own entity. Without `count`, the emitter restarts its cycle, which suits
447
+ a one-shot explosion with `loop: false`. With `count`, that many particles
448
+ spawn at once. Omitting `emitter` selects the first emitter in the list. The
449
+ command is presentation only and never appears in gameplay events or
450
+ snapshots. Particles cannot affect scores, physics or scripts. Prefer them over
451
+ spawned prefabs for sparks, smoke and dust.
452
+
453
+ Each emitter also sets how its particles draw in 2D. Without `sprite`, a
454
+ particle is a soft round dot tinted by its colour. `sprite: { assetId, columns,
455
+ rows, frameCount?, cycles?, sampling? }` uses an image asset instead. A sheet is
456
+ cut into `columns` by `rows` cells read left to right and top to bottom, and the
457
+ first `frameCount` cells play `cycles` times over each particle's life.
458
+ `sampling` defaults to `nearest`. `blend: "additive"` adds light, which suits
459
+ fire, sparks and magic. The default `normal` alpha-blends, which suits smoke and
460
+ dust. Particles take scene lighting like sprites. Set `unlit: true` so glowing
461
+ particles keep their colour in the dark. `layer` sets the draw layer. It
462
+ defaults to the entity's sprite layer, or 0 without a sprite, and particles
463
+ draw above sprites of the same layer. `size` is the particle's width in world
464
+ units. `rotation` and `angularVelocity` are radians and radians per second.
465
+
466
+ The standalone player, the editor's play view and `capture_native_game_frame`
467
+ draw 2D particles. A capture simulates particles for every tick up to the
468
+ captured one, so a burst at tick 0 is still visible a few ticks later. WebGPU
469
+ draws every live particle. The Canvas2D fallback draws at most 1024 visible
470
+ particles. Each emitter's random stream is seeded from its entity and emitter
471
+ ids, so captures repeat.
472
+
473
+ In 3D, `render` sets how each particle draws. The default `{ kind:
474
+ "billboard" }` is a soft round dot facing the camera, `size` units across and
475
+ turned by `rotation`. `{ kind: "stretched", length?, speedScale? }` lies along
476
+ the particle's direction of travel on screen. It is `size` wide and `size ×
477
+ length + speed × speedScale` long, which suits sparks, rain and streaks.
478
+ `length` defaults to 1 and `speedScale` to 0.1 seconds. `{ kind: "mesh",
479
+ shape, align? }` draws a small solid `box`, `sphere`, `tetrahedron` or
480
+ `octahedron`, `size` units across, for debris and confetti. With `align:
481
+ "world"`, the default, it turns by `rotation` about world +Y. With `align:
482
+ "velocity"` its +Y points along its velocity. Mesh particles take scene
483
+ lighting unless `unlit: true`. Billboards always keep their own colour. Both
484
+ take scene fog and post-processing such as bloom, so additive particles glow.
485
+ `softness` (world units) fades a billboard out where it nears the geometry
486
+ behind it, so smoke and glows do not cut a hard line through the floor. The
487
+ renderer then draws the scene's depth once more per frame, so set it only on
488
+ emitters that touch geometry. That depth pass skips transparent materials,
489
+ keeps alpha-tested cut-outs, and counts toward the draw call and
490
+ triangle budgets. 3D particles ignore `sprite` and `layer`, and
491
+ 2D particles ignore `render` and `softness`. Validation warns about `render` or
492
+ `softness` on a 2D emitter, `softness` on a mesh emitter, and `sprite` on a
493
+ 3D emitter, without failing. Editor Play, the 3D standalone player,
494
+ `nodetool game capture` and `capture_native_game_frame` draw 3D particles.
495
+ Translucent particles sort across render types without writing depth. Interleaved
496
+ types can increase draw calls. A scene transition clears the particle field.
497
+ Within a scene, detached world particles retain world-space death chains until
498
+ they finish. Storage grows lazily within the scene particle limit.
499
+
500
+ ## 3D collision layers
501
+
502
+ `collisionLayers` names up to 16 layers, and the layer at index `i` owns bit
503
+ `i`. `collisionMatrix` lists the layer pairs that do not collide, in either
504
+ order. A layer paired with itself stops its colliders from touching each other.
505
+ Every unlisted pair collides. Set both with `set_game {collision_layers,
506
+ collision_matrix}`, and put a collider on a layer with `update_entity set
507
+ collider3d {layer}`. A layered collider derives `category` from its bit and
508
+ `mask` from the matrix, so leave its raw bits at `1` and `65535`. Validation
509
+ rejects an unknown layer name, a pair listed twice in the matrix and
510
+ conflicting layer bits. Raw `category` and `mask` remain an override for
511
+ colliders without a layer. A collider without a layer keeps the default
512
+ category `1`, which is the bit of `collisionLayers[0]`. It therefore shares the
513
+ first layer's pairs: a layer paired with `collisionLayers[0]` does not touch
514
+ unlayered colliders either. Name the first layer for the world, or give such
515
+ colliders a layer. 2D colliders keep raw bits only.
516
+
517
+ ## 3D physics materials
518
+
519
+ `physicsMaterials` names up to 64 surfaces that colliders share. Each material
520
+ has `friction` (0 to 4, default 0.5), `restitution` (0 to 1, default 0),
521
+ `frictionCombine` and `restitutionCombine`. A combine rule is `average`
522
+ (default), `min`, `multiply` or `max`. When two touching colliders use
523
+ different rules, the later rule in that list wins. Use `max` restitution for a
524
+ bouncy ball and `min` friction for ice. Declare materials with `set_game
525
+ {physics_materials: {name: material}}`, and remove them all with
526
+ `physics_materials: null`. Point a collider at one with `update_entity set
527
+ collider3d {physicsMaterial: name}` and clear it with `physicsMaterial: null`.
528
+ A collider with a material takes all four values from it, so leave its inline
529
+ `friction` at 0.5 and `restitution` at 0 and set no inline combine rule.
530
+ Validation rejects an unknown material name and conflicting inline values. A
531
+ collider without a material keeps its inline values and may set
532
+ `frictionCombine` and `restitutionCombine` itself. Existing documents name no
533
+ material and simulate as before. 2D colliders have no friction or
534
+ restitution.
535
+
536
+ ## Audio mix and spatial audio
537
+
538
+ Set the document mix with `set_audio {mixer}` in 2D and 3D. `set_audio
539
+ {mixer: null}` removes it. Every mixer has the buses `master`, `music`, `sfx`,
540
+ `voice` and `ui`. Add buses under `mixer.buses` with `parent` (default
541
+ `master`), `volume` (linear, 1 is unity), `muted`, `lowpassHz` and
542
+ `reverbSend`. A bus multiplies its parent's gain, so muting `sfx` silences its
543
+ children. Scene music plays on `music` and other audio on `sfx` unless
544
+ `mixer.assetBuses` maps the asset slot to another bus.
545
+
546
+ The master bus ends in a limiter at `limiter.thresholdDb` (default -1 dB) that
547
+ does not change levels below the threshold. The default ducking rule lowers
548
+ `music` to 0.35 while a voice plays on `voice` or a child of it. Replace
549
+ `ducking` to change or remove that rule.
550
+
551
+ A mixer snapshot under `mixer.snapshots` overrides named fields of named buses.
552
+ `transitions` move to a snapshot over `fadeTicks` when the simulation emits a
553
+ `trigger` event, a `win` event, or enters a scene. The snapshot `base` returns
554
+ to the base mix. Restarting or restoring a session returns to the base mix or
555
+ the scene's snapshot without a fade. The mix is presentation state. It is not
556
+ saved in game snapshots and never changes playtest results or replay.
557
+
558
+ Set `audioSource.spatial: true` to play an entity's effect at its position in
559
+ 2D and 3D. The listener is the active camera, so an emitter right of the camera
560
+ plays in the right ear. Effects play at full volume within `minDistance`
561
+ (default 1 world unit) and fall off by `distanceModel` (`linear`, `inverse`
562
+ by default, or `exponential`) and `rolloff` (default 1) out to `maxDistance`
563
+ (default 50), which must exceed `minDistance`. The `linear` model clamps
564
+ `rolloff` to 1 and is silent at `maxDistance`. A 3D `cone {innerAngle,
565
+ outerAngle, outerGain}` points along the entity's -Z axis. 2D emitters ignore
566
+ the cone. `doppler` (default 0, 1 is physical) shifts pitch with emitter and
567
+ camera motion. Set a field to `null` in `update_entity` to return it to its
568
+ default. The event that starts the effect carries the emitter position, so a
569
+ collectible that despawns as it plays still sounds where it was. Spatial
570
+ settings never change simulation.
571
+
572
+ ## 3D animation graphs
573
+
574
+ A 3D animation graph lives in the document's `animationGraphs` map. An
575
+ entity's `animator3d.graph` names one, and its `animator3d.clips` maps the
576
+ graph's clip aliases to prepared clip IDs, so one graph can drive several rigs.
577
+ Store a graph with `set_animation_graph {graph_id, graph}` and delete it with
578
+ `remove_animation_graph {graph_id}`. An animator with a graph cannot also set
579
+ `initialClip`.
580
+
581
+ A graph declares `parameters` (`float` with a default, `bool` with a default,
582
+ or `trigger`) and one to eight `layers`. Each layer has an `initialState`,
583
+ `states` and `transitions`. A state plays a `motion`: one `clip`, a `blend1d`
584
+ over a float parameter with points in increasing `value` order, or a `blend2d`
585
+ over two float parameters. `speed` scales the animator's `playbackRate`, and
586
+ `loop` defaults to true. The first layer is the unmasked override base. Later
587
+ layers can be `additive` and can set `weight` and a `mask` of prepared model
588
+ node IDs. A mask covers each listed node and its descendants.
589
+
590
+ A transition names `from` (a state ID, or `*` for any state), `to`,
591
+ `conditions`, optional `exitTicks` and `durationTicks` for the crossfade. Every
592
+ condition must hold. Float conditions use `gt`, `gte`, `lt`, `lte`, `eq` or
593
+ `neq` with a `value`. Bool conditions use `true` or `false`. A trigger uses
594
+ `set` and resets once a transition consumes it. `exitTicks` is how long the
595
+ source state must run before the transition may fire. Each transition needs a
596
+ condition or `exitTicks`. A crossfading layer does not start another
597
+ transition. Transitions are checked in document order.
598
+
599
+ Scripts set parameters with `setAnimParam {name, value}`. Pass `true` to fire
600
+ a trigger. `playAnimation {clip}` on a graph entity switches the base layer
601
+ directly to the state with that ID, or else to the first state that plays that
602
+ clip alias, using `animator3d.transitionTicks`. Example locomotion: a `speed`
603
+ float, a `move` state with `blend1d` points idle at 0, walk at 2 and run at 6,
604
+ and a script that sends `setAnimParam` with the character's planar speed.
605
+
606
+ Graph state (current state, entry tick, crossfade source and parameter values)
607
+ is simulation state, saved in snapshots. Check it with a playtest that sets
608
+ `restore_at_tick` and asserts `replay_matches`. The renderer samples the
609
+ resolved pose at the interpolated tick. Blended clips share one normalized
610
+ phase. The pose sampler blends node translation, rotation and scale. It does
611
+ not blend morph targets.
612
+
613
+ Model preparation derives `clipDurations` from verified GLB time accessors.
614
+ Bindings with this metadata retain a normalized simulation phase when blend
615
+ parameters change, including across save and restore. Do not invent or edit
616
+ durations. For a legacy binding without them, regenerate a model candidate from
617
+ its existing source with `generate_game_asset`, then explicitly install it with
618
+ `install_native_game_asset`. Installation rederives the durations from its bytes.
619
+ Until then, blends keep legacy elapsed-time sampling and validation reports
620
+ `legacy_animation_phase`.
621
+
622
+ ## 3D rendering
623
+
624
+ Set every `environment` field with `update_scene`. It replaces the whole
625
+ `environment`, so send the existing `background`, `ambient`, `fog`, `shadows`,
626
+ `sky` and `postProcessing` with the changed field. Capture the scene and view
627
+ the frames to review a change.
628
+
629
+ `environment.fog` accepts either linear `{ color, near, far }` or height
630
+ `{ kind: "height", color, density, baseHeight, heightFalloff, maxOpacity }`.
631
+ Height fog replaces linear fog and integrates exponential density along the
632
+ camera ray. `density` (0.000001 to 10, default 0.02) is extinction per world
633
+ unit at `baseHeight` (-10000 to 10000, default 0). `heightFalloff` (0.000001
634
+ to 100, default 0.2) reduces density above that height. `maxOpacity` (0 to 1,
635
+ default 1) limits coverage. Omit `fog` to disable it. Both camera projections,
636
+ rigid and animated models, and 3D particles receive scene fog.
637
+
638
+ Add an image projection with `decal3d: { assetId, receiverId, size: { x, y, z },
639
+ opacity, renderOrder }`. `assetId` names a verified PNG, JPEG or WebP image
640
+ slot. Browse a staged image with `browse_native_game_assets`, then use its
641
+ binding with `install_native_game_asset`. `receiverId` names one existing
642
+ non-template primitive or rigid model entity in the same scene. Use a separate
643
+ projector entity whose `transform3d` positions and rotates the projection box.
644
+ Its scale multiplies `size`. Parent the projector to the receiver to keep the
645
+ decal attached as it moves. Otherwise the projection volume stays in world
646
+ space. Each size axis is 0.001 to 1000 world units,
647
+ `opacity` is 0 to 1 (default 1), and integer `renderOrder` is 0 to 31 (default
648
+ 0). Higher order draws later. Ties follow entity order.
649
+
650
+ A scene accepts at most 32 authored decals. Projection follows moving rigid
651
+ receivers and selected visible model nodes, respects foreground depth, and
652
+ supports image transparency. Skinned, morphed or instanced receivers, and
653
+ transparent or alpha-masked receiver materials, report a diagnostic instead of
654
+ projecting. A receiver with `animator3d` is invalid. Projection also reports a
655
+ diagnostic when its bounded node, triangle or output work exceeds the renderer
656
+ limit. Decals affect presentation only and never add collision surfaces.
657
+
658
+ `environment.shadows` is `{ enabled, mapSize, extent, cascades? }`. Without
659
+ `cascades`, the one shadow-casting directional light draws a single map that
660
+ covers `extent` meters around the light. For scenes deeper than about 30 m, add
661
+ `cascades: { count, split, maxDistance }`. It splits the camera view into
662
+ `count` maps (2 to 4, default 3) up to `maxDistance` meters (default 200), so
663
+ near shadows stay sharp and far shadows stay continuous. `split` (0 to 1,
664
+ default 0.5) gives nearby cascades more resolution as it rises. `extent` is
665
+ ignored while cascades are on. Only one directional light can cast shadows.
666
+
667
+ Point and spot lights cast shadows when their `light3d.castShadow` is `true`.
668
+ At most 4 of them may cast per scene. Validation rejects a fifth. If spawned
669
+ prefabs push a frame over the budget, the extra lights render unshadowed and
670
+ the capture stats report one diagnostic. Each local shadow re-renders the scene
671
+ (six times for a point light), so enable it only on lights that need it.
672
+ Every light accepts `shadowBias` (-0.01 to 0.01) and `shadowNormalBias` (0 to
673
+ 1), both default 0. Raise `shadowNormalBias` to about 0.02 to 0.05, or set
674
+ `shadowBias` to about -0.0005, when lit surfaces show striped shadow acne. Too
675
+ much bias detaches shadows from their casters. Set lights with `update_entity`.
676
+
677
+ `environment.sky` sets the background and image-based lighting. Omit it, or use
678
+ `{ kind: "color" }`, for the solid `background` color without environment
679
+ lighting. `{ kind: "procedural", sunEntityId?, turbidity, rayleigh,
680
+ groundColor, intensity }` renders a physical sky. Its sun follows
681
+ `sunEntityId`, which must name a directional light in the same scene. Without
682
+ it, the sun follows the scene's first directional light. Rotate that light to
683
+ move the sun. `turbidity` (1 to 20, default 10) adds haze. `rayleigh` (0 to 4,
684
+ default 2) deepens blue and sunset tones. `groundColor` fills the lower
685
+ hemisphere. `intensity` (0 to 8, default 1) scales both the sky background and
686
+ its reflections. Metal and glossy materials reflect the sky. Ambient light
687
+ still adds on top, so lower `ambient.intensity` when the sky lights the scene.
688
+ HDRI skies are not available yet: an HDRI asset cannot be installed.
689
+
690
+ `environment.postProcessing` grades the rendered image. It is presentation only
691
+ and never changes simulation or snapshots. `enabled: false` keeps the settings
692
+ and renders as if they were absent. The fields are:
693
+
694
+ - `exposure` (1/32 to 16, default 1) multiplies scene light before tone mapping.
695
+ - `toneMapping` is `aces` (default), `agx`, `neutral` or `linear`. AgX keeps
696
+ saturated highlights from turning white. Neutral keeps base colors closest to
697
+ their authored values. Linear scales by exposure and clips.
698
+ - `bloom` `{ threshold, softness, radius, intensity }` adds glow around light
699
+ above `threshold` (0 to 8, default 0.85). Emissive materials and bright
700
+ highlights bloom. `softness` (0 to 0.5) widens the threshold transition.
701
+ `radius` (0 to 1) spreads the glow, and `intensity` (0 to 4) sets its strength.
702
+ - `vignette` `{ intensity, radius, softness }` darkens the corners. `intensity`
703
+ (0 to 1) of 1 reaches black. Darkening starts at `radius` (0 at the center,
704
+ 1 at the corner) and reaches full strength over `softness`.
705
+ - `antialias` is `msaa` (default), `fxaa`, `smaa` or `none`. `smaa` gives the
706
+ cleanest edges after the other effects. `fxaa` is cheaper and softer.
707
+
708
+ The passes run in this order: bloom, exposure and tone mapping, vignette,
709
+ antialiasing. Exposure and tone mapping alone cost nothing extra. Bloom,
710
+ vignette, or an `antialias` other than `msaa` renders through extra full-screen
711
+ passes, and the background color then passes through tone mapping too. SSAO,
712
+ depth of field, LUT grading and chromatic aberration are not available in 3D.
713
+
714
+ ## Input bindings
715
+
716
+ Action and axis names come from `inputActions` and `inputAxes`. The optional
717
+ document-level `inputBindings` maps each name to physical controls. Set it with
718
+ `set_game {input_bindings}`, or pass `input_bindings: null` to return to the
719
+ defaults. The op replaces the whole map:
720
+
721
+ - `actions`: `{ action: [binding] }`. A binding is `{kind: "key", code}` with a
722
+ `KeyboardEvent.code` such as `KeyJ`, `{kind: "keyValue", key}` with a
723
+ `KeyboardEvent.key` such as `z` or `+` (letter case is ignored, and a key
724
+ that a `key` binding names by code does not also match),
725
+ `{kind: "mouseButton", button}`, `{kind: "gamepadButton", button}` in the
726
+ standard gamepad layout, `{kind: "gamepadAxis", axis, direction:
727
+ "negative"|"positive", threshold?}`, `{kind: "touchButton", label?}` or
728
+ `{kind: "touchStick", direction}`.
729
+ - `axes` (3D only): `{ axis: [binding] }` with `{kind: "keys", negative, positive}`
730
+ code lists, `{kind: "gamepadAxis", axis, deadZone?, invert?}`,
731
+ `{kind: "gamepadButtons", negative, positive}` or
732
+ `{kind: "touchStick", axis: "x"|"y", invert?}`. The binding with the largest
733
+ magnitude sets the value.
734
+ - `look` (3D only): a list of `{kind: "mouse", sensitivity?, invertY?}`,
735
+ `{kind: "gamepadStick", xAxis?, yAxis?, deadZone?, speed?, invertY?}` and
736
+ `{kind: "touchDrag", sensitivity?, invertY?}`.
737
+
738
+ A listed action or axis uses exactly the listed bindings, and an empty list
739
+ unbinds it. An unlisted one keeps the defaults generated from its name. In 2D,
740
+ arrows and WASD are `left`/`right`/`up`/`down` and Space is `space`, by key
741
+ position. Any other action is a `keyValue` binding of its name, so it follows
742
+ the player's keyboard layout. In 3D, `fire` is the left mouse button and F,
743
+ `jump` is Space, `respawn` is R, and `moveX`/`moveZ` read WASD and arrows.
744
+ Defaults add the gamepad d-pad and left stick for directions and movement,
745
+ button 0 for `jump`/`space`, the right trigger for `fire`, the right stick for
746
+ look, and touch buttons for every other action. Binding an undeclared action or
747
+ axis is a validation error.
748
+
749
+ Bindings only turn physical input into input frames. They never enter the
750
+ simulation, so playtests, replays and snapshots use the same `pressed`,
751
+ `justPressed`, `axes` and `look` frames. Gamepads are polled once per tick. The
752
+ 3D players request mouse capture on click only when mouse look is bound.
753
+ Mouse actions work on the acquisition click and when capture is unavailable or
754
+ rejected. Explicit `look: []` disables capture. HUD buttons own their presses
755
+ before gameplay mouse or touch controls, and letterbox bars do not hit the HUD.
756
+ On a touch screen, editor play, public players and both standalone players
757
+ show a stick when an action or axis has a touch stick
758
+ binding and one button per action with a touch button binding. The 3D player
759
+ also turns the camera by dragging on the right half. Input pressed while play
760
+ is paused is dropped. Player-facing rebinding is not stored yet.
761
+
762
+ ## HUD widget tree
763
+
764
+ `ui` on the document holds HUD widgets drawn in every scene. `ui` on a scene
765
+ holds widgets drawn above them in that scene. Set either with
766
+ `set_ui {scene_id?, ui}`, or `ui: null` to remove it. The op replaces the whole
767
+ tree: `{safeArea?, focusNavigation?, nodes: [node]}`. 2D and 3D documents
768
+ take the same tree and draw it with the same layout.
769
+
770
+ Every node has `kind`, `id`, and optionally `parent`, `anchor`, `pivot`,
771
+ `offset`, `width`, `height`, `visible` and `opacity`. `anchor` is a point
772
+ `{x, y}` in 0..1 of the parent's rectangle, or of the HUD rectangle for a root
773
+ node, so `{x: 1, y: 0}` is the top-right corner. `pivot` is the node's own point
774
+ placed there and defaults to `anchor`. `offset` moves the node in HUD pixels.
775
+ The 2D HUD rectangle is the camera view at `pixelsPerUnit` pixels per unit, so
776
+ a 16 by 9 camera at 32 pixels per unit gives 512 by 288. The 3D HUD rectangle
777
+ is `presentation.hudWidth` by `hudHeight`, stretched over the viewport. With
778
+ `safeArea` (default true) root anchors stay inside the device's safe-area
779
+ insets.
780
+
781
+ - `panel {color?, cornerRadius?}` fills its rectangle and holds children placed
782
+ by their anchors.
783
+ - `image {assetId}` draws an image asset. 3D documents bind no images, so a 3D
784
+ image node fails validation.
785
+ - `text {text, size?, color?, align?, fontId?}` sizes itself from its text
786
+ unless it has a width.
787
+ - `bar {source?, value?, max?, color?, background?, direction?}` fills
788
+ `value / max` (max defaults to 1). `source: {kind: "health", entityId}` reads
789
+ the entity's health and its health behavior's maximum.
790
+ - `button {action, text?, background?, size?, color?, fontId?}` presses a
791
+ declared input action while a mouse button or a finger holds it.
792
+ - `stack {direction?, gap?, padding?, align?}` lays children in a column or a
793
+ row and sizes itself from them.
794
+ - `grid {columns, gap?, padding?}` lays children in cells as large as the
795
+ largest child.
796
+
797
+ A child must come after its parent, and only panels, stacks and grids hold
798
+ children. Panels, images, bars and buttons need `width` and `height`. Ids are
799
+ unique across the document tree and each scene tree.
800
+
801
+ Scripts change nodes with `{kind: "ui", id, text?, value?, max?, visible?}`.
802
+ `text` applies to text and buttons, `value` and `max` to bars, and `visible`
803
+ to any node. A change lasts until the scene changes and is saved in snapshots.
804
+ A command for a missing node, or a field the node does not have, fails the
805
+ step. An `onDestroy` hook may return `ui` commands.
806
+
807
+ A button press reaches scripts as its action in `pressed` and `justPressed`,
808
+ exactly like a key, so replays and snapshots need nothing extra. In 3D a click
809
+ on a button reaches the HUD only while the mouse is not captured. With
810
+ `focusNavigation: true`, the gamepad D-pad moves focus between visible buttons
811
+ and button 0 presses the focused one. While the tree shows a button, those
812
+ gamepad buttons drive the HUD instead of the game's bindings. Existing `hud`
813
+ labels still draw, above the tree.
814
+
815
+ ## Distance culling and frame budgets (3D)
816
+
817
+ Both are presentation only. They never change simulation, snapshots or replay.
818
+
819
+ - `set_performance {performance}` replaces document `performance`. `null`
820
+ removes it. `cullLayers` maps a layer name to `{maxDistance}` (at most 32
821
+ layers). `budgets` sets `drawCalls`, `triangles`, `particles` and `voices`.
822
+ - `update_entity` with `set: {renderCulling: {layer?, maxDistance?}}` hides
823
+ that entity and its children when the game camera is farther than the
824
+ distance from the entity's origin. The nearest setting in the parent chain
825
+ wins, and an entity's own `maxDistance` wins over its layer's. A layer must
826
+ be declared, or validation reports `missing_cull_layer`.
827
+ - Distance is measured to the entity origin, not to its nearest surface. Cull
828
+ props and small decoration. Do not cull large scenery such as terrain or
829
+ buildings, because they disappear while the camera is still close to their
830
+ edges. Never cull the player or anything the player must see to win. The
831
+ editor camera shows every entity.
832
+ - Budgets left out use the player defaults: 1000 draw calls, 1,000,000
833
+ triangles, 4096 particles and 24 voices. The standalone player warns in the
834
+ browser console once each time a budget is exceeded. The standalone players
835
+ and 3D capture count live particles. The 2D player uses the default particle
836
+ budget because 2D documents have no `performance`.
837
+
838
+ ## Editor panels the user can open
839
+
840
+ Point the user to these when they work in the game editor alongside you.
841
+
842
+ - **Run 10 s** diagnoses the draft in an independent session and reports the
843
+ first failing tick and script time by entity. **Replay to tick** rewinds the
844
+ active play session after a script error. **Ask the assistant** passes the
845
+ script context to the game assistant.
846
+ - Editing an existing script source during play recompiles it without resetting
847
+ the tick or script state. A compilation failure keeps the previous source.
848
+ Changing script form between a function and lifecycle object requires
849
+ **Restart from tick 0**. Other draft changes also require a restart.
850
+ - The **Console** panel lists play-session errors, script errors with their
851
+ tick, reload compilation errors, script log lines, **Run 10 s** failures and
852
+ validation errors.
853
+ - The **Assets** panel shows the `browse_native_game_assets` catalog with
854
+ thumbnails. **Generate** and **Regenerate** run `generate_game_asset` for
855
+ image, speech and music slots. **Use** binds an older candidate and moves the
856
+ slot's frame and tile bindings onto it.
857
+ - Ctrl+K (Cmd+K on macOS) opens the command palette, where **Edit keyboard
858
+ shortcuts** rebinds editor shortcuts.
859
+ - When the script pane reports a draft conflict, local typing stays unsaved
860
+ until the user picks **Keep my version** or **Use draft version**.
861
+ - Restoring a revision asks for confirmation because it replaces the draft and
862
+ clears its undo history. The newest 100 published revisions are retained.
863
+
864
+
865
+ ## 3D forces and torques
866
+
867
+ Return `{kind: "addForce", force: {x, y, z}}` to accelerate the issuing
868
+ entity's dynamic body, `{kind: "addTorque", torque: {x, y, z}}` to turn it,
869
+ or `{kind: "addForceAtPoint", force: {x, y, z}, point: {x, y, z}}` to apply
870
+ a force at a world-space point. An offset point also produces torque. All
871
+ vectors use world space, even when the body is rotated. Each component must
872
+ be finite and between -1000000 and 1000000.
873
+
874
+ Loads accumulate within the current simulation tick and clear after its
875
+ physics step. Return the commands on subsequent ticks to keep applying them.
876
+ Use `impulse` for an immediate velocity change. Forces require the issuing
877
+ entity's active dynamic body. Static, kinematic, missing and inactive bodies
878
+ reject them. A nonzero load wakes the body, while a zero load keeps a sleeping
879
+ body asleep. These commands are available only in 3D scripts.