[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6BY1CSS51H0P7B67RP3SE3F":3,"skill-same:01M6BY1CSS51H0P7B67RP3SE3F":75},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":47,"linkOnly":48,"markdownContent":49,"markdownBody":50,"frontMatter":51,"refName":52,"commitSha":53,"skillDirectory":54,"forksCount":55,"repositoryHtmlUrl":56,"repositoryHomepageUrl":57,"ownerHtmlUrl":58,"ownerBlogUrl":12,"ownerDescription":59,"updatedAt":60,"install":61,"resources":74},"01M6BY1CSS51H0P7B67RP3SE3F","native-game-reference",null,"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.","nodetool-ai\u002Fnodetool","nodetool-ai","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F164873569?v=4","01M6BXPFDW36K9K1MR8ZH63HG2","nodetool.ai","01M6BXPFJ893XM219GQN60B0WK",627,"AGPL-3.0","https:\u002F\u002Fgithub.com\u002Fnodetool-ai\u002Fnodetool\u002Fblob\u002Fmain\u002Fpackages\u002Fsystem-skills\u002Fnative-game-reference\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000016","图像与多媒体","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],"native","game","native game reference","native game","reference","hooks","when","effects","mix","particles","rendering","scripts","panels","widgets","physics","performance","Audio","3D","Animation","lifecycle","documents","visuals","editor","2026-11-10 10:12:30",false,"---\nname: native-game-reference\ndescription: \"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.\"\n---\n\n# Native game field reference\n\nThe fields and runtime rules behind `native-game`. Load `native-game` first for\nthe build, playtest and asset loop. The `edit_native_game` op description also\nlists the op shapes for prefabs, the mixer, script params, input bindings, the\nHUD tree, animation graphs, performance, spatial audio and collision layers.\nSections here say what the fields do and how they interact.\n\n## Schema versions\n\nNew 2D games start at `schemaVersion: 4` with `engineVersion: \"3\"`. Older 2D\ndocuments use schema 1 or 2 with engine `\"1\"`. Fonts, prepared assets, scene\nmusic, lighting, backgrounds, gravity, `visualAnimation` and a render effect\nchain need schema 2 or later. Tags, props and script params need schema 4.\nValidation rejects these fields on an earlier schema. 2D `schemaVersion: 5` with\n`engineVersion: \"4\"` is reserved for Rapier 2D physics, which this runtime does\nnot provide: validation and sessions refuse it with `engine_unavailable`. Do not\nauthor schema 5. A schema must use its own engine version. 3D games use\n`schemaVersion: 3` with `engineVersion: \"2\"`.\n\n## Entity tags and properties\n\nBoth dimensions support optional `tags` and `props` on an entity. Use\n`update_entity` with `set: {tags: [\"hero\"], props: {health: 10}}`. Each field\nreplaces its complete value. A top-level `null` removes the stored field.\nNested `null` is JSON data. Tags are unique, with at most 64 strings of\n1–128 characters. Properties hold finite JSON, with at most 64 top-level keys,\n16 levels of nesting and 64 KiB of UTF-8 JSON. Keys contain 1–128 characters.\n`__proto__`, `constructor` and `prototype` are rejected at every nesting level.\n\nScripts read `entity.tags`, `entity.props`, `entity.active` and\n`entity.rotation`, and the same fields on existing `world` entries and\n`world.get(id)` results. `world.query({tag})` returns the ids carrying a tag.\nRotation is radians in 2D and the committed world quaternion `[x, y, z, w]` in 3D.\nUse `setProp {key, value}` and `removeProp {key}` to change the caller's\nruntime properties. Setting `null` keeps that key. Removal deletes it. The\nruntime checks every property command in a tick before it changes any state,\nand an error names the entity and tick. The props of each entity that runs a\nscript count once toward the 64 KiB script input limit. Other entities' props\ntravel with the world only when a script reads `input.world`. Runtime properties survive snapshot restore and\nremain independent of the authored document and other prefab instances.\nMutating an input object does not change runtime state.\n\n## 2D scripts\n\nA `script` behavior is a function expression, or an object of lifecycle hooks\n(see Lifecycle hooks and timers). A function receives `{tick, pressed,\njustPressed, events, entity, world, state, random}` and returns `{state,\ncommands}`. `events` are the previous tick's events. `world` lists every active\nentity with a collider or camera as `{id, source, x, y}`. `entity` holds the\ncaller's `id`, `source`, position, velocity, and `touching {down, up, left,\nright}`, which names the sides of its collider that rest against a solid at the\nstart of the tick. A spawned instance has the id `\u003Cprefab>#\u003Cn>`, its `source`\nis the prefab id, and it runs the prefab's script with its own state.\n\nFunctions, lifecycle hooks, and timer methods must be synchronous. Async functions\nand generators fail preparation. Returning a promise, thenable, or iterator fails\nthe call. Use tick timers for delayed work.\n\nStore persistent values in returned `state`. Nothing else carries over between\ncalls. A plain function expression such as `(input) => { … }` runs in strict\nmode in a reused sandbox whose built-ins and global object are frozen, which\nmakes each call several times cheaper. Assigning to a global or a built-in\nthere throws, and the engine then repeats the call in a fresh context and keeps\nthat source in fresh contexts. Other sources, such as a function returned by an\ninitializer call, evaluate in a fresh context each call. Prefer plain function\nexpressions and keep helpers and temporary values in local variables.\nSource initialization uses the seeded random generator. `maxTickMs` limits each\ncall, subject to the total script budget for the tick. Source and input\u002Foutput\nlimits count UTF-8 bytes.\n\nDuring a function call, global `world.get(id)` returns an entity or `undefined`,\nand `world.query({source?, tag?, near?, radius?, limit?})` returns matching IDs\nin runtime entity order. Both read the same immutable tick-start snapshot,\nbefore movement behaviors or script commands run. Returned values are private\ncopies. Queries cover every active entity. Legacy `input.world` keeps its\nexisting shape and population, including only collider or camera entities in 2D.\nThe engine serializes `input.world` only when a script reads it, with a 1 MiB\nlimit, so prefer `world.get` and `world.query` with `near` and `radius`.\nThe 2D query record is `{id, source, x, y, velocityX, velocityY, grounded}`.\nThe 3D record is `{id, source, position, velocity, grounded}`, with XYZ vectors.\n3D records and schema 4 2D records add `tags`, `props`, `rotation` and `active`.\nA `tag` filter matches only entities that carry the tag, so it matches nothing\non schema 1 or 2. Pass `near: {x, y, z?}` and `radius` together. The radius is\nnonnegative and includes entities on its boundary, measured between centers,\nwith omitted `z` treated as zero. `source` matches the record's original entity\nID, not a 3D prefab asset ID. `limit` is an integer from 0 to 1024, defaulting\nto 1024. A call allows 64 combined `get`\u002F`query` attempts. Exceeding the count\nfails the call even if guest code catches it. Query arguments allow 4096 JSON\ncharacters, `get` IDs 1024 characters, and responses 64 KiB. These APIs are\nunavailable during source initialization.\n\nThe commands are `setVelocity`, `setPosition`, `setVisual {tint?, opacity?,\nrotation?, scaleX?, scaleY?, flipX?}`, `playAnimation {clip}`, `spawn {prefabId,\nx?, y?, velocityX?, velocityY?}`, `despawn`, `emit`, `sceneTransition`, and\n`hud {id, text, x, y, size?, color?, align?}`. HUD coordinates are canvas\npixels. Empty text removes a label. A label with the id `score` or `win`\nreplaces the built-in label. Set `fontId` to a logical font binding for a HUD\nlabel. Font assets use `mediaKind: \"font\"`, `fontFormat: \"ttf\" | \"otf\"`, and\n`required: true` by default. Required fonts load before the first frame or fail\nplay and capture. An optional font (`required: false`) reports a fallback before\nplay. Export embeds the font bytes for offline use.\n\n`log {message, level?}` writes a line to the editor console in 2D and 3D.\n`level` is `\"log\"` (the default), `\"warning\"` or `\"error\"`. A log line never\nchanges the simulation, snapshots or replay, and it does not count toward\n`maxCommands`. A tick keeps 32 lines and reports the rest as one dropped-lines\nwarning. Messages longer than 1000 characters are truncated. Log text still\ncounts toward the 64 KiB output limit of a script call. Lines logged before a\nscript failure appear in the console ahead of the error.\n\n### Script time limits\n\n`maxTickMs` accepts 1 through 20 ms, defaults to 8 ms, and cannot raise the\nhost's 20 ms ceiling. It counts the CPU time of the thread running the script, from just\nbefore the call (or before compiling its source) until its output is checked.\nWaiting for a processor does not count, and on Node neither do garbage collection\npauses during the call. A call ends only when both its CPU time\nand its wall time reach the limit. Browsers have no thread CPU clock, so there\nthe limit counts wall time. The 50 ms script budget for a whole tick and the\n100 ms limit for evaluating a source during preparation count wall time on\nevery host. A session's first scripted tick has 1,000 ms instead of 50 ms,\nbecause it runs each script for the first time. A script that loops forever still ends at its limit.\n\nWhat an overrun does depends on the host. The editor's play mode and the\nstandalone players skip the call: the entity keeps its script state, issues no\ncommands that tick, and the editor console shows a warning naming the call. A tick\npast its 50 ms budget skips its remaining calls the same way. Headless runs,\nreplays, the CLI and agent playtests stop with a `Game script interrupted`\nerror, so a slow script fails verification. A skipped call depends on timing,\nso a play session that skipped one may not replay identically.\n\n### Script parameters\n\nDeclare a script's tunables as `params` on its script behavior instead of\nconstants in the source. The inspector edits them and scripts read the values\non `input.params`. Set them with `set_script_params {entity_id, scene_id?,\nindex, params?, values?}`. `params` replaces the declarations and `null`\nremoves them with their values. `values` merges stored values, and a `null`\nvalue returns one to its default. 2D games need schema 4. 3D games accept\nparams on schema 3.\n\n```json\n{\"op\": \"set_script_params\", \"entity_id\": \"player\", \"index\": 0,\n \"params\": {\"speed\": {\"type\": \"number\", \"default\": 3, \"minimum\": 0, \"maximum\": 10},\n            \"target\": {\"type\": \"entity\"}, \"hit\": {\"type\": \"asset\", \"kind\": \"audio\"}},\n \"values\": {\"speed\": 5, \"target\": \"goal\"}}\n```\n\nTypes are `number` (`minimum`, `maximum`, `integer`), `boolean`, `color`\n(`#rrggbb`), `enum` (`options`, default the first option), `entity`, `asset`\n(optional `kind`) and `vector` (`dimensions` 2 or 3). A behavior declares at\nmost 32 params with identifier names. `input.params` holds the stored value,\nelse the default. An `entity` or `asset` param without either is `null`.\nValidation rejects an entity reference outside the behavior's scene, a missing\nasset slot, an asset of the wrong `kind`, and in 3D an entity reference inside\na prefab or an undeclared prefab asset. A behavior without `params` has no\n`input.params` key. Params count once per behavior definition toward the\n64 KiB script input limit.\n\n### Lifecycle hooks and timers\n\nA script source may be an object of hooks instead of a function. It works in\n2D and 3D with no schema change.\n\n```js\n({\n  onStart(input) { every(30, \"fire\"); return { state: { shots: 0 } }; },\n  fire(input) { return { state: { shots: input.state.shots + 1 },\n    commands: [{ kind: \"spawn\", prefabId: \"bolt\" }] }; },\n  onTriggerEnter(input, contact) { return { commands: [{ kind: \"emit\", event: \"hit:\" + contact.otherId }] }; },\n  onDestroy(input) { return { commands: [{ kind: \"emit\", event: \"gone\" }] }; }\n})\n```\n\nHooks receive the same `input` as a function script and return\n`{state?, commands?}` or nothing. Each hook sees the state returned by the\none before it. Within a tick they run in this order:\n\n1. `onStart`, on the behavior's first call.\n2. `onSceneEnter`, on the first tick of the scene.\n3. `onTriggerEnter`, `onTriggerExit` and `onContact`, once per contact of\n   the entity in the previous tick's events, in event order. A contact where\n   either collider is a sensor is a trigger. Triggers fire on `enter` and\n   `exit`. `onContact` receives every phase of a solid contact. The second\n   argument is `{otherId, phase, sensor}`.\n4. `onEvent`, once per trigger event of the previous tick that reaches the\n   entity, in event order. See Typed events.\n5. Due timers, in the order they were scheduled.\n6. `onFixedUpdate` or `onUpdate`. They are aliases. Define one of them.\n\n`onDestroy` runs alone in the tick after the entity despawns. It may return\nonly `hud`, `emit`, `spawn`, `despawn`, `sceneTransition`, `log`, `save` and `load` commands. A scene\ntransition discards behavior state without calling `onDestroy`.\n\n`after(ticks, name)` calls the object's method `name` once, `ticks` ticks\nlater. `every(ticks, name)` calls it every `ticks` ticks. `ticks` is an\ninteger from 1 to 1000000, `name` must be a method that is not a hook, and a\nbehavior holds at most 32 timers. Scheduling an existing name replaces that\ntimer. Timers live in the behavior's snapshot state, so they replay from any\nsnapshot. Session preparation rejects an unknown `on*` key, a hook that is not\na function, and an object that defines both `onUpdate` and `onFixedUpdate`.\nLifecycle objects run in a fresh context each call.\n\n### Typed events\n\n`emit` takes an optional `payload` and `target` in 2D and 3D, with no schema\nchange. The payload is any JSON value up to 1 KiB serialized and 16 levels\ndeep. The target is `{entityId}` for one entity or `{tag}` for every entity\nwith that tag. Omit `target` to broadcast.\n\n```js\n\u002F\u002F Sender\n({ onTriggerEnter(input, contact) { return { commands: [{ kind: \"emit\", event: \"alarm\",\n  payload: { by: contact.otherId, level: 2 }, target: { tag: \"guard\" } }] }; } })\n\u002F\u002F Receiver, on an entity tagged \"guard\"\n({ onEvent(input, event) { if (event.event === \"alarm\") { return { state: { level: event.payload.level } }; } } })\n```\n\nThe event lands in the stream as `{kind: \"trigger\", event, entityId,\npayload?, target?}`, where `entityId` is the sender, and reaches scripts on\nthe next tick. `onEvent(input, event)` receives that object for each trigger\nevent that reaches its entity, broadcasts included, and the sender receives\nits own broadcasts. Built-in `trigger` behaviors emit untargeted events, so\n`onEvent` sees them too. The target also limits built-in listeners: a `spawn`\nor `sceneTransition` behavior with `onEvent` and an `audioSource` with\n`onEvent` react only to events that reach their entity. `input.events` still\nholds every event of the previous tick, targeted or not. A tag target reaches\nnothing on 2D schemas before 4. A plain `{kind: \"emit\", event}` produces the\nsame event as before. Payloads count toward the 64 KiB script input limit once\nper tick.\n\n### Player saves\n\nScripts save and load in 2D and 3D with two commands:\n\n- `save {slot, data, scope?, snapshot?}` writes `data` to a slot after the tick.\n- `load {slot, scope?}` reads a slot.\n\n`scope` is `\"progress\"` (the default) or `\"settings\"`. The two scopes are\nseparate stores, so a progress slot and a settings slot can share a name and\nremoving one never touches the other. `snapshot: true` also stores the\nend-of-tick session snapshot with a progress save. A settings save with a\nsnapshot fails the tick. The player keeps one snapshot save pending at a time,\nand the editor stores the data without the snapshot.\n\nThe player runs saves and loads in order, with at most 8 waiting. A request\npast that limit fails without stopping the game, and a failed load answers\nwith null data.\n\nA slot name is 1 to 32 letters, digits, `_` or `-`. `data` must be a JSON\nobject of at most 16 KiB serialized, 256 keys counted at every depth and 16\nlevels deep. Each scope holds at most 16 slots per game. A tick accepts at most\n4 save and load commands across all scripts, and more fail the tick. These\ncommands do not count toward `maxCommands` and never enter snapshots.\n\nA load answers on a later tick through `input.loaded`, a list of `{scope, slot,\ndata}` where `data` is null for an empty slot. A frame carries at most 2\nresults. Keep what the game needs in script state:\n\n```js\n({ onStart() { return { commands: [{ kind: \"load\", slot: \"main\" }] }; },\n  onUpdate(input) {\n    const result = input.loaded?.find((entry) => entry.slot === \"main\");\n    return { state: result ? { best: result.data?.best ?? 0 } : input.state };\n  } })\n```\n\nThe loaded data is part of the input frame, so a replay of recorded frames\nreproduces it without storage. The standalone player stores saves in IndexedDB.\nThe editor stores them in localStorage under the game id. `nodetool game\nsimulate` and agent playtests keep them in memory. `simulate --saves \u003Cfile>`\nseeds slots from `{progress?, settings?}`, and its JSON report lists the slots\nafter the run.\n\n## 2D visuals\n\nFor generated art in a workflow, pass one style entity with a reference image.\nThe guided graph sends that image through an image-to-image generation input\nfor every visual slot. `StageGameAssets.preparation` accepts per-slot alpha\ntrimming, target size with a crop policy, pivot, nearest or linear sampling,\nand mirror tiling for single images. Trimming records crop offsets so placement\nkeeps its anchor. `StageGameAssets.fonts` stages TrueType or OpenType assets by\nlogical font ID.\n\n1. Use `sprite.blend: \"additive\"` for light drawn on black, such as glows and\n   sparks. Set `sampling: \"linear\"` on painted or soft assets.\n2. Use `lifetime {ticks, fade, endScale}` for short-lived entities. A kinematic\n   body without a collider moves but never collides, which keeps them cheap.\n   Prefer the `particles` component for sparks, smoke and dust.\n3. Use `animator` frames on a sprite sheet. Playback starts when the entity\n   spawns. Named `animator.clips` hold other frame sets. `playAnimation` starts\n   a clip from its first frame, and a repeated request for the current clip\n   continues it. Set `sprite.flipX` or the `setVisual` flag to mirror art, or\n   `sprite.faceMotion: \"left\" | \"right\"` to turn art that faces that way toward\n   the body's horizontal motion.\n4. Give `audioSource.onEvent` an event kind or the name of an emitted trigger.\n   Set `audioSource.volume` from `0` to `1` for effect loudness. Effects receive\n   distinct voice ids, so repeated triggers can overlap within the 32-voice cap.\n5. Make hazards sensors. Only a non-sensor body collects a `collectible`.\n\nSet `scene.music` to an installed audio `assetId` for looping music. `volume`\nranges from `0` to `1`, and `fadeInTicks` and `fadeOutTicks` range from `0` to\n`600`. Music starts at the scene's entry tick. Pause suspends audio, reset\nrestarts the entry scene once, and loading resumes the loop at the saved\nlogical tick. Set `music.streaming: {loopStartSeconds, loopEndSeconds?}` for\nlong tracks in 2D or 3D. This streams through the music bus without decoding\nthe whole asset. Start defaults to 0 seconds, end defaults to the track's end,\nand an explicit end must exceed start. Both are bounded by 86400 seconds.\nThe intro plays once. Custom loop boundaries follow browser media events.\nExisting fades crossfade outgoing and incoming scene tracks. Use `update_scene`\nto replace the whole `music` object. Omit `streaming` to retain decoded\nplayback. Browser playback starts after a user gesture. A missing or\nunplayable audio asset is reported in the player.\n\n`visualAnimation` on a sprite animates `rotation`, `scaleX`, `scaleY`,\n`opacity`, or `tint` from `from` to `to` over `durationTicks` in its `tracks`.\n`delayTicks`, `repeat`, `pingPong`, and `easing` control timing. `rotationRate`\nadds radians per tick for continuous spins and cannot share a rotation track.\nEach property has at most one track. Tracks start at scene entry or spawn time.\nThe visual order is authored transform and sprite values, tracks, script\n`setVisual`, then lifetime fade and scale. Tracks do not change collision.\n\nScene `backgrounds` define image layers independent of entities. Each layer\nsets `assetId`, world-unit `width` and `height`, `origin` at the tile center,\n`layer`, per-axis `parallax`, `scrollRate` in world units per second, and `mode`\n(`none`, `repeat`, `repeatX`, or `mirror`). `repeatX` tiles only sideways, which\nsuits a strip of scenery. Parallax `0` stays fixed to the screen and `1`\nstays fixed in the world. Optional `frame` selects an atlas rectangle.\n\nScene `lighting` holds an `ambient` color and intensity plus up to 32 `points`\nwith world position, color, intensity, radius, and falloff. Lighting multiplies\nworld color before effects. HUD stays unlit. Set `sprite.unlit: true` for\nemissive art such as glows. Canvas2D capture paints these sprites above the lit\nworld. An entity `light2d {color, intensity, radius, falloff, offset?}` moves\nwith its entity. Each frame adds the entity lights nearest the camera to the\nscene's fixed points, up to 32 lights in total.\n\n`renderEffects` is an ordered chain of at most eight effects.\n`brightnessContrast` uses `brightness` from `-1` to `1` and `contrast` from `0`\nto `4`. `bloom` uses `threshold` from `0` to `1`, `softness` from `0` to `0.5`,\n`radius` from `0` to `64` pixels, and `intensity` from `0` to `4`. `lut` refers\nto an opaque packed 2D cube image with dimensions `size² × size` and an\noptional intensity and color domain. Effects run in listed order. Set\n`hudEffectOrder` to `afterEffects` to keep text crisp, or `beforeEffects` to\nprocess HUD with the world. A bloom chain defaults to `afterEffects`.\n`required: true` makes unsupported GPU effects fail before play or capture.\nOptional effects are omitted with a diagnostic on Canvas 2D.\n\nCollider `category` and `mask` are unsigned 32-bit bitsets in 2D. They default\nto `1` and `4294967295`. A pair interacts only when each mask includes a\ncategory bit from the other collider. Give bullets masks that exclude other\nbullets when bullet-to-bullet interaction is unnecessary.\n\nContacts report `phase: \"enter\" | \"stay\" | \"exit\"` and `normalX`\u002F`normalY`.\nCheck both `entityId` and `otherId`, since each pair emits one contact per tick.\nBuilt-in triggers fire on entry. Use `stay` contacts for repeated overlap logic.\nSwept collisions detect fast movement through thin static walls and sensors.\nScene entry starts authored entities' animation and lifetime clocks, and saves\npreserve those clocks and active contact pairs.\n\n## Particles\n\nThe particle inspector offers Fire, Smoke, Sparks, Dust, Magic, Rain, Snow and\nExplosion presets in both dimensions. Applying one replaces that entity's\n`particles` component and preserves its other components. Select an emitter\nto edit its fields, lifetime curves and color gradient. Renaming an emitter\nrepairs its death-emitter references. Removing one removes references to it.\nThe isolated preview uses a fixed seed and restarts when edited. Pause and\nRestart affect the preview only. The 2D preview uses Canvas2D, including sprite\nsheets and blending. The 3D preview uses WebGL2, including stretched and mesh\nparticles and depth softness. It does not run gameplay scripts or change the\nplay session.\n\nAgents and CLI document edits use the same `update_entity` operation as the\ninspector. Set the complete component with\n`{op: \"update_entity\", entity_id: \"effect\", set: {particles: {emitters: [...]}}}`.\nThere is no preset-name operation. Node.js authoring can also import\n`getGameParticlePreset(name, dimension)` from `@nodetool-ai\u002Fprotocol` to get\nfresh canonical data. In a code action, use the JSON recipes below with the\nexisting game edit API instead of importing that host package.\n\n### Particle preset recipes\n\nEach payload is a complete `particles` component. Omitted fields take schema\ndefaults. These canonical recipes use 3D draw settings. For a 2D entity, remove\n`render` from each emitter and set `shape.size.z` to `0` for box shapes.\nKeep other values unchanged. Both dimensions use world units and +Y upward.\nPlace rain and snow emitters above the area they cover. Explosion is a finite\nburst, which can be restarted with `emitParticles` without a count.\n\n#### fire\n\n```json\n{\"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}]}\n```\n\n#### smoke\n\n```json\n{\"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}]}\n```\n\n#### sparks\n\n```json\n{\"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}}]}\n```\n\n#### dust\n\n```json\n{\"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}]}\n```\n\n#### magic\n\n```json\n{\"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}]}\n```\n\n#### rain\n\n```json\n{\"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}}]}\n```\n\n#### snow\n\n```json\n{\"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}]}\n```\n\n#### explosion\n\n```json\n{\"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}]}\n```\n\n### Particle component fields\n\nAdd particle effects with the `particles` component on any 2D (schema 2 or\nlater) or 3D entity. It holds up to 8 `emitters`, each with a unique `id`. An\nemitter sets `rate` (particles per second), `bursts` (`time`, `count`,\n`cycles`, `interval`), `duration`, `loop`, `playOnStart`, `maxParticles` and a\n`shape`: `point`, `circle`, `sphere`, `cone`, `box` or `edge`. Cone, box and\nedge emit along local +Y. Point, circle and sphere emit outward. Per-particle\nvalues `lifetime`, `speed`, `size`, `rotation` and `angularVelocity` take a\nnumber or `{ min, max }`. `color` takes `#rrggbb` or `{ min, max }`. The\n`sizeOverLifetime`, `speedOverLifetime` and `opacityOverLifetime` curves are\nlists of `{ t, value }` keys sorted by `t` from 0 to 1. `colorOverLifetime` uses\n`{ t, color }` keys. Add `gravity` (`{ x, y, z }` in units per second squared)\nand `drag`. `space: \"local\"` makes particles follow the entity.\n`onDeath: [{ emitter, count }]` fires another emitter of the same component\nwhere each particle dies. Give that emitter `playOnStart: false` and `rate: 0`.\n\nScripts trigger emitters with `{ kind: \"emitParticles\", emitter?, count? }` on\ntheir own entity. Without `count`, the emitter restarts its cycle, which suits\na one-shot explosion with `loop: false`. With `count`, that many particles\nspawn at once. Omitting `emitter` selects the first emitter in the list. The\ncommand is presentation only and never appears in gameplay events or\nsnapshots. Particles cannot affect scores, physics or scripts. Prefer them over\nspawned prefabs for sparks, smoke and dust.\n\nEach emitter also sets how its particles draw in 2D. Without `sprite`, a\nparticle is a soft round dot tinted by its colour. `sprite: { assetId, columns,\nrows, frameCount?, cycles?, sampling? }` uses an image asset instead. A sheet is\ncut into `columns` by `rows` cells read left to right and top to bottom, and the\nfirst `frameCount` cells play `cycles` times over each particle's life.\n`sampling` defaults to `nearest`. `blend: \"additive\"` adds light, which suits\nfire, sparks and magic. The default `normal` alpha-blends, which suits smoke and\ndust. Particles take scene lighting like sprites. Set `unlit: true` so glowing\nparticles keep their colour in the dark. `layer` sets the draw layer. It\ndefaults to the entity's sprite layer, or 0 without a sprite, and particles\ndraw above sprites of the same layer. `size` is the particle's width in world\nunits. `rotation` and `angularVelocity` are radians and radians per second.\n\nThe standalone player, the editor's play view and `capture_native_game_frame`\ndraw 2D particles. A capture simulates particles for every tick up to the\ncaptured one, so a burst at tick 0 is still visible a few ticks later. WebGPU\ndraws every live particle. The Canvas2D fallback draws at most 1024 visible\nparticles. Each emitter's random stream is seeded from its entity and emitter\nids, so captures repeat.\n\nIn 3D, `render` sets how each particle draws. The default `{ kind:\n\"billboard\" }` is a soft round dot facing the camera, `size` units across and\nturned by `rotation`. `{ kind: \"stretched\", length?, speedScale? }` lies along\nthe particle's direction of travel on screen. It is `size` wide and `size ×\nlength + speed × speedScale` long, which suits sparks, rain and streaks.\n`length` defaults to 1 and `speedScale` to 0.1 seconds. `{ kind: \"mesh\",\nshape, align? }` draws a small solid `box`, `sphere`, `tetrahedron` or\n`octahedron`, `size` units across, for debris and confetti. With `align:\n\"world\"`, the default, it turns by `rotation` about world +Y. With `align:\n\"velocity\"` its +Y points along its velocity. Mesh particles take scene\nlighting unless `unlit: true`. Billboards always keep their own colour. Both\ntake scene fog and post-processing such as bloom, so additive particles glow.\n`softness` (world units) fades a billboard out where it nears the geometry\nbehind it, so smoke and glows do not cut a hard line through the floor. The\nrenderer then draws the scene's depth once more per frame, so set it only on\nemitters that touch geometry. That depth pass skips transparent materials,\nkeeps alpha-tested cut-outs, and counts toward the draw call and\ntriangle budgets. 3D particles ignore `sprite` and `layer`, and\n2D particles ignore `render` and `softness`. Validation warns about `render` or\n`softness` on a 2D emitter, `softness` on a mesh emitter, and `sprite` on a\n3D emitter, without failing. Editor Play, the 3D standalone player,\n`nodetool game capture` and `capture_native_game_frame` draw 3D particles.\nTranslucent particles sort across render types without writing depth. Interleaved\ntypes can increase draw calls. A scene transition clears the particle field.\nWithin a scene, detached world particles retain world-space death chains until\nthey finish. Storage grows lazily within the scene particle limit.\n\n## 3D collision layers\n\n`collisionLayers` names up to 16 layers, and the layer at index `i` owns bit\n`i`. `collisionMatrix` lists the layer pairs that do not collide, in either\norder. A layer paired with itself stops its colliders from touching each other.\nEvery unlisted pair collides. Set both with `set_game {collision_layers,\ncollision_matrix}`, and put a collider on a layer with `update_entity set\ncollider3d {layer}`. A layered collider derives `category` from its bit and\n`mask` from the matrix, so leave its raw bits at `1` and `65535`. Validation\nrejects an unknown layer name, a pair listed twice in the matrix and\nconflicting layer bits. Raw `category` and `mask` remain an override for\ncolliders without a layer. A collider without a layer keeps the default\ncategory `1`, which is the bit of `collisionLayers[0]`. It therefore shares the\nfirst layer's pairs: a layer paired with `collisionLayers[0]` does not touch\nunlayered colliders either. Name the first layer for the world, or give such\ncolliders a layer. 2D colliders keep raw bits only.\n\n## 3D physics materials\n\n`physicsMaterials` names up to 64 surfaces that colliders share. Each material\nhas `friction` (0 to 4, default 0.5), `restitution` (0 to 1, default 0),\n`frictionCombine` and `restitutionCombine`. A combine rule is `average`\n(default), `min`, `multiply` or `max`. When two touching colliders use\ndifferent rules, the later rule in that list wins. Use `max` restitution for a\nbouncy ball and `min` friction for ice. Declare materials with `set_game\n{physics_materials: {name: material}}`, and remove them all with\n`physics_materials: null`. Point a collider at one with `update_entity set\ncollider3d {physicsMaterial: name}` and clear it with `physicsMaterial: null`.\nA collider with a material takes all four values from it, so leave its inline\n`friction` at 0.5 and `restitution` at 0 and set no inline combine rule.\nValidation rejects an unknown material name and conflicting inline values. A\ncollider without a material keeps its inline values and may set\n`frictionCombine` and `restitutionCombine` itself. Existing documents name no\nmaterial and simulate as before. 2D colliders have no friction or\nrestitution.\n\n## Audio mix and spatial audio\n\nSet the document mix with `set_audio {mixer}` in 2D and 3D. `set_audio\n{mixer: null}` removes it. Every mixer has the buses `master`, `music`, `sfx`,\n`voice` and `ui`. Add buses under `mixer.buses` with `parent` (default\n`master`), `volume` (linear, 1 is unity), `muted`, `lowpassHz` and\n`reverbSend`. A bus multiplies its parent's gain, so muting `sfx` silences its\nchildren. Scene music plays on `music` and other audio on `sfx` unless\n`mixer.assetBuses` maps the asset slot to another bus.\n\nThe master bus ends in a limiter at `limiter.thresholdDb` (default -1 dB) that\ndoes not change levels below the threshold. The default ducking rule lowers\n`music` to 0.35 while a voice plays on `voice` or a child of it. Replace\n`ducking` to change or remove that rule.\n\nA mixer snapshot under `mixer.snapshots` overrides named fields of named buses.\n`transitions` move to a snapshot over `fadeTicks` when the simulation emits a\n`trigger` event, a `win` event, or enters a scene. The snapshot `base` returns\nto the base mix. Restarting or restoring a session returns to the base mix or\nthe scene's snapshot without a fade. The mix is presentation state. It is not\nsaved in game snapshots and never changes playtest results or replay.\n\nSet `audioSource.spatial: true` to play an entity's effect at its position in\n2D and 3D. The listener is the active camera, so an emitter right of the camera\nplays in the right ear. Effects play at full volume within `minDistance`\n(default 1 world unit) and fall off by `distanceModel` (`linear`, `inverse`\nby default, or `exponential`) and `rolloff` (default 1) out to `maxDistance`\n(default 50), which must exceed `minDistance`. The `linear` model clamps\n`rolloff` to 1 and is silent at `maxDistance`. A 3D `cone {innerAngle,\nouterAngle, outerGain}` points along the entity's -Z axis. 2D emitters ignore\nthe cone. `doppler` (default 0, 1 is physical) shifts pitch with emitter and\ncamera motion. Set a field to `null` in `update_entity` to return it to its\ndefault. The event that starts the effect carries the emitter position, so a\ncollectible that despawns as it plays still sounds where it was. Spatial\nsettings never change simulation.\n\n## 3D animation graphs\n\nA 3D animation graph lives in the document's `animationGraphs` map. An\nentity's `animator3d.graph` names one, and its `animator3d.clips` maps the\ngraph's clip aliases to prepared clip IDs, so one graph can drive several rigs.\nStore a graph with `set_animation_graph {graph_id, graph}` and delete it with\n`remove_animation_graph {graph_id}`. An animator with a graph cannot also set\n`initialClip`.\n\nA graph declares `parameters` (`float` with a default, `bool` with a default,\nor `trigger`) and one to eight `layers`. Each layer has an `initialState`,\n`states` and `transitions`. A state plays a `motion`: one `clip`, a `blend1d`\nover a float parameter with points in increasing `value` order, or a `blend2d`\nover two float parameters. `speed` scales the animator's `playbackRate`, and\n`loop` defaults to true. The first layer is the unmasked override base. Later\nlayers can be `additive` and can set `weight` and a `mask` of prepared model\nnode IDs. A mask covers each listed node and its descendants.\n\nA transition names `from` (a state ID, or `*` for any state), `to`,\n`conditions`, optional `exitTicks` and `durationTicks` for the crossfade. Every\ncondition must hold. Float conditions use `gt`, `gte`, `lt`, `lte`, `eq` or\n`neq` with a `value`. Bool conditions use `true` or `false`. A trigger uses\n`set` and resets once a transition consumes it. `exitTicks` is how long the\nsource state must run before the transition may fire. Each transition needs a\ncondition or `exitTicks`. A crossfading layer does not start another\ntransition. Transitions are checked in document order.\n\nScripts set parameters with `setAnimParam {name, value}`. Pass `true` to fire\na trigger. `playAnimation {clip}` on a graph entity switches the base layer\ndirectly to the state with that ID, or else to the first state that plays that\nclip alias, using `animator3d.transitionTicks`. Example locomotion: a `speed`\nfloat, a `move` state with `blend1d` points idle at 0, walk at 2 and run at 6,\nand a script that sends `setAnimParam` with the character's planar speed.\n\nGraph state (current state, entry tick, crossfade source and parameter values)\nis simulation state, saved in snapshots. Check it with a playtest that sets\n`restore_at_tick` and asserts `replay_matches`. The renderer samples the\nresolved pose at the interpolated tick. Blended clips share one normalized\nphase. The pose sampler blends node translation, rotation and scale. It does\nnot blend morph targets.\n\nModel preparation derives `clipDurations` from verified GLB time accessors.\nBindings with this metadata retain a normalized simulation phase when blend\nparameters change, including across save and restore. Do not invent or edit\ndurations. For a legacy binding without them, regenerate a model candidate from\nits existing source with `generate_game_asset`, then explicitly install it with\n`install_native_game_asset`. Installation rederives the durations from its bytes.\nUntil then, blends keep legacy elapsed-time sampling and validation reports\n`legacy_animation_phase`.\n\n## 3D rendering\n\nSet every `environment` field with `update_scene`. It replaces the whole\n`environment`, so send the existing `background`, `ambient`, `fog`, `shadows`,\n`sky` and `postProcessing` with the changed field. Capture the scene and view\nthe frames to review a change.\n\n`environment.fog` accepts either linear `{ color, near, far }` or height\n`{ kind: \"height\", color, density, baseHeight, heightFalloff, maxOpacity }`.\nHeight fog replaces linear fog and integrates exponential density along the\ncamera ray. `density` (0.000001 to 10, default 0.02) is extinction per world\nunit at `baseHeight` (-10000 to 10000, default 0). `heightFalloff` (0.000001\nto 100, default 0.2) reduces density above that height. `maxOpacity` (0 to 1,\ndefault 1) limits coverage. Omit `fog` to disable it. Both camera projections,\nrigid and animated models, and 3D particles receive scene fog.\n\nAdd an image projection with `decal3d: { assetId, receiverId, size: { x, y, z },\nopacity, renderOrder }`. `assetId` names a verified PNG, JPEG or WebP image\nslot. Browse a staged image with `browse_native_game_assets`, then use its\nbinding with `install_native_game_asset`. `receiverId` names one existing\nnon-template primitive or rigid model entity in the same scene. Use a separate\nprojector entity whose `transform3d` positions and rotates the projection box.\nIts scale multiplies `size`. Parent the projector to the receiver to keep the\ndecal attached as it moves. Otherwise the projection volume stays in world\nspace. Each size axis is 0.001 to 1000 world units,\n`opacity` is 0 to 1 (default 1), and integer `renderOrder` is 0 to 31 (default\n0). Higher order draws later. Ties follow entity order.\n\nA scene accepts at most 32 authored decals. Projection follows moving rigid\nreceivers and selected visible model nodes, respects foreground depth, and\nsupports image transparency. Skinned, morphed or instanced receivers, and\ntransparent or alpha-masked receiver materials, report a diagnostic instead of\nprojecting. A receiver with `animator3d` is invalid. Projection also reports a\ndiagnostic when its bounded node, triangle or output work exceeds the renderer\nlimit. Decals affect presentation only and never add collision surfaces.\n\n`environment.shadows` is `{ enabled, mapSize, extent, cascades? }`. Without\n`cascades`, the one shadow-casting directional light draws a single map that\ncovers `extent` meters around the light. For scenes deeper than about 30 m, add\n`cascades: { count, split, maxDistance }`. It splits the camera view into\n`count` maps (2 to 4, default 3) up to `maxDistance` meters (default 200), so\nnear shadows stay sharp and far shadows stay continuous. `split` (0 to 1,\ndefault 0.5) gives nearby cascades more resolution as it rises. `extent` is\nignored while cascades are on. Only one directional light can cast shadows.\n\nEvery mesh with `castShadow` (true by default on primitives and models) draws\nagain into each cascade it reaches. The nearest cascade re-renders every frame,\nthe second every other frame, and the rest every fourth frame or when the view\nmoves past their drift limit. Use 2 or 3 cascades, set `maxDistance` to how far\nthe player can judge a shadow, and set `castShadow: false` on terrain and\nground, which still receive shadows. Validation warns with\n`large_shadow_caster` when a casting mesh 100 units or wider sits in a scene\nwith cascades. Each 2048 map holds 16 MiB of depth.\n\nPoint and spot lights cast shadows when their `light3d.castShadow` is `true`.\nAt most 4 of them may cast per scene. Validation rejects a fifth. If spawned\nprefabs push a frame over the budget, the extra lights render unshadowed and\nthe capture stats report one diagnostic. Each local shadow re-renders the scene\n(six times for a point light), so enable it only on lights that need it.\nEvery light accepts `shadowBias` (-0.01 to 0.01) and `shadowNormalBias` (0 to\n1), both default 0. Raise `shadowNormalBias` to about 0.02 to 0.05, or set\n`shadowBias` to about -0.0005, when lit surfaces show striped shadow acne. Too\nmuch bias detaches shadows from their casters. Set lights with `update_entity`.\n\n`environment.sky` sets the background and image-based lighting. Omit it, or use\n`{ kind: \"color\" }`, for the solid `background` color without environment\nlighting. `{ kind: \"procedural\", sunEntityId?, turbidity, rayleigh,\ngroundColor, intensity }` renders a physical sky. Its sun follows\n`sunEntityId`, which must name a directional light in the same scene. Without\nit, the sun follows the scene's first directional light. Rotate that light to\nmove the sun. `turbidity` (1 to 20, default 10) adds haze. `rayleigh` (0 to 4,\ndefault 2) deepens blue and sunset tones. `groundColor` fills the lower\nhemisphere. `intensity` (0 to 8, default 1) scales both the sky background and\nits reflections. Each change of sun direction re-renders the sky cube and its\nenvironment map, so a sun that turns every tick rebuilds them every tick. Keep\nthe sun still, or turn it in rare steps. Metal and glossy materials reflect the sky. Ambient light\nstill adds on top, so lower `ambient.intensity` when the sky lights the scene.\nHDRI skies are not available yet: an HDRI asset cannot be installed.\n\n`environment.postProcessing` grades the rendered image. It is presentation only\nand never changes simulation or snapshots. `enabled: false` keeps the settings\nand renders as if they were absent. The fields are:\n\n- `exposure` (1\u002F32 to 16, default 1) multiplies scene light before tone mapping.\n- `toneMapping` is `aces` (default), `agx`, `neutral` or `linear`. AgX keeps\n  saturated highlights from turning white. Neutral keeps base colors closest to\n  their authored values. Linear scales by exposure and clips.\n- `bloom` `{ threshold, softness, radius, intensity }` adds glow around light\n  above `threshold` (0 to 8, default 0.85). Emissive materials and bright\n  highlights bloom. `softness` (0 to 0.5) widens the threshold transition.\n  `radius` (0 to 1) spreads the glow, and `intensity` (0 to 4) sets its strength.\n- `vignette` `{ intensity, radius, softness }` darkens the corners. `intensity`\n  (0 to 1) of 1 reaches black. Darkening starts at `radius` (0 at the center,\n  1 at the corner) and reaches full strength over `softness`.\n- `antialias` is `msaa` (default), `fxaa`, `smaa` or `none`. `smaa` gives the\n  cleanest edges after the other effects. `fxaa` is cheaper and softer.\n\nThe passes run in this order: bloom, exposure and tone mapping, vignette,\nantialiasing. Exposure and tone mapping alone cost nothing extra. Bloom,\nvignette, or an `antialias` other than `msaa` renders through extra full-screen\npasses, and the background color then passes through tone mapping too. Bloom\nadds a bright pass and a chain of five blur levels, the costliest effect. SSAO,\ndepth of field, LUT grading and chromatic aberration are not available in 3D.\n\n## Input bindings\n\nAction and axis names come from `inputActions` and `inputAxes`. The optional\ndocument-level `inputBindings` maps each name to physical controls. Set it with\n`set_game {input_bindings}`, or pass `input_bindings: null` to return to the\ndefaults. The op replaces the whole map:\n\n- `actions`: `{ action: [binding] }`. A binding is `{kind: \"key\", code}` with a\n  `KeyboardEvent.code` such as `KeyJ`, `{kind: \"keyValue\", key}` with a\n  `KeyboardEvent.key` such as `z` or `+` (letter case is ignored, and a key\n  that a `key` binding names by code does not also match),\n  `{kind: \"mouseButton\", button}`, `{kind: \"gamepadButton\", button}` in the\n  standard gamepad layout, `{kind: \"gamepadAxis\", axis, direction:\n  \"negative\"|\"positive\", threshold?}`, `{kind: \"touchButton\", label?}` or\n  `{kind: \"touchStick\", direction}`.\n- `axes` (3D only): `{ axis: [binding] }` with `{kind: \"keys\", negative, positive}`\n  code lists, `{kind: \"gamepadAxis\", axis, deadZone?, invert?}`,\n  `{kind: \"gamepadButtons\", negative, positive}` or\n  `{kind: \"touchStick\", axis: \"x\"|\"y\", invert?}`. The binding with the largest\n  magnitude sets the value.\n- `look` (3D only): a list of `{kind: \"mouse\", sensitivity?, invertY?}`,\n  `{kind: \"gamepadStick\", xAxis?, yAxis?, deadZone?, speed?, invertY?}` and\n  `{kind: \"touchDrag\", sensitivity?, invertY?}`.\n\nA listed action or axis uses exactly the listed bindings, and an empty list\nunbinds it. An unlisted one keeps the defaults generated from its name. In 2D,\narrows and WASD are `left`\u002F`right`\u002F`up`\u002F`down` and Space is `space`, by key\nposition. Any other action is a `keyValue` binding of its name, so it follows\nthe player's keyboard layout. In 3D, `fire` is the left mouse button and F,\n`jump` is Space, `respawn` is R, and `moveX`\u002F`moveZ` read WASD and arrows.\nDefaults add the gamepad d-pad and left stick for directions and movement,\nbutton 0 for `jump`\u002F`space`, the right trigger for `fire`, the right stick for\nlook, and touch buttons for every other action. Binding an undeclared action or\naxis is a validation error.\n\nBindings only turn physical input into input frames. They never enter the\nsimulation, so playtests, replays and snapshots use the same `pressed`,\n`justPressed`, `axes` and `look` frames. Gamepads are polled once per tick. The\n3D players request mouse capture on click only when mouse look is bound.\nMouse actions work on the acquisition click and when capture is unavailable or\nrejected. Explicit `look: []` disables capture. HUD buttons own their presses\nbefore gameplay mouse or touch controls, and letterbox bars do not hit the HUD.\nOn a touch screen, editor play, public players and both standalone players\nshow a stick when an action or axis has a touch stick\nbinding and one button per action with a touch button binding. The 3D player\nalso turns the camera by dragging on the right half. Input pressed while play\nis paused is dropped. Player-facing rebinding is not stored yet.\n\n## HUD widget tree\n\n`ui` on the document holds HUD widgets drawn in every scene. `ui` on a scene\nholds widgets drawn above them in that scene. Set either with\n`set_ui {scene_id?, ui}`, or `ui: null` to remove it. The op replaces the whole\ntree: `{safeArea?, focusNavigation?, nodes: [node]}`. 2D and 3D documents\ntake the same tree and draw it with the same layout.\n\nEvery node has `kind`, `id`, and optionally `parent`, `anchor`, `pivot`,\n`offset`, `width`, `height`, `visible` and `opacity`. `anchor` is a point\n`{x, y}` in 0..1 of the parent's rectangle, or of the HUD rectangle for a root\nnode, so `{x: 1, y: 0}` is the top-right corner. `pivot` is the node's own point\nplaced there and defaults to `anchor`. `offset` moves the node in HUD pixels.\nThe 2D HUD rectangle is the camera view at `pixelsPerUnit` pixels per unit, so\na 16 by 9 camera at 32 pixels per unit gives 512 by 288. The 3D HUD rectangle\nis `presentation.hudWidth` by `hudHeight`, stretched over the viewport. The\nexported 3D player also renders the scene at `hudWidth` by\n`hudWidth \u002F aspectRatio`, so `hudWidth` sets the render resolution and the cost\nof every full-screen pass. A HUD value that changes repaints the whole HUD and\nuploads it as a texture. With\n`safeArea` (default true) root anchors stay inside the device's safe-area\ninsets.\n\n- `panel {color?, cornerRadius?}` fills its rectangle and holds children placed\n  by their anchors.\n- `image {assetId}` draws an image asset. 3D documents bind no images, so a 3D\n  image node fails validation.\n- `text {text, size?, color?, align?, fontId?}` sizes itself from its text\n  unless it has a width.\n- `bar {source?, value?, max?, color?, background?, direction?}` fills\n  `value \u002F max` (max defaults to 1). `source: {kind: \"health\", entityId}` reads\n  the entity's health and its health behavior's maximum.\n- `button {action, text?, background?, size?, color?, fontId?}` presses a\n  declared input action while a mouse button or a finger holds it.\n- `stack {direction?, gap?, padding?, align?}` lays children in a column or a\n  row and sizes itself from them.\n- `grid {columns, gap?, padding?}` lays children in cells as large as the\n  largest child.\n\nA child must come after its parent, and only panels, stacks and grids hold\nchildren. Panels, images, bars and buttons need `width` and `height`. Ids are\nunique across the document tree and each scene tree.\n\nScripts change nodes with `{kind: \"ui\", id, text?, value?, max?, visible?}`.\n`text` applies to text and buttons, `value` and `max` to bars, and `visible`\nto any node. A change lasts until the scene changes and is saved in snapshots.\nA command for a missing node, or a field the node does not have, fails the\nstep. An `onDestroy` hook may return `ui` commands.\n\nA button press reaches scripts as its action in `pressed` and `justPressed`,\nexactly like a key, so replays and snapshots need nothing extra. In 3D a click\non a button reaches the HUD only while the mouse is not captured. With\n`focusNavigation: true`, the gamepad D-pad moves focus between visible buttons\nand button 0 presses the focused one. While the tree shows a button, those\ngamepad buttons drive the HUD instead of the game's bindings. Existing `hud`\nlabels still draw, above the tree.\n\n## Distance culling and frame budgets (3D)\n\nBoth are presentation only. They never change simulation, snapshots or replay.\n\n- `set_performance {performance}` replaces document `performance`. `null`\n  removes it. `cullLayers` maps a layer name to `{maxDistance}` (at most 32\n  layers). `budgets` sets `drawCalls`, `triangles`, `particles` and `voices`,\n  and the time budgets `tickMs` (tick p95, default 4) and `frameMs` (frame p95,\n  default 16.6).\n- `update_entity` with `set: {renderCulling: {layer?, maxDistance?}}` hides\n  that entity and its children when the game camera is farther than the\n  distance from the entity's origin. The nearest setting in the parent chain\n  wins, and an entity's own `maxDistance` wins over its layer's. A layer must\n  be declared, or validation reports `missing_cull_layer`.\n- Distance is measured to the entity origin, not to its nearest surface. Cull\n  props and small decoration. Do not cull large scenery such as terrain or\n  buildings, because they disappear while the camera is still close to their\n  edges. Never cull the player or anything the player must see to win. The\n  editor camera shows every entity.\n- Budgets left out use the player defaults: 1000 draw calls, 1,000,000\n  triangles, 4096 particles and 24 voices. The standalone player warns in the\n  browser console once each time a budget is exceeded. The standalone players\n  and 3D capture count live particles. The 2D player uses the default particle\n  budget because 2D documents have no `performance`.\n- `nodetool game bench`, `nodetool game capture` and `nodetool game perf` check\n  these budgets and exit 1 on an overrun with `--fail-on-budget`. Load\n  `native-game-performance` for the targets, the budgets per game type and the\n  measure-fix loop.\n\n## Editor panels the user can open\n\nPoint the user to these when they work in the game editor alongside you.\n\n- **Run 10 s** diagnoses the draft in an independent session and reports the\n  first failing tick and script time by entity. **Replay to tick** rewinds the\n  active play session after a script error. **Ask the assistant** passes the\n  script context to the game assistant.\n- Editing an existing script source during play recompiles it without resetting\n  the tick or script state. A compilation failure keeps the previous source.\n  Changing script form between a function and lifecycle object requires\n  **Restart from tick 0**. Other draft changes also require a restart.\n- The **Console** panel lists play-session errors, script errors with their\n  tick, reload compilation errors, script log lines, **Run 10 s** failures and\n  validation errors.\n- The **Assets** panel shows the `browse_native_game_assets` catalog with\n  thumbnails. **Generate** and **Regenerate** run `generate_game_asset` for\n  image, speech and music slots. **Use** binds an older candidate and moves the\n  slot's frame and tile bindings onto it.\n- Ctrl+K (Cmd+K on macOS) opens the command palette, where **Edit keyboard\n  shortcuts** rebinds editor shortcuts.\n- When the script pane reports a draft conflict, local typing stays unsaved\n  until the user picks **Keep my version** or **Use draft version**.\n- Restoring a revision asks for confirmation because it replaces the draft and\n  clears its undo history. The newest 100 published revisions are retained.\n\n\n## 3D forces and torques\n\nReturn `{kind: \"addForce\", force: {x, y, z}}` to accelerate the issuing\nentity's dynamic body, `{kind: \"addTorque\", torque: {x, y, z}}` to turn it,\nor `{kind: \"addForceAtPoint\", force: {x, y, z}, point: {x, y, z}}` to apply\na force at a world-space point. An offset point also produces torque. All\nvectors use world space, even when the body is rotated. Each component must\nbe finite and between -1000000 and 1000000.\n\nLoads accumulate within the current simulation tick and clear after its\nphysics step. Return the commands on subsequent ticks to keep applying them.\nUse `impulse` for an immediate velocity change. Forces require the issuing\nentity's active dynamic body. Static, kinematic, missing and inactive bodies\nreject them. A nonzero load wakes the body, while a zero load keeps a sleeping\nbody asleep. These commands are available only in 3D scripts.\n","# Native game field reference\n\nThe fields and runtime rules behind `native-game`. Load `native-game` first for\nthe build, playtest and asset loop. The `edit_native_game` op description also\nlists the op shapes for prefabs, the mixer, script params, input bindings, the\nHUD tree, animation graphs, performance, spatial audio and collision layers.\nSections here say what the fields do and how they interact.\n\n## Schema versions\n\nNew 2D games start at `schemaVersion: 4` with `engineVersion: \"3\"`. Older 2D\ndocuments use schema 1 or 2 with engine `\"1\"`. Fonts, prepared assets, scene\nmusic, lighting, backgrounds, gravity, `visualAnimation` and a render effect\nchain need schema 2 or later. Tags, props and script params need schema 4.\nValidation rejects these fields on an earlier schema. 2D `schemaVersion: 5` with\n`engineVersion: \"4\"` is reserved for Rapier 2D physics, which this runtime does\nnot provide: validation and sessions refuse it with `engine_unavailable`. Do not\nauthor schema 5. A schema must use its own engine version. 3D games use\n`schemaVersion: 3` with `engineVersion: \"2\"`.\n\n## Entity tags and properties\n\nBoth dimensions support optional `tags` and `props` on an entity. Use\n`update_entity` with `set: {tags: [\"hero\"], props: {health: 10}}`. Each field\nreplaces its complete value. A top-level `null` removes the stored field.\nNested `null` is JSON data. Tags are unique, with at most 64 strings of\n1–128 characters. Properties hold finite JSON, with at most 64 top-level keys,\n16 levels of nesting and 64 KiB of UTF-8 JSON. Keys contain 1–128 characters.\n`__proto__`, `constructor` and `prototype` are rejected at every nesting level.\n\nScripts read `entity.tags`, `entity.props`, `entity.active` and\n`entity.rotation`, and the same fields on existing `world` entries and\n`world.get(id)` results. `world.query({tag})` returns the ids carrying a tag.\nRotation is radians in 2D and the committed world quaternion `[x, y, z, w]` in 3D.\nUse `setProp {key, value}` and `removeProp {key}` to change the caller's\nruntime properties. Setting `null` keeps that key. Removal deletes it. The\nruntime checks every property command in a tick before it changes any state,\nand an error names the entity and tick. The props of each entity that runs a\nscript count once toward the 64 KiB script input limit. Other entities' props\ntravel with the world only when a script reads `input.world`. Runtime properties survive snapshot restore and\nremain independent of the authored document and other prefab instances.\nMutating an input object does not change runtime state.\n\n## 2D scripts\n\nA `script` behavior is a function expression, or an object of lifecycle hooks\n(see Lifecycle hooks and timers). A function receives `{tick, pressed,\njustPressed, events, entity, world, state, random}` and returns `{state,\ncommands}`. `events` are the previous tick's events. `world` lists every active\nentity with a collider or camera as `{id, source, x, y}`. `entity` holds the\ncaller's `id`, `source`, position, velocity, and `touching {down, up, left,\nright}`, which names the sides of its collider that rest against a solid at the\nstart of the tick. A spawned instance has the id `\u003Cprefab>#\u003Cn>`, its `source`\nis the prefab id, and it runs the prefab's script with its own state.\n\nFunctions, lifecycle hooks, and timer methods must be synchronous. Async functions\nand generators fail preparation. Returning a promise, thenable, or iterator fails\nthe call. Use tick timers for delayed work.\n\nStore persistent values in returned `state`. Nothing else carries over between\ncalls. A plain function expression such as `(input) => { … }` runs in strict\nmode in a reused sandbox whose built-ins and global object are frozen, which\nmakes each call several times cheaper. Assigning to a global or a built-in\nthere throws, and the engine then repeats the call in a fresh context and keeps\nthat source in fresh contexts. Other sources, such as a function returned by an\ninitializer call, evaluate in a fresh context each call. Prefer plain function\nexpressions and keep helpers and temporary values in local variables.\nSource initialization uses the seeded random generator. `maxTickMs` limits each\ncall, subject to the total script budget for the tick. Source and input\u002Foutput\nlimits count UTF-8 bytes.\n\nDuring a function call, global `world.get(id)` returns an entity or `undefined`,\nand `world.query({source?, tag?, near?, radius?, limit?})` returns matching IDs\nin runtime entity order. Both read the same immutable tick-start snapshot,\nbefore movement behaviors or script commands run. Returned values are private\ncopies. Queries cover every active entity. Legacy `input.world` keeps its\nexisting shape and population, including only collider or camera entities in 2D.\nThe engine serializes `input.world` only when a script reads it, with a 1 MiB\nlimit, so prefer `world.get` and `world.query` with `near` and `radius`.\nThe 2D query record is `{id, source, x, y, velocityX, velocityY, grounded}`.\nThe 3D record is `{id, source, position, velocity, grounded}`, with XYZ vectors.\n3D records and schema 4 2D records add `tags`, `props`, `rotation` and `active`.\nA `tag` filter matches only entities that carry the tag, so it matches nothing\non schema 1 or 2. Pass `near: {x, y, z?}` and `radius` together. The radius is\nnonnegative and includes entities on its boundary, measured between centers,\nwith omitted `z` treated as zero. `source` matches the record's original entity\nID, not a 3D prefab asset ID. `limit` is an integer from 0 to 1024, defaulting\nto 1024. A call allows 64 combined `get`\u002F`query` attempts. Exceeding the count\nfails the call even if guest code catches it. Query arguments allow 4096 JSON\ncharacters, `get` IDs 1024 characters, and responses 64 KiB. These APIs are\nunavailable during source initialization.\n\nThe commands are `setVelocity`, `setPosition`, `setVisual {tint?, opacity?,\nrotation?, scaleX?, scaleY?, flipX?}`, `playAnimation {clip}`, `spawn {prefabId,\nx?, y?, velocityX?, velocityY?}`, `despawn`, `emit`, `sceneTransition`, and\n`hud {id, text, x, y, size?, color?, align?}`. HUD coordinates are canvas\npixels. Empty text removes a label. A label with the id `score` or `win`\nreplaces the built-in label. Set `fontId` to a logical font binding for a HUD\nlabel. Font assets use `mediaKind: \"font\"`, `fontFormat: \"ttf\" | \"otf\"`, and\n`required: true` by default. Required fonts load before the first frame or fail\nplay and capture. An optional font (`required: false`) reports a fallback before\nplay. Export embeds the font bytes for offline use.\n\n`log {message, level?}` writes a line to the editor console in 2D and 3D.\n`level` is `\"log\"` (the default), `\"warning\"` or `\"error\"`. A log line never\nchanges the simulation, snapshots or replay, and it does not count toward\n`maxCommands`. A tick keeps 32 lines and reports the rest as one dropped-lines\nwarning. Messages longer than 1000 characters are truncated. Log text still\ncounts toward the 64 KiB output limit of a script call. Lines logged before a\nscript failure appear in the console ahead of the error.\n\n### Script time limits\n\n`maxTickMs` accepts 1 through 20 ms, defaults to 8 ms, and cannot raise the\nhost's 20 ms ceiling. It counts the CPU time of the thread running the script, from just\nbefore the call (or before compiling its source) until its output is checked.\nWaiting for a processor does not count, and on Node neither do garbage collection\npauses during the call. A call ends only when both its CPU time\nand its wall time reach the limit. Browsers have no thread CPU clock, so there\nthe limit counts wall time. The 50 ms script budget for a whole tick and the\n100 ms limit for evaluating a source during preparation count wall time on\nevery host. A session's first scripted tick has 1,000 ms instead of 50 ms,\nbecause it runs each script for the first time. A script that loops forever still ends at its limit.\n\nWhat an overrun does depends on the host. The editor's play mode and the\nstandalone players skip the call: the entity keeps its script state, issues no\ncommands that tick, and the editor console shows a warning naming the call. A tick\npast its 50 ms budget skips its remaining calls the same way. Headless runs,\nreplays, the CLI and agent playtests stop with a `Game script interrupted`\nerror, so a slow script fails verification. A skipped call depends on timing,\nso a play session that skipped one may not replay identically.\n\n### Script parameters\n\nDeclare a script's tunables as `params` on its script behavior instead of\nconstants in the source. The inspector edits them and scripts read the values\non `input.params`. Set them with `set_script_params {entity_id, scene_id?,\nindex, params?, values?}`. `params` replaces the declarations and `null`\nremoves them with their values. `values` merges stored values, and a `null`\nvalue returns one to its default. 2D games need schema 4. 3D games accept\nparams on schema 3.\n\n```json\n{\"op\": \"set_script_params\", \"entity_id\": \"player\", \"index\": 0,\n \"params\": {\"speed\": {\"type\": \"number\", \"default\": 3, \"minimum\": 0, \"maximum\": 10},\n            \"target\": {\"type\": \"entity\"}, \"hit\": {\"type\": \"asset\", \"kind\": \"audio\"}},\n \"values\": {\"speed\": 5, \"target\": \"goal\"}}\n```\n\nTypes are `number` (`minimum`, `maximum`, `integer`), `boolean`, `color`\n(`#rrggbb`), `enum` (`options`, default the first option), `entity`, `asset`\n(optional `kind`) and `vector` (`dimensions` 2 or 3). A behavior declares at\nmost 32 params with identifier names. `input.params` holds the stored value,\nelse the default. An `entity` or `asset` param without either is `null`.\nValidation rejects an entity reference outside the behavior's scene, a missing\nasset slot, an asset of the wrong `kind`, and in 3D an entity reference inside\na prefab or an undeclared prefab asset. A behavior without `params` has no\n`input.params` key. Params count once per behavior definition toward the\n64 KiB script input limit.\n\n### Lifecycle hooks and timers\n\nA script source may be an object of hooks instead of a function. It works in\n2D and 3D with no schema change.\n\n```js\n({\n  onStart(input) { every(30, \"fire\"); return { state: { shots: 0 } }; },\n  fire(input) { return { state: { shots: input.state.shots + 1 },\n    commands: [{ kind: \"spawn\", prefabId: \"bolt\" }] }; },\n  onTriggerEnter(input, contact) { return { commands: [{ kind: \"emit\", event: \"hit:\" + contact.otherId }] }; },\n  onDestroy(input) { return { commands: [{ kind: \"emit\", event: \"gone\" }] }; }\n})\n```\n\nHooks receive the same `input` as a function script and return\n`{state?, commands?}` or nothing. Each hook sees the state returned by the\none before it. Within a tick they run in this order:\n\n1. `onStart`, on the behavior's first call.\n2. `onSceneEnter`, on the first tick of the scene.\n3. `onTriggerEnter`, `onTriggerExit` and `onContact`, once per contact of\n   the entity in the previous tick's events, in event order. A contact where\n   either collider is a sensor is a trigger. Triggers fire on `enter` and\n   `exit`. `onContact` receives every phase of a solid contact. The second\n   argument is `{otherId, phase, sensor}`.\n4. `onEvent`, once per trigger event of the previous tick that reaches the\n   entity, in event order. See Typed events.\n5. Due timers, in the order they were scheduled.\n6. `onFixedUpdate` or `onUpdate`. They are aliases. Define one of them.\n\n`onDestroy` runs alone in the tick after the entity despawns. It may return\nonly `hud`, `emit`, `spawn`, `despawn`, `sceneTransition`, `log`, `save` and `load` commands. A scene\ntransition discards behavior state without calling `onDestroy`.\n\n`after(ticks, name)` calls the object's method `name` once, `ticks` ticks\nlater. `every(ticks, name)` calls it every `ticks` ticks. `ticks` is an\ninteger from 1 to 1000000, `name` must be a method that is not a hook, and a\nbehavior holds at most 32 timers. Scheduling an existing name replaces that\ntimer. Timers live in the behavior's snapshot state, so they replay from any\nsnapshot. Session preparation rejects an unknown `on*` key, a hook that is not\na function, and an object that defines both `onUpdate` and `onFixedUpdate`.\nLifecycle objects run in a fresh context each call.\n\n### Typed events\n\n`emit` takes an optional `payload` and `target` in 2D and 3D, with no schema\nchange. The payload is any JSON value up to 1 KiB serialized and 16 levels\ndeep. The target is `{entityId}` for one entity or `{tag}` for every entity\nwith that tag. Omit `target` to broadcast.\n\n```js\n\u002F\u002F Sender\n({ onTriggerEnter(input, contact) { return { commands: [{ kind: \"emit\", event: \"alarm\",\n  payload: { by: contact.otherId, level: 2 }, target: { tag: \"guard\" } }] }; } })\n\u002F\u002F Receiver, on an entity tagged \"guard\"\n({ onEvent(input, event) { if (event.event === \"alarm\") { return { state: { level: event.payload.level } }; } } })\n```\n\nThe event lands in the stream as `{kind: \"trigger\", event, entityId,\npayload?, target?}`, where `entityId` is the sender, and reaches scripts on\nthe next tick. `onEvent(input, event)` receives that object for each trigger\nevent that reaches its entity, broadcasts included, and the sender receives\nits own broadcasts. Built-in `trigger` behaviors emit untargeted events, so\n`onEvent` sees them too. The target also limits built-in listeners: a `spawn`\nor `sceneTransition` behavior with `onEvent` and an `audioSource` with\n`onEvent` react only to events that reach their entity. `input.events` still\nholds every event of the previous tick, targeted or not. A tag target reaches\nnothing on 2D schemas before 4. A plain `{kind: \"emit\", event}` produces the\nsame event as before. Payloads count toward the 64 KiB script input limit once\nper tick.\n\n### Player saves\n\nScripts save and load in 2D and 3D with two commands:\n\n- `save {slot, data, scope?, snapshot?}` writes `data` to a slot after the tick.\n- `load {slot, scope?}` reads a slot.\n\n`scope` is `\"progress\"` (the default) or `\"settings\"`. The two scopes are\nseparate stores, so a progress slot and a settings slot can share a name and\nremoving one never touches the other. `snapshot: true` also stores the\nend-of-tick session snapshot with a progress save. A settings save with a\nsnapshot fails the tick. The player keeps one snapshot save pending at a time,\nand the editor stores the data without the snapshot.\n\nThe player runs saves and loads in order, with at most 8 waiting. A request\npast that limit fails without stopping the game, and a failed load answers\nwith null data.\n\nA slot name is 1 to 32 letters, digits, `_` or `-`. `data` must be a JSON\nobject of at most 16 KiB serialized, 256 keys counted at every depth and 16\nlevels deep. Each scope holds at most 16 slots per game. A tick accepts at most\n4 save and load commands across all scripts, and more fail the tick. These\ncommands do not count toward `maxCommands` and never enter snapshots.\n\nA load answers on a later tick through `input.loaded`, a list of `{scope, slot,\ndata}` where `data` is null for an empty slot. A frame carries at most 2\nresults. Keep what the game needs in script state:\n\n```js\n({ onStart() { return { commands: [{ kind: \"load\", slot: \"main\" }] }; },\n  onUpdate(input) {\n    const result = input.loaded?.find((entry) => entry.slot === \"main\");\n    return { state: result ? { best: result.data?.best ?? 0 } : input.state };\n  } })\n```\n\nThe loaded data is part of the input frame, so a replay of recorded frames\nreproduces it without storage. The standalone player stores saves in IndexedDB.\nThe editor stores them in localStorage under the game id. `nodetool game\nsimulate` and agent playtests keep them in memory. `simulate --saves \u003Cfile>`\nseeds slots from `{progress?, settings?}`, and its JSON report lists the slots\nafter the run.\n\n## 2D visuals\n\nFor generated art in a workflow, pass one style entity with a reference image.\nThe guided graph sends that image through an image-to-image generation input\nfor every visual slot. `StageGameAssets.preparation` accepts per-slot alpha\ntrimming, target size with a crop policy, pivot, nearest or linear sampling,\nand mirror tiling for single images. Trimming records crop offsets so placement\nkeeps its anchor. `StageGameAssets.fonts` stages TrueType or OpenType assets by\nlogical font ID.\n\n1. Use `sprite.blend: \"additive\"` for light drawn on black, such as glows and\n   sparks. Set `sampling: \"linear\"` on painted or soft assets.\n2. Use `lifetime {ticks, fade, endScale}` for short-lived entities. A kinematic\n   body without a collider moves but never collides, which keeps them cheap.\n   Prefer the `particles` component for sparks, smoke and dust.\n3. Use `animator` frames on a sprite sheet. Playback starts when the entity\n   spawns. Named `animator.clips` hold other frame sets. `playAnimation` starts\n   a clip from its first frame, and a repeated request for the current clip\n   continues it. Set `sprite.flipX` or the `setVisual` flag to mirror art, or\n   `sprite.faceMotion: \"left\" | \"right\"` to turn art that faces that way toward\n   the body's horizontal motion.\n4. Give `audioSource.onEvent` an event kind or the name of an emitted trigger.\n   Set `audioSource.volume` from `0` to `1` for effect loudness. Effects receive\n   distinct voice ids, so repeated triggers can overlap within the 32-voice cap.\n5. Make hazards sensors. Only a non-sensor body collects a `collectible`.\n\nSet `scene.music` to an installed audio `assetId` for looping music. `volume`\nranges from `0` to `1`, and `fadeInTicks` and `fadeOutTicks` range from `0` to\n`600`. Music starts at the scene's entry tick. Pause suspends audio, reset\nrestarts the entry scene once, and loading resumes the loop at the saved\nlogical tick. Set `music.streaming: {loopStartSeconds, loopEndSeconds?}` for\nlong tracks in 2D or 3D. This streams through the music bus without decoding\nthe whole asset. Start defaults to 0 seconds, end defaults to the track's end,\nand an explicit end must exceed start. Both are bounded by 86400 seconds.\nThe intro plays once. Custom loop boundaries follow browser media events.\nExisting fades crossfade outgoing and incoming scene tracks. Use `update_scene`\nto replace the whole `music` object. Omit `streaming` to retain decoded\nplayback. Browser playback starts after a user gesture. A missing or\nunplayable audio asset is reported in the player.\n\n`visualAnimation` on a sprite animates `rotation`, `scaleX`, `scaleY`,\n`opacity`, or `tint` from `from` to `to` over `durationTicks` in its `tracks`.\n`delayTicks`, `repeat`, `pingPong`, and `easing` control timing. `rotationRate`\nadds radians per tick for continuous spins and cannot share a rotation track.\nEach property has at most one track. Tracks start at scene entry or spawn time.\nThe visual order is authored transform and sprite values, tracks, script\n`setVisual`, then lifetime fade and scale. Tracks do not change collision.\n\nScene `backgrounds` define image layers independent of entities. Each layer\nsets `assetId`, world-unit `width` and `height`, `origin` at the tile center,\n`layer`, per-axis `parallax`, `scrollRate` in world units per second, and `mode`\n(`none`, `repeat`, `repeatX`, or `mirror`). `repeatX` tiles only sideways, which\nsuits a strip of scenery. Parallax `0` stays fixed to the screen and `1`\nstays fixed in the world. Optional `frame` selects an atlas rectangle.\n\nScene `lighting` holds an `ambient` color and intensity plus up to 32 `points`\nwith world position, color, intensity, radius, and falloff. Lighting multiplies\nworld color before effects. HUD stays unlit. Set `sprite.unlit: true` for\nemissive art such as glows. Canvas2D capture paints these sprites above the lit\nworld. An entity `light2d {color, intensity, radius, falloff, offset?}` moves\nwith its entity. Each frame adds the entity lights nearest the camera to the\nscene's fixed points, up to 32 lights in total.\n\n`renderEffects` is an ordered chain of at most eight effects.\n`brightnessContrast` uses `brightness` from `-1` to `1` and `contrast` from `0`\nto `4`. `bloom` uses `threshold` from `0` to `1`, `softness` from `0` to `0.5`,\n`radius` from `0` to `64` pixels, and `intensity` from `0` to `4`. `lut` refers\nto an opaque packed 2D cube image with dimensions `size² × size` and an\noptional intensity and color domain. Effects run in listed order. Set\n`hudEffectOrder` to `afterEffects` to keep text crisp, or `beforeEffects` to\nprocess HUD with the world. A bloom chain defaults to `afterEffects`.\n`required: true` makes unsupported GPU effects fail before play or capture.\nOptional effects are omitted with a diagnostic on Canvas 2D.\n\nCollider `category` and `mask` are unsigned 32-bit bitsets in 2D. They default\nto `1` and `4294967295`. A pair interacts only when each mask includes a\ncategory bit from the other collider. Give bullets masks that exclude other\nbullets when bullet-to-bullet interaction is unnecessary.\n\nContacts report `phase: \"enter\" | \"stay\" | \"exit\"` and `normalX`\u002F`normalY`.\nCheck both `entityId` and `otherId`, since each pair emits one contact per tick.\nBuilt-in triggers fire on entry. Use `stay` contacts for repeated overlap logic.\nSwept collisions detect fast movement through thin static walls and sensors.\nScene entry starts authored entities' animation and lifetime clocks, and saves\npreserve those clocks and active contact pairs.\n\n## Particles\n\nThe particle inspector offers Fire, Smoke, Sparks, Dust, Magic, Rain, Snow and\nExplosion presets in both dimensions. Applying one replaces that entity's\n`particles` component and preserves its other components. Select an emitter\nto edit its fields, lifetime curves and color gradient. Renaming an emitter\nrepairs its death-emitter references. Removing one removes references to it.\nThe isolated preview uses a fixed seed and restarts when edited. Pause and\nRestart affect the preview only. The 2D preview uses Canvas2D, including sprite\nsheets and blending. The 3D preview uses WebGL2, including stretched and mesh\nparticles and depth softness. It does not run gameplay scripts or change the\nplay session.\n\nAgents and CLI document edits use the same `update_entity` operation as the\ninspector. Set the complete component with\n`{op: \"update_entity\", entity_id: \"effect\", set: {particles: {emitters: [...]}}}`.\nThere is no preset-name operation. Node.js authoring can also import\n`getGameParticlePreset(name, dimension)` from `@nodetool-ai\u002Fprotocol` to get\nfresh canonical data. In a code action, use the JSON recipes below with the\nexisting game edit API instead of importing that host package.\n\n### Particle preset recipes\n\nEach payload is a complete `particles` component. Omitted fields take schema\ndefaults. These canonical recipes use 3D draw settings. For a 2D entity, remove\n`render` from each emitter and set `shape.size.z` to `0` for box shapes.\nKeep other values unchanged. Both dimensions use world units and +Y upward.\nPlace rain and snow emitters above the area they cover. Explosion is a finite\nburst, which can be restarted with `emitParticles` without a count.\n\n#### fire\n\n```json\n{\"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}]}\n```\n\n#### smoke\n\n```json\n{\"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}]}\n```\n\n#### sparks\n\n```json\n{\"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}}]}\n```\n\n#### dust\n\n```json\n{\"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}]}\n```\n\n#### magic\n\n```json\n{\"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}]}\n```\n\n#### rain\n\n```json\n{\"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}}]}\n```\n\n#### snow\n\n```json\n{\"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}]}\n```\n\n#### explosion\n\n```json\n{\"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}]}\n```\n\n### Particle component fields\n\nAdd particle effects with the `particles` component on any 2D (schema 2 or\nlater) or 3D entity. It holds up to 8 `emitters`, each with a unique `id`. An\nemitter sets `rate` (particles per second), `bursts` (`time`, `count`,\n`cycles`, `interval`), `duration`, `loop`, `playOnStart`, `maxParticles` and a\n`shape`: `point`, `circle`, `sphere`, `cone`, `box` or `edge`. Cone, box and\nedge emit along local +Y. Point, circle and sphere emit outward. Per-particle\nvalues `lifetime`, `speed`, `size`, `rotation` and `angularVelocity` take a\nnumber or `{ min, max }`. `color` takes `#rrggbb` or `{ min, max }`. The\n`sizeOverLifetime`, `speedOverLifetime` and `opacityOverLifetime` curves are\nlists of `{ t, value }` keys sorted by `t` from 0 to 1. `colorOverLifetime` uses\n`{ t, color }` keys. Add `gravity` (`{ x, y, z }` in units per second squared)\nand `drag`. `space: \"local\"` makes particles follow the entity.\n`onDeath: [{ emitter, count }]` fires another emitter of the same component\nwhere each particle dies. Give that emitter `playOnStart: false` and `rate: 0`.\n\nScripts trigger emitters with `{ kind: \"emitParticles\", emitter?, count? }` on\ntheir own entity. Without `count`, the emitter restarts its cycle, which suits\na one-shot explosion with `loop: false`. With `count`, that many particles\nspawn at once. Omitting `emitter` selects the first emitter in the list. The\ncommand is presentation only and never appears in gameplay events or\nsnapshots. Particles cannot affect scores, physics or scripts. Prefer them over\nspawned prefabs for sparks, smoke and dust.\n\nEach emitter also sets how its particles draw in 2D. Without `sprite`, a\nparticle is a soft round dot tinted by its colour. `sprite: { assetId, columns,\nrows, frameCount?, cycles?, sampling? }` uses an image asset instead. A sheet is\ncut into `columns` by `rows` cells read left to right and top to bottom, and the\nfirst `frameCount` cells play `cycles` times over each particle's life.\n`sampling` defaults to `nearest`. `blend: \"additive\"` adds light, which suits\nfire, sparks and magic. The default `normal` alpha-blends, which suits smoke and\ndust. Particles take scene lighting like sprites. Set `unlit: true` so glowing\nparticles keep their colour in the dark. `layer` sets the draw layer. It\ndefaults to the entity's sprite layer, or 0 without a sprite, and particles\ndraw above sprites of the same layer. `size` is the particle's width in world\nunits. `rotation` and `angularVelocity` are radians and radians per second.\n\nThe standalone player, the editor's play view and `capture_native_game_frame`\ndraw 2D particles. A capture simulates particles for every tick up to the\ncaptured one, so a burst at tick 0 is still visible a few ticks later. WebGPU\ndraws every live particle. The Canvas2D fallback draws at most 1024 visible\nparticles. Each emitter's random stream is seeded from its entity and emitter\nids, so captures repeat.\n\nIn 3D, `render` sets how each particle draws. The default `{ kind:\n\"billboard\" }` is a soft round dot facing the camera, `size` units across and\nturned by `rotation`. `{ kind: \"stretched\", length?, speedScale? }` lies along\nthe particle's direction of travel on screen. It is `size` wide and `size ×\nlength + speed × speedScale` long, which suits sparks, rain and streaks.\n`length` defaults to 1 and `speedScale` to 0.1 seconds. `{ kind: \"mesh\",\nshape, align? }` draws a small solid `box`, `sphere`, `tetrahedron` or\n`octahedron`, `size` units across, for debris and confetti. With `align:\n\"world\"`, the default, it turns by `rotation` about world +Y. With `align:\n\"velocity\"` its +Y points along its velocity. Mesh particles take scene\nlighting unless `unlit: true`. Billboards always keep their own colour. Both\ntake scene fog and post-processing such as bloom, so additive particles glow.\n`softness` (world units) fades a billboard out where it nears the geometry\nbehind it, so smoke and glows do not cut a hard line through the floor. The\nrenderer then draws the scene's depth once more per frame, so set it only on\nemitters that touch geometry. That depth pass skips transparent materials,\nkeeps alpha-tested cut-outs, and counts toward the draw call and\ntriangle budgets. 3D particles ignore `sprite` and `layer`, and\n2D particles ignore `render` and `softness`. Validation warns about `render` or\n`softness` on a 2D emitter, `softness` on a mesh emitter, and `sprite` on a\n3D emitter, without failing. Editor Play, the 3D standalone player,\n`nodetool game capture` and `capture_native_game_frame` draw 3D particles.\nTranslucent particles sort across render types without writing depth. Interleaved\ntypes can increase draw calls. A scene transition clears the particle field.\nWithin a scene, detached world particles retain world-space death chains until\nthey finish. Storage grows lazily within the scene particle limit.\n\n## 3D collision layers\n\n`collisionLayers` names up to 16 layers, and the layer at index `i` owns bit\n`i`. `collisionMatrix` lists the layer pairs that do not collide, in either\norder. A layer paired with itself stops its colliders from touching each other.\nEvery unlisted pair collides. Set both with `set_game {collision_layers,\ncollision_matrix}`, and put a collider on a layer with `update_entity set\ncollider3d {layer}`. A layered collider derives `category` from its bit and\n`mask` from the matrix, so leave its raw bits at `1` and `65535`. Validation\nrejects an unknown layer name, a pair listed twice in the matrix and\nconflicting layer bits. Raw `category` and `mask` remain an override for\ncolliders without a layer. A collider without a layer keeps the default\ncategory `1`, which is the bit of `collisionLayers[0]`. It therefore shares the\nfirst layer's pairs: a layer paired with `collisionLayers[0]` does not touch\nunlayered colliders either. Name the first layer for the world, or give such\ncolliders a layer. 2D colliders keep raw bits only.\n\n## 3D physics materials\n\n`physicsMaterials` names up to 64 surfaces that colliders share. Each material\nhas `friction` (0 to 4, default 0.5), `restitution` (0 to 1, default 0),\n`frictionCombine` and `restitutionCombine`. A combine rule is `average`\n(default), `min`, `multiply` or `max`. When two touching colliders use\ndifferent rules, the later rule in that list wins. Use `max` restitution for a\nbouncy ball and `min` friction for ice. Declare materials with `set_game\n{physics_materials: {name: material}}`, and remove them all with\n`physics_materials: null`. Point a collider at one with `update_entity set\ncollider3d {physicsMaterial: name}` and clear it with `physicsMaterial: null`.\nA collider with a material takes all four values from it, so leave its inline\n`friction` at 0.5 and `restitution` at 0 and set no inline combine rule.\nValidation rejects an unknown material name and conflicting inline values. A\ncollider without a material keeps its inline values and may set\n`frictionCombine` and `restitutionCombine` itself. Existing documents name no\nmaterial and simulate as before. 2D colliders have no friction or\nrestitution.\n\n## Audio mix and spatial audio\n\nSet the document mix with `set_audio {mixer}` in 2D and 3D. `set_audio\n{mixer: null}` removes it. Every mixer has the buses `master`, `music`, `sfx`,\n`voice` and `ui`. Add buses under `mixer.buses` with `parent` (default\n`master`), `volume` (linear, 1 is unity), `muted`, `lowpassHz` and\n`reverbSend`. A bus multiplies its parent's gain, so muting `sfx` silences its\nchildren. Scene music plays on `music` and other audio on `sfx` unless\n`mixer.assetBuses` maps the asset slot to another bus.\n\nThe master bus ends in a limiter at `limiter.thresholdDb` (default -1 dB) that\ndoes not change levels below the threshold. The default ducking rule lowers\n`music` to 0.35 while a voice plays on `voice` or a child of it. Replace\n`ducking` to change or remove that rule.\n\nA mixer snapshot under `mixer.snapshots` overrides named fields of named buses.\n`transitions` move to a snapshot over `fadeTicks` when the simulation emits a\n`trigger` event, a `win` event, or enters a scene. The snapshot `base` returns\nto the base mix. Restarting or restoring a session returns to the base mix or\nthe scene's snapshot without a fade. The mix is presentation state. It is not\nsaved in game snapshots and never changes playtest results or replay.\n\nSet `audioSource.spatial: true` to play an entity's effect at its position in\n2D and 3D. The listener is the active camera, so an emitter right of the camera\nplays in the right ear. Effects play at full volume within `minDistance`\n(default 1 world unit) and fall off by `distanceModel` (`linear`, `inverse`\nby default, or `exponential`) and `rolloff` (default 1) out to `maxDistance`\n(default 50), which must exceed `minDistance`. The `linear` model clamps\n`rolloff` to 1 and is silent at `maxDistance`. A 3D `cone {innerAngle,\nouterAngle, outerGain}` points along the entity's -Z axis. 2D emitters ignore\nthe cone. `doppler` (default 0, 1 is physical) shifts pitch with emitter and\ncamera motion. Set a field to `null` in `update_entity` to return it to its\ndefault. The event that starts the effect carries the emitter position, so a\ncollectible that despawns as it plays still sounds where it was. Spatial\nsettings never change simulation.\n\n## 3D animation graphs\n\nA 3D animation graph lives in the document's `animationGraphs` map. An\nentity's `animator3d.graph` names one, and its `animator3d.clips` maps the\ngraph's clip aliases to prepared clip IDs, so one graph can drive several rigs.\nStore a graph with `set_animation_graph {graph_id, graph}` and delete it with\n`remove_animation_graph {graph_id}`. An animator with a graph cannot also set\n`initialClip`.\n\nA graph declares `parameters` (`float` with a default, `bool` with a default,\nor `trigger`) and one to eight `layers`. Each layer has an `initialState`,\n`states` and `transitions`. A state plays a `motion`: one `clip`, a `blend1d`\nover a float parameter with points in increasing `value` order, or a `blend2d`\nover two float parameters. `speed` scales the animator's `playbackRate`, and\n`loop` defaults to true. The first layer is the unmasked override base. Later\nlayers can be `additive` and can set `weight` and a `mask` of prepared model\nnode IDs. A mask covers each listed node and its descendants.\n\nA transition names `from` (a state ID, or `*` for any state), `to`,\n`conditions`, optional `exitTicks` and `durationTicks` for the crossfade. Every\ncondition must hold. Float conditions use `gt`, `gte`, `lt`, `lte`, `eq` or\n`neq` with a `value`. Bool conditions use `true` or `false`. A trigger uses\n`set` and resets once a transition consumes it. `exitTicks` is how long the\nsource state must run before the transition may fire. Each transition needs a\ncondition or `exitTicks`. A crossfading layer does not start another\ntransition. Transitions are checked in document order.\n\nScripts set parameters with `setAnimParam {name, value}`. Pass `true` to fire\na trigger. `playAnimation {clip}` on a graph entity switches the base layer\ndirectly to the state with that ID, or else to the first state that plays that\nclip alias, using `animator3d.transitionTicks`. Example locomotion: a `speed`\nfloat, a `move` state with `blend1d` points idle at 0, walk at 2 and run at 6,\nand a script that sends `setAnimParam` with the character's planar speed.\n\nGraph state (current state, entry tick, crossfade source and parameter values)\nis simulation state, saved in snapshots. Check it with a playtest that sets\n`restore_at_tick` and asserts `replay_matches`. The renderer samples the\nresolved pose at the interpolated tick. Blended clips share one normalized\nphase. The pose sampler blends node translation, rotation and scale. It does\nnot blend morph targets.\n\nModel preparation derives `clipDurations` from verified GLB time accessors.\nBindings with this metadata retain a normalized simulation phase when blend\nparameters change, including across save and restore. Do not invent or edit\ndurations. For a legacy binding without them, regenerate a model candidate from\nits existing source with `generate_game_asset`, then explicitly install it with\n`install_native_game_asset`. Installation rederives the durations from its bytes.\nUntil then, blends keep legacy elapsed-time sampling and validation reports\n`legacy_animation_phase`.\n\n## 3D rendering\n\nSet every `environment` field with `update_scene`. It replaces the whole\n`environment`, so send the existing `background`, `ambient`, `fog`, `shadows`,\n`sky` and `postProcessing` with the changed field. Capture the scene and view\nthe frames to review a change.\n\n`environment.fog` accepts either linear `{ color, near, far }` or height\n`{ kind: \"height\", color, density, baseHeight, heightFalloff, maxOpacity }`.\nHeight fog replaces linear fog and integrates exponential density along the\ncamera ray. `density` (0.000001 to 10, default 0.02) is extinction per world\nunit at `baseHeight` (-10000 to 10000, default 0). `heightFalloff` (0.000001\nto 100, default 0.2) reduces density above that height. `maxOpacity` (0 to 1,\ndefault 1) limits coverage. Omit `fog` to disable it. Both camera projections,\nrigid and animated models, and 3D particles receive scene fog.\n\nAdd an image projection with `decal3d: { assetId, receiverId, size: { x, y, z },\nopacity, renderOrder }`. `assetId` names a verified PNG, JPEG or WebP image\nslot. Browse a staged image with `browse_native_game_assets`, then use its\nbinding with `install_native_game_asset`. `receiverId` names one existing\nnon-template primitive or rigid model entity in the same scene. Use a separate\nprojector entity whose `transform3d` positions and rotates the projection box.\nIts scale multiplies `size`. Parent the projector to the receiver to keep the\ndecal attached as it moves. Otherwise the projection volume stays in world\nspace. Each size axis is 0.001 to 1000 world units,\n`opacity` is 0 to 1 (default 1), and integer `renderOrder` is 0 to 31 (default\n0). Higher order draws later. Ties follow entity order.\n\nA scene accepts at most 32 authored decals. Projection follows moving rigid\nreceivers and selected visible model nodes, respects foreground depth, and\nsupports image transparency. Skinned, morphed or instanced receivers, and\ntransparent or alpha-masked receiver materials, report a diagnostic instead of\nprojecting. A receiver with `animator3d` is invalid. Projection also reports a\ndiagnostic when its bounded node, triangle or output work exceeds the renderer\nlimit. Decals affect presentation only and never add collision surfaces.\n\n`environment.shadows` is `{ enabled, mapSize, extent, cascades? }`. Without\n`cascades`, the one shadow-casting directional light draws a single map that\ncovers `extent` meters around the light. For scenes deeper than about 30 m, add\n`cascades: { count, split, maxDistance }`. It splits the camera view into\n`count` maps (2 to 4, default 3) up to `maxDistance` meters (default 200), so\nnear shadows stay sharp and far shadows stay continuous. `split` (0 to 1,\ndefault 0.5) gives nearby cascades more resolution as it rises. `extent` is\nignored while cascades are on. Only one directional light can cast shadows.\n\nEvery mesh with `castShadow` (true by default on primitives and models) draws\nagain into each cascade it reaches. The nearest cascade re-renders every frame,\nthe second every other frame, and the rest every fourth frame or when the view\nmoves past their drift limit. Use 2 or 3 cascades, set `maxDistance` to how far\nthe player can judge a shadow, and set `castShadow: false` on terrain and\nground, which still receive shadows. Validation warns with\n`large_shadow_caster` when a casting mesh 100 units or wider sits in a scene\nwith cascades. Each 2048 map holds 16 MiB of depth.\n\nPoint and spot lights cast shadows when their `light3d.castShadow` is `true`.\nAt most 4 of them may cast per scene. Validation rejects a fifth. If spawned\nprefabs push a frame over the budget, the extra lights render unshadowed and\nthe capture stats report one diagnostic. Each local shadow re-renders the scene\n(six times for a point light), so enable it only on lights that need it.\nEvery light accepts `shadowBias` (-0.01 to 0.01) and `shadowNormalBias` (0 to\n1), both default 0. Raise `shadowNormalBias` to about 0.02 to 0.05, or set\n`shadowBias` to about -0.0005, when lit surfaces show striped shadow acne. Too\nmuch bias detaches shadows from their casters. Set lights with `update_entity`.\n\n`environment.sky` sets the background and image-based lighting. Omit it, or use\n`{ kind: \"color\" }`, for the solid `background` color without environment\nlighting. `{ kind: \"procedural\", sunEntityId?, turbidity, rayleigh,\ngroundColor, intensity }` renders a physical sky. Its sun follows\n`sunEntityId`, which must name a directional light in the same scene. Without\nit, the sun follows the scene's first directional light. Rotate that light to\nmove the sun. `turbidity` (1 to 20, default 10) adds haze. `rayleigh` (0 to 4,\ndefault 2) deepens blue and sunset tones. `groundColor` fills the lower\nhemisphere. `intensity` (0 to 8, default 1) scales both the sky background and\nits reflections. Each change of sun direction re-renders the sky cube and its\nenvironment map, so a sun that turns every tick rebuilds them every tick. Keep\nthe sun still, or turn it in rare steps. Metal and glossy materials reflect the sky. Ambient light\nstill adds on top, so lower `ambient.intensity` when the sky lights the scene.\nHDRI skies are not available yet: an HDRI asset cannot be installed.\n\n`environment.postProcessing` grades the rendered image. It is presentation only\nand never changes simulation or snapshots. `enabled: false` keeps the settings\nand renders as if they were absent. The fields are:\n\n- `exposure` (1\u002F32 to 16, default 1) multiplies scene light before tone mapping.\n- `toneMapping` is `aces` (default), `agx`, `neutral` or `linear`. AgX keeps\n  saturated highlights from turning white. Neutral keeps base colors closest to\n  their authored values. Linear scales by exposure and clips.\n- `bloom` `{ threshold, softness, radius, intensity }` adds glow around light\n  above `threshold` (0 to 8, default 0.85). Emissive materials and bright\n  highlights bloom. `softness` (0 to 0.5) widens the threshold transition.\n  `radius` (0 to 1) spreads the glow, and `intensity` (0 to 4) sets its strength.\n- `vignette` `{ intensity, radius, softness }` darkens the corners. `intensity`\n  (0 to 1) of 1 reaches black. Darkening starts at `radius` (0 at the center,\n  1 at the corner) and reaches full strength over `softness`.\n- `antialias` is `msaa` (default), `fxaa`, `smaa` or `none`. `smaa` gives the\n  cleanest edges after the other effects. `fxaa` is cheaper and softer.\n\nThe passes run in this order: bloom, exposure and tone mapping, vignette,\nantialiasing. Exposure and tone mapping alone cost nothing extra. Bloom,\nvignette, or an `antialias` other than `msaa` renders through extra full-screen\npasses, and the background color then passes through tone mapping too. Bloom\nadds a bright pass and a chain of five blur levels, the costliest effect. SSAO,\ndepth of field, LUT grading and chromatic aberration are not available in 3D.\n\n## Input bindings\n\nAction and axis names come from `inputActions` and `inputAxes`. The optional\ndocument-level `inputBindings` maps each name to physical controls. Set it with\n`set_game {input_bindings}`, or pass `input_bindings: null` to return to the\ndefaults. The op replaces the whole map:\n\n- `actions`: `{ action: [binding] }`. A binding is `{kind: \"key\", code}` with a\n  `KeyboardEvent.code` such as `KeyJ`, `{kind: \"keyValue\", key}` with a\n  `KeyboardEvent.key` such as `z` or `+` (letter case is ignored, and a key\n  that a `key` binding names by code does not also match),\n  `{kind: \"mouseButton\", button}`, `{kind: \"gamepadButton\", button}` in the\n  standard gamepad layout, `{kind: \"gamepadAxis\", axis, direction:\n  \"negative\"|\"positive\", threshold?}`, `{kind: \"touchButton\", label?}` or\n  `{kind: \"touchStick\", direction}`.\n- `axes` (3D only): `{ axis: [binding] }` with `{kind: \"keys\", negative, positive}`\n  code lists, `{kind: \"gamepadAxis\", axis, deadZone?, invert?}`,\n  `{kind: \"gamepadButtons\", negative, positive}` or\n  `{kind: \"touchStick\", axis: \"x\"|\"y\", invert?}`. The binding with the largest\n  magnitude sets the value.\n- `look` (3D only): a list of `{kind: \"mouse\", sensitivity?, invertY?}`,\n  `{kind: \"gamepadStick\", xAxis?, yAxis?, deadZone?, speed?, invertY?}` and\n  `{kind: \"touchDrag\", sensitivity?, invertY?}`.\n\nA listed action or axis uses exactly the listed bindings, and an empty list\nunbinds it. An unlisted one keeps the defaults generated from its name. In 2D,\narrows and WASD are `left`\u002F`right`\u002F`up`\u002F`down` and Space is `space`, by key\nposition. Any other action is a `keyValue` binding of its name, so it follows\nthe player's keyboard layout. In 3D, `fire` is the left mouse button and F,\n`jump` is Space, `respawn` is R, and `moveX`\u002F`moveZ` read WASD and arrows.\nDefaults add the gamepad d-pad and left stick for directions and movement,\nbutton 0 for `jump`\u002F`space`, the right trigger for `fire`, the right stick for\nlook, and touch buttons for every other action. Binding an undeclared action or\naxis is a validation error.\n\nBindings only turn physical input into input frames. They never enter the\nsimulation, so playtests, replays and snapshots use the same `pressed`,\n`justPressed`, `axes` and `look` frames. Gamepads are polled once per tick. The\n3D players request mouse capture on click only when mouse look is bound.\nMouse actions work on the acquisition click and when capture is unavailable or\nrejected. Explicit `look: []` disables capture. HUD buttons own their presses\nbefore gameplay mouse or touch controls, and letterbox bars do not hit the HUD.\nOn a touch screen, editor play, public players and both standalone players\nshow a stick when an action or axis has a touch stick\nbinding and one button per action with a touch button binding. The 3D player\nalso turns the camera by dragging on the right half. Input pressed while play\nis paused is dropped. Player-facing rebinding is not stored yet.\n\n## HUD widget tree\n\n`ui` on the document holds HUD widgets drawn in every scene. `ui` on a scene\nholds widgets drawn above them in that scene. Set either with\n`set_ui {scene_id?, ui}`, or `ui: null` to remove it. The op replaces the whole\ntree: `{safeArea?, focusNavigation?, nodes: [node]}`. 2D and 3D documents\ntake the same tree and draw it with the same layout.\n\nEvery node has `kind`, `id`, and optionally `parent`, `anchor`, `pivot`,\n`offset`, `width`, `height`, `visible` and `opacity`. `anchor` is a point\n`{x, y}` in 0..1 of the parent's rectangle, or of the HUD rectangle for a root\nnode, so `{x: 1, y: 0}` is the top-right corner. `pivot` is the node's own point\nplaced there and defaults to `anchor`. `offset` moves the node in HUD pixels.\nThe 2D HUD rectangle is the camera view at `pixelsPerUnit` pixels per unit, so\na 16 by 9 camera at 32 pixels per unit gives 512 by 288. The 3D HUD rectangle\nis `presentation.hudWidth` by `hudHeight`, stretched over the viewport. The\nexported 3D player also renders the scene at `hudWidth` by\n`hudWidth \u002F aspectRatio`, so `hudWidth` sets the render resolution and the cost\nof every full-screen pass. A HUD value that changes repaints the whole HUD and\nuploads it as a texture. With\n`safeArea` (default true) root anchors stay inside the device's safe-area\ninsets.\n\n- `panel {color?, cornerRadius?}` fills its rectangle and holds children placed\n  by their anchors.\n- `image {assetId}` draws an image asset. 3D documents bind no images, so a 3D\n  image node fails validation.\n- `text {text, size?, color?, align?, fontId?}` sizes itself from its text\n  unless it has a width.\n- `bar {source?, value?, max?, color?, background?, direction?}` fills\n  `value \u002F max` (max defaults to 1). `source: {kind: \"health\", entityId}` reads\n  the entity's health and its health behavior's maximum.\n- `button {action, text?, background?, size?, color?, fontId?}` presses a\n  declared input action while a mouse button or a finger holds it.\n- `stack {direction?, gap?, padding?, align?}` lays children in a column or a\n  row and sizes itself from them.\n- `grid {columns, gap?, padding?}` lays children in cells as large as the\n  largest child.\n\nA child must come after its parent, and only panels, stacks and grids hold\nchildren. Panels, images, bars and buttons need `width` and `height`. Ids are\nunique across the document tree and each scene tree.\n\nScripts change nodes with `{kind: \"ui\", id, text?, value?, max?, visible?}`.\n`text` applies to text and buttons, `value` and `max` to bars, and `visible`\nto any node. A change lasts until the scene changes and is saved in snapshots.\nA command for a missing node, or a field the node does not have, fails the\nstep. An `onDestroy` hook may return `ui` commands.\n\nA button press reaches scripts as its action in `pressed` and `justPressed`,\nexactly like a key, so replays and snapshots need nothing extra. In 3D a click\non a button reaches the HUD only while the mouse is not captured. With\n`focusNavigation: true`, the gamepad D-pad moves focus between visible buttons\nand button 0 presses the focused one. While the tree shows a button, those\ngamepad buttons drive the HUD instead of the game's bindings. Existing `hud`\nlabels still draw, above the tree.\n\n## Distance culling and frame budgets (3D)\n\nBoth are presentation only. They never change simulation, snapshots or replay.\n\n- `set_performance {performance}` replaces document `performance`. `null`\n  removes it. `cullLayers` maps a layer name to `{maxDistance}` (at most 32\n  layers). `budgets` sets `drawCalls`, `triangles`, `particles` and `voices`,\n  and the time budgets `tickMs` (tick p95, default 4) and `frameMs` (frame p95,\n  default 16.6).\n- `update_entity` with `set: {renderCulling: {layer?, maxDistance?}}` hides\n  that entity and its children when the game camera is farther than the\n  distance from the entity's origin. The nearest setting in the parent chain\n  wins, and an entity's own `maxDistance` wins over its layer's. A layer must\n  be declared, or validation reports `missing_cull_layer`.\n- Distance is measured to the entity origin, not to its nearest surface. Cull\n  props and small decoration. Do not cull large scenery such as terrain or\n  buildings, because they disappear while the camera is still close to their\n  edges. Never cull the player or anything the player must see to win. The\n  editor camera shows every entity.\n- Budgets left out use the player defaults: 1000 draw calls, 1,000,000\n  triangles, 4096 particles and 24 voices. The standalone player warns in the\n  browser console once each time a budget is exceeded. The standalone players\n  and 3D capture count live particles. The 2D player uses the default particle\n  budget because 2D documents have no `performance`.\n- `nodetool game bench`, `nodetool game capture` and `nodetool game perf` check\n  these budgets and exit 1 on an overrun with `--fail-on-budget`. Load\n  `native-game-performance` for the targets, the budgets per game type and the\n  measure-fix loop.\n\n## Editor panels the user can open\n\nPoint the user to these when they work in the game editor alongside you.\n\n- **Run 10 s** diagnoses the draft in an independent session and reports the\n  first failing tick and script time by entity. **Replay to tick** rewinds the\n  active play session after a script error. **Ask the assistant** passes the\n  script context to the game assistant.\n- Editing an existing script source during play recompiles it without resetting\n  the tick or script state. A compilation failure keeps the previous source.\n  Changing script form between a function and lifecycle object requires\n  **Restart from tick 0**. Other draft changes also require a restart.\n- The **Console** panel lists play-session errors, script errors with their\n  tick, reload compilation errors, script log lines, **Run 10 s** failures and\n  validation errors.\n- The **Assets** panel shows the `browse_native_game_assets` catalog with\n  thumbnails. **Generate** and **Regenerate** run `generate_game_asset` for\n  image, speech and music slots. **Use** binds an older candidate and moves the\n  slot's frame and tile bindings onto it.\n- Ctrl+K (Cmd+K on macOS) opens the command palette, where **Edit keyboard\n  shortcuts** rebinds editor shortcuts.\n- When the script pane reports a draft conflict, local typing stays unsaved\n  until the user picks **Keep my version** or **Use draft version**.\n- Restoring a revision asks for confirmation because it replaces the draft and\n  clears its undo history. The newest 100 published revisions are retained.\n\n\n## 3D forces and torques\n\nReturn `{kind: \"addForce\", force: {x, y, z}}` to accelerate the issuing\nentity's dynamic body, `{kind: \"addTorque\", torque: {x, y, z}}` to turn it,\nor `{kind: \"addForceAtPoint\", force: {x, y, z}, point: {x, y, z}}` to apply\na force at a world-space point. An offset point also produces torque. All\nvectors use world space, even when the body is rotated. Each component must\nbe finite and between -1000000 and 1000000.\n\nLoads accumulate within the current simulation tick and clear after its\nphysics step. Return the commands on subsequent ticks to keep applying them.\nUse `impulse` for an immediate velocity change. Forces require the issuing\nentity's active dynamic body. Static, kinematic, missing and inactive bodies\nreject them. A nonzero load wakes the body, while a zero load keeps a sleeping\nbody asleep. These commands are available only in 3D scripts.\n",{"name":5,"description":7},"main","c7226baa82daca42a19a5855c116544f97aa8bac","packages\u002Fsystem-skills\u002Fnative-game-reference",90,"https:\u002F\u002Fgithub.com\u002Fnodetool-ai\u002Fnodetool","https:\u002F\u002Fnodetool.ai\u002F","https:\u002F\u002Fgithub.com\u002Fnodetool-ai","No-code AI development","2026-11-11 08:55:40",{"source":8,"skillName":5,"commands":62,"prompt":72,"zipAvailable":73},[63,66,69],{"tool":64,"command":65},"npx","npx skills add nodetool-ai\u002Fnodetool --skill native-game-reference",{"tool":67,"command":68},"bunx","bunx skills add nodetool-ai\u002Fnodetool --skill native-game-reference",{"tool":70,"command":71},"pnpm","pnpm dlx skills add nodetool-ai\u002Fnodetool --skill native-game-reference","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fnodetool-ai\u002Fnodetool\u002Fblob\u002Fmain\u002Fpackages\u002Fsystem-skills\u002Fnative-game-reference\u002FSKILL.md",true,[],[76,111,138,153,168,192],{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":90,"publishedAt":47},"01M6BY4D93R0Q9400ZHJ04DXP1","nodetool","Use the NodeTool MCP server to build, validate, run, and debug visual AI workflows, and to work with assets, collections, and jobs. Use when the user mentions NodeTool or asks for a workflow graph, a node, or a media generation run on their NodeTool server.","https:\u002F\u002Fgithub.com\u002Fnodetool-ai\u002Fnodetool\u002Fblob\u002Fmain\u002Fplugins\u002Fchatgpt\u002Fnodetool\u002Fskills\u002Fnodetool\u002FSKILL.md",[82,85],{"categoryNo":83,"categoryName":84,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":86,"categoryName":87,"taxonomyType":88,"taxonomyTypeDesc":89},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[78,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,30],"MCP","mcp server","workflow","workflows","validate","debug","run","graph","server","Media","assets","work","node","use","visual","generation","collections","media generation","with","for",{"skillNo":112,"name":113,"nameZh":6,"description":114,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":115,"categories":116,"keywords":118,"publishedAt":47},"01M6BY4AEB05ZPBRVD078ATR2H","wan-2-6-prompting","Prompt Alibaba's Wan 2.6 video — text-to-video with a global style line plus timing-bracketed shots, and image-to-video that describes only what changes over time. Covers the shot-mode switch per provider, the prompt budget, negative prompts, motion intensity wording, audio input and prompt expansion. Use whenever the model id names a Wan 2.6 video route (alibaba\u002Fwan-2.6\u002Ftext-to-video, alibaba\u002Fwan-2.6\u002Fimage-to-video, wan-video\u002Fwan-2.6-t2v, wan-video\u002Fwan-2.6-i2v, wan-video\u002Fwan2.6-i2v-flash) on generate_video, animate_image or a TextToVideo node. Not for Wan 2.5 or 2.7, or the Wan 2.6 image routes.","https:\u002F\u002Fgithub.com\u002Fnodetool-ai\u002Fnodetool\u002Fblob\u002Fmain\u002Fpackages\u002Fsystem-skills\u002Fwan-2-6-prompting\u002FSKILL.md",[117],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[119,120,121,122,123,124,40,125,126,127,128,103,129,130,131,132,133,134,135,136,137],"wan 2 6 prompting","prompting","image","generate","Budget","Video","text","animate","model","flash","only","route","time","line","input","text-to-video","image-to-video","mode","expansion",{"skillNo":139,"name":140,"nameZh":6,"description":141,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":146,"publishedAt":47},"01M6BY47T4748ZG5ZMTAR97ZWZ","video-workflow","Build a reusable NodeTool video workflow for batches or repeated inputs. For a single finished piece, use a storyboard skill.","https:\u002F\u002Fgithub.com\u002Fnodetool-ai\u002Fnodetool\u002Fblob\u002Fmain\u002Fpackages\u002Fsystem-skills\u002Fvideo-workflow\u002FSKILL.md",[144,145],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":83,"categoryName":84,"taxonomyType":21,"taxonomyTypeDesc":22},[93,124,147,78,104,148,149,150,151,110,152],"video workflow","reusable","storyboard","build","batches","single",{"skillNo":154,"name":155,"nameZh":6,"description":156,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":157,"categories":158,"keywords":160,"publishedAt":47},"01M6BY4587N2SYN315SXGEMZC3","video-clone","Analyze a reference video and recreate its shot structure with a new product or cast in NodeTool.","https:\u002F\u002Fgithub.com\u002Fnodetool-ai\u002Fnodetool\u002Fblob\u002Fmain\u002Fpackages\u002Fsystem-skills\u002Fvideo-clone\u002FSKILL.md",[159],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[124,161,162,163,164,78,28,165,166,109,167],"video clone","clone","product","analyze","cast","structure","new",{"skillNo":169,"name":170,"nameZh":6,"description":171,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":172,"categories":173,"keywords":175,"publishedAt":47},"01M6BY4257C2KQYYMD9EK6DK8E","video-audio-continuity","Keep sound continuous across a multi-scene piece cut from generated video — why one clip per scene hard-cuts the audio at every boundary, when to write all the scenes into a single generation instead, the sound-design vocabulary that survives a provider's copyright filter, and how to check the length, the aspect ratio and the mix of what came back. Use when a piece has more than one scene and the video model writes its own audio (Seedance 2, Veo 3, MiniMax H3, Kling 2.6 and later), or when a cut's sound drops out at the shot changes.","https:\u002F\u002Fgithub.com\u002Fnodetool-ai\u002Fnodetool\u002Fblob\u002Fmain\u002Fpackages\u002Fsystem-skills\u002Fvideo-audio-continuity\u002FSKILL.md",[174],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[124,40,176,177,178,179,180,181,127,182,183,184,185,186,187,188,189,32,190,191,30],"video audio continuity","continuity","design","boundary","how","why","write","generated","later","back","across","cut","how to","filter","at","veo 3",{"skillNo":193,"name":194,"nameZh":6,"description":195,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":196,"categories":197,"keywords":199,"publishedAt":47},"01M6BY3ZANBSQMZRH4MG55R8Z7","veo-3-prompting","Prompt Google's Veo 3 video line — the five-element shot\u002Fsetting\u002Fsubject\u002Faction\u002Fdialogue structure, the cinematography vocabulary it reads directly, duration and aspect-ratio tradeoffs, the audio and auto-fix switches per provider, and the fixes for visual drift, temporal inconsistency and audio mismatch. Use whenever the model id contains veo3 or veo-3 (fal-ai\u002Fveo3.1 and its fast, lite, image-to-video, first-last-frame and reference-to-video routes, fal-ai\u002Fveo3\u002Fimage-to-video, google\u002Fveo3.1 on atlascloud, google\u002Fveo-3 and google\u002Fveo-3.1 on replicate, kie's veo-3-1, and Gemini's own veo-3.1-generate-preview) on generate_video, animate_image, generate_video_from_references or a TextToVideo node.","https:\u002F\u002Fgithub.com\u002Fnodetool-ai\u002Fnodetool\u002Fblob\u002Fmain\u002Fpackages\u002Fsystem-skills\u002Fveo-3-prompting\u002FSKILL.md",[198],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[200,201,120,191,121,202,203,122,204,205,124,40,126,127,206,207,208,209,210,211,212,166,213,214,215],"veo 3 prompting","veo","google","gemini","fast","first","fal","kie","replicate","fix","auto","references","drift","preview","element","auto-fix"]