native-game-reference
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.
- License 许可
- AGPL-3.0
- 最近更新
- 2026-11-11
- Stars
- ★ 627
- Forks
- 90
01 前言 Front Matter
- name
- native-game-reference
- description
- Field reference for native game documents: scripts, lifecycle hooks, events, tags and props, 2D visuals, particles, lighting, effects, 3D rendering, physics layers, audio mix, animation graphs, input bindings, HUD widgets, performance and editor panels. Load after native-game when an edit needs one of these fields.
03 SKILL.md 正文
Native game field reference
The fields and runtime rules behind native-game. Load native-game first for
the build, playtest and asset loop. The edit_native_game op description also
lists the op shapes for prefabs, the mixer, script params, input bindings, the
HUD tree, animation graphs, performance, spatial audio and collision layers.
Sections here say what the fields do and how they interact.
Schema versions
New 2D games start at schemaVersion: 4 with engineVersion: "3". Older 2D
documents use schema 1 or 2 with engine "1". Fonts, prepared assets, scene
music, lighting, backgrounds, gravity, visualAnimation and a render effect
chain need schema 2 or later. Tags, props and script params need schema 4.
Validation rejects these fields on an earlier schema. 2D schemaVersion: 5 with
engineVersion: "4" is reserved for Rapier 2D physics, which this runtime does
not provide: validation and sessions refuse it with engine_unavailable. Do not
author schema 5. A schema must use its own engine version. 3D games use
schemaVersion: 3 with engineVersion: "2".
Entity tags and properties
Both dimensions support optional tags and props on an entity. Use
update_entity with set: {tags: ["hero"], props: {health: 10}}. Each field
replaces its complete value. A top-level null removes the stored field.
Nested null is JSON data. Tags are unique, with at most 64 strings of
1–128 characters. Properties hold finite JSON, with at most 64 top-level keys,
16 levels of nesting and 64 KiB of UTF-8 JSON. Keys contain 1–128 characters.
__proto__, constructor and prototype are rejected at every nesting level.
Scripts read entity.tags, entity.props, entity.active and
entity.rotation, and the same fields on existing world entries and
world.get(id) results. world.query({tag}) returns the ids carrying a tag.
Rotation is radians in 2D and the committed world quaternion [x, y, z, w] in 3D.
Use setProp {key, value} and removeProp {key} to change the caller's
runtime properties. Setting null keeps that key. Removal deletes it. The
runtime checks every property command in a tick before it changes any state,
and an error names the entity and tick. The props of each entity that runs a
script count once toward the 64 KiB script input limit. Other entities' props
travel with the world only when a script reads input.world. Runtime properties survive snapshot restore and
remain independent of the authored document and other prefab instances.
Mutating an input object does not change runtime state.
2D scripts
A script behavior is a function expression, or an object of lifecycle hooks
(see Lifecycle hooks and timers). A function receives {tick, pressed, justPressed, events, entity, world, state, random} and returns {state, commands}. events are the previous tick's events. world lists every active
entity with a collider or camera as {id, source, x, y}. entity holds the
caller's id, source, position, velocity, and touching {down, up, left, right}, which names the sides of its collider that rest against a solid at the
start of the tick. A spawned instance has the id <prefab>#<n>, its source
is the prefab id, and it runs the prefab's script with its own state.
Functions, lifecycle hooks, and timer methods must be synchronous. Async functions and generators fail preparation. Returning a promise, thenable, or iterator fails the call. Use tick timers for delayed work.
Store persistent values in returned state. Nothing else carries over between
calls. A plain function expression such as (input) => { … } runs in strict
mode in a reused sandbox whose built-ins and global object are frozen, which
makes each call several times cheaper. Assigning to a global or a built-in
there throws, and the engine then repeats the call in a fresh context and keeps
that source in fresh contexts. Other sources, such as a function returned by an
initializer call, evaluate in a fresh context each call. Prefer plain function
expressions and keep helpers and temporary values in local variables.
Source initialization uses the seeded random generator. maxTickMs limits each
call, subject to the total script budget for the tick. Source and input/output
limits count UTF-8 bytes.
During a function call, global world.get(id) returns an entity or undefined,
and world.query({source?, tag?, near?, radius?, limit?}) returns matching IDs
in runtime entity order. Both read the same immutable tick-start snapshot,
before movement behaviors or script commands run. Returned values are private
copies. Queries cover every active entity. Legacy input.world keeps its
existing shape and population, including only collider or camera entities in 2D.
The engine serializes input.world only when a script reads it, with a 1 MiB
limit, so prefer world.get and world.query with near and radius.
The 2D query record is {id, source, x, y, velocityX, velocityY, grounded}.
The 3D record is {id, source, position, velocity, grounded}, with XYZ vectors.
3D records and schema 4 2D records add tags, props, rotation and active.
A tag filter matches only entities that carry the tag, so it matches nothing
on schema 1 or 2. Pass near: {x, y, z?} and radius together. The radius is
nonnegative and includes entities on its boundary, measured between centers,
with omitted z treated as zero. source matches the record's original entity
ID, not a 3D prefab asset ID. limit is an integer from 0 to 1024, defaulting
to 1024. A call allows 64 combined get/query attempts. Exceeding the count
fails the call even if guest code catches it. Query arguments allow 4096 JSON
characters, get IDs 1024 characters, and responses 64 KiB. These APIs are
unavailable during source initialization.
The commands are setVelocity, setPosition, setVisual {tint?, opacity?, rotation?, scaleX?, scaleY?, flipX?}, playAnimation {clip}, spawn {prefabId, x?, y?, velocityX?, velocityY?}, despawn, emit, sceneTransition, and
hud {id, text, x, y, size?, color?, align?}. HUD coordinates are canvas
pixels. Empty text removes a label. A label with the id score or win
replaces the built-in label. Set fontId to a logical font binding for a HUD
label. Font assets use mediaKind: "font", fontFormat: "ttf" | "otf", and
required: true by default. Required fonts load before the first frame or fail
play and capture. An optional font (required: false) reports a fallback before
play. Export embeds the font bytes for offline use.
log {message, level?} writes a line to the editor console in 2D and 3D.
level is "log" (the default), "warning" or "error". A log line never
changes the simulation, snapshots or replay, and it does not count toward
maxCommands. A tick keeps 32 lines and reports the rest as one dropped-lines
warning. Messages longer than 1000 characters are truncated. Log text still
counts toward the 64 KiB output limit of a script call. Lines logged before a
script failure appear in the console ahead of the error.
Script time limits
maxTickMs accepts 1 through 20 ms, defaults to 8 ms, and cannot raise the
host's 20 ms ceiling. It counts the CPU time of the thread running the script, from just
before the call (or before compiling its source) until its output is checked.
Waiting for a processor does not count, and on Node neither do garbage collection
pauses during the call. A call ends only when both its CPU time
and its wall time reach the limit. Browsers have no thread CPU clock, so there
the limit counts wall time. The 50 ms script budget for a whole tick and the
100 ms limit for evaluating a source during preparation count wall time on
every host. A session's first scripted tick has 1,000 ms instead of 50 ms,
because it runs each script for the first time. A script that loops forever still ends at its limit.
What an overrun does depends on the host. The editor's play mode and the
standalone players skip the call: the entity keeps its script state, issues no
commands that tick, and the editor console shows a warning naming the call. A tick
past its 50 ms budget skips its remaining calls the same way. Headless runs,
replays, the CLI and agent playtests stop with a Game script interrupted
error, so a slow script fails verification. A skipped call depends on timing,
so a play session that skipped one may not replay identically.
Script parameters
Declare a script's tunables as params on its script behavior instead of
constants in the source. The inspector edits them and scripts read the values
on input.params. Set them with set_script_params {entity_id, scene_id?, index, params?, values?}. params replaces the declarations and null
removes them with their values. values merges stored values, and a null
value returns one to its default. 2D games need schema 4. 3D games accept
params on schema 3.
{"op": "set_script_params", "entity_id": "player", "index": 0,
"params": {"speed": {"type": "number", "default": 3, "minimum": 0, "maximum": 10},
"target": {"type": "entity"}, "hit": {"type": "asset", "kind": "audio"}},
"values": {"speed": 5, "target": "goal"}}
Types are number (minimum, maximum, integer), boolean, color
(#rrggbb), enum (options, default the first option), entity, asset
(optional kind) and vector (dimensions 2 or 3). A behavior declares at
most 32 params with identifier names. input.params holds the stored value,
else the default. An entity or asset param without either is null.
Validation rejects an entity reference outside the behavior's scene, a missing
asset slot, an asset of the wrong kind, and in 3D an entity reference inside
a prefab or an undeclared prefab asset. A behavior without params has no
input.params key. Params count once per behavior definition toward the
64 KiB script input limit.
Lifecycle hooks and timers
A script source may be an object of hooks instead of a function. It works in 2D and 3D with no schema change.
({
onStart(input) { every(30, "fire"); return { state: { shots: 0 } }; },
fire(input) { return { state: { shots: input.state.shots + 1 },
commands: [{ kind: "spawn", prefabId: "bolt" }] }; },
onTriggerEnter(input, contact) { return { commands: [{ kind: "emit", event: "hit:" + contact.otherId }] }; },
onDestroy(input) { return { commands: [{ kind: "emit", event: "gone" }] }; }
})
Hooks receive the same input as a function script and return
{state?, commands?} or nothing. Each hook sees the state returned by the
one before it. Within a tick they run in this order:
onStart, on the behavior's first call.onSceneEnter, on the first tick of the scene.onTriggerEnter,onTriggerExitandonContact, once per contact of the entity in the previous tick's events, in event order. A contact where either collider is a sensor is a trigger. Triggers fire onenterandexit.onContactreceives every phase of a solid contact. The second argument is{otherId, phase, sensor}.onEvent, once per trigger event of the previous tick that reaches the entity, in event order. See Typed events.- Due timers, in the order they were scheduled.
onFixedUpdateoronUpdate. They are aliases. Define one of them.
onDestroy runs alone in the tick after the entity despawns. It may return
only hud, emit, spawn, despawn, sceneTransition, log, save and load commands. A scene
transition discards behavior state without calling onDestroy.
after(ticks, name) calls the object's method name once, ticks ticks
later. every(ticks, name) calls it every ticks ticks. ticks is an
integer from 1 to 1000000, name must be a method that is not a hook, and a
behavior holds at most 32 timers. Scheduling an existing name replaces that
timer. Timers live in the behavior's snapshot state, so they replay from any
snapshot. Session preparation rejects an unknown on* key, a hook that is not
a function, and an object that defines both onUpdate and onFixedUpdate.
Lifecycle objects run in a fresh context each call.
Typed events
emit takes an optional payload and target in 2D and 3D, with no schema
change. The payload is any JSON value up to 1 KiB serialized and 16 levels
deep. The target is {entityId} for one entity or {tag} for every entity
with that tag. Omit target to broadcast.
// Sender
({ onTriggerEnter(input, contact) { return { commands: [{ kind: "emit", event: "alarm",
payload: { by: contact.otherId, level: 2 }, target: { tag: "guard" } }] }; } })
// Receiver, on an entity tagged "guard"
({ onEvent(input, event) { if (event.event === "alarm") { return { state: { level: event.payload.level } }; } } })
The event lands in the stream as {kind: "trigger", event, entityId, payload?, target?}, where entityId is the sender, and reaches scripts on
the next tick. onEvent(input, event) receives that object for each trigger
event that reaches its entity, broadcasts included, and the sender receives
its own broadcasts. Built-in trigger behaviors emit untargeted events, so
onEvent sees them too. The target also limits built-in listeners: a spawn
or sceneTransition behavior with onEvent and an audioSource with
onEvent react only to events that reach their entity. input.events still
holds every event of the previous tick, targeted or not. A tag target reaches
nothing on 2D schemas before 4. A plain {kind: "emit", event} produces the
same event as before. Payloads count toward the 64 KiB script input limit once
per tick.
Player saves
Scripts save and load in 2D and 3D with two commands:
save {slot, data, scope?, snapshot?}writesdatato a slot after the tick.load {slot, scope?}reads a slot.
scope is "progress" (the default) or "settings". The two scopes are
separate stores, so a progress slot and a settings slot can share a name and
removing one never touches the other. snapshot: true also stores the
end-of-tick session snapshot with a progress save. A settings save with a
snapshot fails the tick. The player keeps one snapshot save pending at a time,
and the editor stores the data without the snapshot.
The player runs saves and loads in order, with at most 8 waiting. A request past that limit fails without stopping the game, and a failed load answers with null data.
A slot name is 1 to 32 letters, digits, _ or -. data must be a JSON
object of at most 16 KiB serialized, 256 keys counted at every depth and 16
levels deep. Each scope holds at most 16 slots per game. A tick accepts at most
4 save and load commands across all scripts, and more fail the tick. These
commands do not count toward maxCommands and never enter snapshots.
A load answers on a later tick through input.loaded, a list of {scope, slot, data} where data is null for an empty slot. A frame carries at most 2
results. Keep what the game needs in script state:
({ onStart() { return { commands: [{ kind: "load", slot: "main" }] }; },
onUpdate(input) {
const result = input.loaded?.find((entry) => entry.slot === "main");
return { state: result ? { best: result.data?.best ?? 0 } : input.state };
} })
The loaded data is part of the input frame, so a replay of recorded frames
reproduces it without storage. The standalone player stores saves in IndexedDB.
The editor stores them in localStorage under the game id. nodetool game simulate and agent playtests keep them in memory. simulate --saves <file>
seeds slots from {progress?, settings?}, and its JSON report lists the slots
after the run.
2D visuals
For generated art in a workflow, pass one style entity with a reference image.
The guided graph sends that image through an image-to-image generation input
for every visual slot. StageGameAssets.preparation accepts per-slot alpha
trimming, target size with a crop policy, pivot, nearest or linear sampling,
and mirror tiling for single images. Trimming records crop offsets so placement
keeps its anchor. StageGameAssets.fonts stages TrueType or OpenType assets by
logical font ID.
- Use
sprite.blend: "additive"for light drawn on black, such as glows and sparks. Setsampling: "linear"on painted or soft assets. - Use
lifetime {ticks, fade, endScale}for short-lived entities. A kinematic body without a collider moves but never collides, which keeps them cheap. Prefer theparticlescomponent for sparks, smoke and dust. - Use
animatorframes on a sprite sheet. Playback starts when the entity spawns. Namedanimator.clipshold other frame sets.playAnimationstarts a clip from its first frame, and a repeated request for the current clip continues it. Setsprite.flipXor thesetVisualflag to mirror art, orsprite.faceMotion: "left" | "right"to turn art that faces that way toward the body's horizontal motion. - Give
audioSource.onEventan event kind or the name of an emitted trigger. SetaudioSource.volumefrom0to1for effect loudness. Effects receive distinct voice ids, so repeated triggers can overlap within the 32-voice cap. - Make hazards sensors. Only a non-sensor body collects a
collectible.
Set scene.music to an installed audio assetId for looping music. volume
ranges from 0 to 1, and fadeInTicks and fadeOutTicks range from 0 to
600. Music starts at the scene's entry tick. Pause suspends audio, reset
restarts the entry scene once, and loading resumes the loop at the saved
logical tick. Set music.streaming: {loopStartSeconds, loopEndSeconds?} for
long tracks in 2D or 3D. This streams through the music bus without decoding
the whole asset. Start defaults to 0 seconds, end defaults to the track's end,
and an explicit end must exceed start. Both are bounded by 86400 seconds.
The intro plays once. Custom loop boundaries follow browser media events.
Existing fades crossfade outgoing and incoming scene tracks. Use update_scene
to replace the whole music object. Omit streaming to retain decoded
playback. Browser playback starts after a user gesture. A missing or
unplayable audio asset is reported in the player.
visualAnimation on a sprite animates rotation, scaleX, scaleY,
opacity, or tint from from to to over durationTicks in its tracks.
delayTicks, repeat, pingPong, and easing control timing. rotationRate
adds radians per tick for continuous spins and cannot share a rotation track.
Each property has at most one track. Tracks start at scene entry or spawn time.
The visual order is authored transform and sprite values, tracks, script
setVisual, then lifetime fade and scale. Tracks do not change collision.
Scene backgrounds define image layers independent of entities. Each layer
sets assetId, world-unit width and height, origin at the tile center,
layer, per-axis parallax, scrollRate in world units per second, and mode
(none, repeat, repeatX, or mirror). repeatX tiles only sideways, which
suits a strip of scenery. Parallax 0 stays fixed to the screen and 1
stays fixed in the world. Optional frame selects an atlas rectangle.
Scene lighting holds an ambient color and intensity plus up to 32 points
with world position, color, intensity, radius, and falloff. Lighting multiplies
world color before effects. HUD stays unlit. Set sprite.unlit: true for
emissive art such as glows. Canvas2D capture paints these sprites above the lit
world. An entity light2d {color, intensity, radius, falloff, offset?} moves
with its entity. Each frame adds the entity lights nearest the camera to the
scene's fixed points, up to 32 lights in total.
renderEffects is an ordered chain of at most eight effects.
brightnessContrast uses brightness from -1 to 1 and contrast from 0
to 4. bloom uses threshold from 0 to 1, softness from 0 to 0.5,
radius from 0 to 64 pixels, and intensity from 0 to 4. lut refers
to an opaque packed 2D cube image with dimensions size² × size and an
optional intensity and color domain. Effects run in listed order. Set
hudEffectOrder to afterEffects to keep text crisp, or beforeEffects to
process HUD with the world. A bloom chain defaults to afterEffects.
required: true makes unsupported GPU effects fail before play or capture.
Optional effects are omitted with a diagnostic on Canvas 2D.
Collider category and mask are unsigned 32-bit bitsets in 2D. They default
to 1 and 4294967295. A pair interacts only when each mask includes a
category bit from the other collider. Give bullets masks that exclude other
bullets when bullet-to-bullet interaction is unnecessary.
Contacts report phase: "enter" | "stay" | "exit" and normalX/normalY.
Check both entityId and otherId, since each pair emits one contact per tick.
Built-in triggers fire on entry. Use stay contacts for repeated overlap logic.
Swept collisions detect fast movement through thin static walls and sensors.
Scene entry starts authored entities' animation and lifetime clocks, and saves
preserve those clocks and active contact pairs.
Particles
The particle inspector offers Fire, Smoke, Sparks, Dust, Magic, Rain, Snow and
Explosion presets in both dimensions. Applying one replaces that entity's
particles component and preserves its other components. Select an emitter
to edit its fields, lifetime curves and color gradient. Renaming an emitter
repairs its death-emitter references. Removing one removes references to it.
The isolated preview uses a fixed seed and restarts when edited. Pause and
Restart affect the preview only. The 2D preview uses Canvas2D, including sprite
sheets and blending. The 3D preview uses WebGL2, including stretched and mesh
particles and depth softness. It does not run gameplay scripts or change the
play session.
Agents and CLI document edits use the same update_entity operation as the
inspector. Set the complete component with
{op: "update_entity", entity_id: "effect", set: {particles: {emitters: [...]}}}.
There is no preset-name operation. Node.js authoring can also import
getGameParticlePreset(name, dimension) from @nodetool-ai/protocol to get
fresh canonical data. In a code action, use the JSON recipes below with the
existing game edit API instead of importing that host package.
Particle preset recipes
Each payload is a complete particles component. Omitted fields take schema
defaults. These canonical recipes use 3D draw settings. For a 2D entity, remove
render from each emitter and set shape.size.z to 0 for box shapes.
Keep other values unchanged. Both dimensions use world units and +Y upward.
Place rain and snow emitters above the area they cover. Explosion is a finite
burst, which can be restarted with emitParticles without a count.
fire
{"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}]}
smoke
{"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}]}
sparks
{"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}}]}
dust
{"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}]}
magic
{"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}]}
rain
{"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}}]}
snow
{"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}]}
explosion
{"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}]}
Particle component fields
Add particle effects with the particles component on any 2D (schema 2 or
later) or 3D entity. It holds up to 8 emitters, each with a unique id. An
emitter sets rate (particles per second), bursts (time, count,
cycles, interval), duration, loop, playOnStart, maxParticles and a
shape: point, circle, sphere, cone, box or edge. Cone, box and
edge emit along local +Y. Point, circle and sphere emit outward. Per-particle
values lifetime, speed, size, rotation and angularVelocity take a
number or { min, max }. color takes #rrggbb or { min, max }. The
sizeOverLifetime, speedOverLifetime and opacityOverLifetime curves are
lists of { t, value } keys sorted by t from 0 to 1. colorOverLifetime uses
{ t, color } keys. Add gravity ({ x, y, z } in units per second squared)
and drag. space: "local" makes particles follow the entity.
onDeath: [{ emitter, count }] fires another emitter of the same component
where each particle dies. Give that emitter playOnStart: false and rate: 0.
Scripts trigger emitters with { kind: "emitParticles", emitter?, count? } on
their own entity. Without count, the emitter restarts its cycle, which suits
a one-shot explosion with loop: false. With count, that many particles
spawn at once. Omitting emitter selects the first emitter in the list. The
command is presentation only and never appears in gameplay events or
snapshots. Particles cannot affect scores, physics or scripts. Prefer them over
spawned prefabs for sparks, smoke and dust.
Each emitter also sets how its particles draw in 2D. Without sprite, a
particle is a soft round dot tinted by its colour. sprite: { assetId, columns, rows, frameCount?, cycles?, sampling? } uses an image asset instead. A sheet is
cut into columns by rows cells read left to right and top to bottom, and the
first frameCount cells play cycles times over each particle's life.
sampling defaults to nearest. blend: "additive" adds light, which suits
fire, sparks and magic. The default normal alpha-blends, which suits smoke and
dust. Particles take scene lighting like sprites. Set unlit: true so glowing
particles keep their colour in the dark. layer sets the draw layer. It
defaults to the entity's sprite layer, or 0 without a sprite, and particles
draw above sprites of the same layer. size is the particle's width in world
units. rotation and angularVelocity are radians and radians per second.
The standalone player, the editor's play view and capture_native_game_frame
draw 2D particles. A capture simulates particles for every tick up to the
captured one, so a burst at tick 0 is still visible a few ticks later. WebGPU
draws every live particle. The Canvas2D fallback draws at most 1024 visible
particles. Each emitter's random stream is seeded from its entity and emitter
ids, so captures repeat.
In 3D, render sets how each particle draws. The default { kind: "billboard" } is a soft round dot facing the camera, size units across and
turned by rotation. { kind: "stretched", length?, speedScale? } lies along
the particle's direction of travel on screen. It is size wide and size × length + speed × speedScale long, which suits sparks, rain and streaks.
length defaults to 1 and speedScale to 0.1 seconds. { kind: "mesh", shape, align? } draws a small solid box, sphere, tetrahedron or
octahedron, size units across, for debris and confetti. With align: "world", the default, it turns by rotation about world +Y. With align: "velocity" its +Y points along its velocity. Mesh particles take scene
lighting unless unlit: true. Billboards always keep their own colour. Both
take scene fog and post-processing such as bloom, so additive particles glow.
softness (world units) fades a billboard out where it nears the geometry
behind it, so smoke and glows do not cut a hard line through the floor. The
renderer then draws the scene's depth once more per frame, so set it only on
emitters that touch geometry. That depth pass skips transparent materials,
keeps alpha-tested cut-outs, and counts toward the draw call and
triangle budgets. 3D particles ignore sprite and layer, and
2D particles ignore render and softness. Validation warns about render or
softness on a 2D emitter, softness on a mesh emitter, and sprite on a
3D emitter, without failing. Editor Play, the 3D standalone player,
nodetool game capture and capture_native_game_frame draw 3D particles.
Translucent particles sort across render types without writing depth. Interleaved
types can increase draw calls. A scene transition clears the particle field.
Within a scene, detached world particles retain world-space death chains until
they finish. Storage grows lazily within the scene particle limit.
3D collision layers
collisionLayers names up to 16 layers, and the layer at index i owns bit
i. collisionMatrix lists the layer pairs that do not collide, in either
order. A layer paired with itself stops its colliders from touching each other.
Every unlisted pair collides. Set both with set_game {collision_layers, collision_matrix}, and put a collider on a layer with update_entity set collider3d {layer}. A layered collider derives category from its bit and
mask from the matrix, so leave its raw bits at 1 and 65535. Validation
rejects an unknown layer name, a pair listed twice in the matrix and
conflicting layer bits. Raw category and mask remain an override for
colliders without a layer. A collider without a layer keeps the default
category 1, which is the bit of collisionLayers[0]. It therefore shares the
first layer's pairs: a layer paired with collisionLayers[0] does not touch
unlayered colliders either. Name the first layer for the world, or give such
colliders a layer. 2D colliders keep raw bits only.
3D physics materials
physicsMaterials names up to 64 surfaces that colliders share. Each material
has friction (0 to 4, default 0.5), restitution (0 to 1, default 0),
frictionCombine and restitutionCombine. A combine rule is average
(default), min, multiply or max. When two touching colliders use
different rules, the later rule in that list wins. Use max restitution for a
bouncy ball and min friction for ice. Declare materials with set_game {physics_materials: {name: material}}, and remove them all with
physics_materials: null. Point a collider at one with update_entity set collider3d {physicsMaterial: name} and clear it with physicsMaterial: null.
A collider with a material takes all four values from it, so leave its inline
friction at 0.5 and restitution at 0 and set no inline combine rule.
Validation rejects an unknown material name and conflicting inline values. A
collider without a material keeps its inline values and may set
frictionCombine and restitutionCombine itself. Existing documents name no
material and simulate as before. 2D colliders have no friction or
restitution.
Audio mix and spatial audio
Set the document mix with set_audio {mixer} in 2D and 3D. set_audio {mixer: null} removes it. Every mixer has the buses master, music, sfx,
voice and ui. Add buses under mixer.buses with parent (default
master), volume (linear, 1 is unity), muted, lowpassHz and
reverbSend. A bus multiplies its parent's gain, so muting sfx silences its
children. Scene music plays on music and other audio on sfx unless
mixer.assetBuses maps the asset slot to another bus.
The master bus ends in a limiter at limiter.thresholdDb (default -1 dB) that
does not change levels below the threshold. The default ducking rule lowers
music to 0.35 while a voice plays on voice or a child of it. Replace
ducking to change or remove that rule.
A mixer snapshot under mixer.snapshots overrides named fields of named buses.
transitions move to a snapshot over fadeTicks when the simulation emits a
trigger event, a win event, or enters a scene. The snapshot base returns
to the base mix. Restarting or restoring a session returns to the base mix or
the scene's snapshot without a fade. The mix is presentation state. It is not
saved in game snapshots and never changes playtest results or replay.
Set audioSource.spatial: true to play an entity's effect at its position in
2D and 3D. The listener is the active camera, so an emitter right of the camera
plays in the right ear. Effects play at full volume within minDistance
(default 1 world unit) and fall off by distanceModel (linear, inverse
by default, or exponential) and rolloff (default 1) out to maxDistance
(default 50), which must exceed minDistance. The linear model clamps
rolloff to 1 and is silent at maxDistance. A 3D cone {innerAngle, outerAngle, outerGain} points along the entity's -Z axis. 2D emitters ignore
the cone. doppler (default 0, 1 is physical) shifts pitch with emitter and
camera motion. Set a field to null in update_entity to return it to its
default. The event that starts the effect carries the emitter position, so a
collectible that despawns as it plays still sounds where it was. Spatial
settings never change simulation.
3D animation graphs
A 3D animation graph lives in the document's animationGraphs map. An
entity's animator3d.graph names one, and its animator3d.clips maps the
graph's clip aliases to prepared clip IDs, so one graph can drive several rigs.
Store a graph with set_animation_graph {graph_id, graph} and delete it with
remove_animation_graph {graph_id}. An animator with a graph cannot also set
initialClip.
A graph declares parameters (float with a default, bool with a default,
or trigger) and one to eight layers. Each layer has an initialState,
states and transitions. A state plays a motion: one clip, a blend1d
over a float parameter with points in increasing value order, or a blend2d
over two float parameters. speed scales the animator's playbackRate, and
loop defaults to true. The first layer is the unmasked override base. Later
layers can be additive and can set weight and a mask of prepared model
node IDs. A mask covers each listed node and its descendants.
A transition names from (a state ID, or * for any state), to,
conditions, optional exitTicks and durationTicks for the crossfade. Every
condition must hold. Float conditions use gt, gte, lt, lte, eq or
neq with a value. Bool conditions use true or false. A trigger uses
set and resets once a transition consumes it. exitTicks is how long the
source state must run before the transition may fire. Each transition needs a
condition or exitTicks. A crossfading layer does not start another
transition. Transitions are checked in document order.
Scripts set parameters with setAnimParam {name, value}. Pass true to fire
a trigger. playAnimation {clip} on a graph entity switches the base layer
directly to the state with that ID, or else to the first state that plays that
clip alias, using animator3d.transitionTicks. Example locomotion: a speed
float, a move state with blend1d points idle at 0, walk at 2 and run at 6,
and a script that sends setAnimParam with the character's planar speed.
Graph state (current state, entry tick, crossfade source and parameter values)
is simulation state, saved in snapshots. Check it with a playtest that sets
restore_at_tick and asserts replay_matches. The renderer samples the
resolved pose at the interpolated tick. Blended clips share one normalized
phase. The pose sampler blends node translation, rotation and scale. It does
not blend morph targets.
Model preparation derives clipDurations from verified GLB time accessors.
Bindings with this metadata retain a normalized simulation phase when blend
parameters change, including across save and restore. Do not invent or edit
durations. For a legacy binding without them, regenerate a model candidate from
its existing source with generate_game_asset, then explicitly install it with
install_native_game_asset. Installation rederives the durations from its bytes.
Until then, blends keep legacy elapsed-time sampling and validation reports
legacy_animation_phase.
3D rendering
Set every environment field with update_scene. It replaces the whole
environment, so send the existing background, ambient, fog, shadows,
sky and postProcessing with the changed field. Capture the scene and view
the frames to review a change.
environment.fog accepts either linear { color, near, far } or height
{ kind: "height", color, density, baseHeight, heightFalloff, maxOpacity }.
Height fog replaces linear fog and integrates exponential density along the
camera ray. density (0.000001 to 10, default 0.02) is extinction per world
unit at baseHeight (-10000 to 10000, default 0). heightFalloff (0.000001
to 100, default 0.2) reduces density above that height. maxOpacity (0 to 1,
default 1) limits coverage. Omit fog to disable it. Both camera projections,
rigid and animated models, and 3D particles receive scene fog.
Add an image projection with decal3d: { assetId, receiverId, size: { x, y, z }, opacity, renderOrder }. assetId names a verified PNG, JPEG or WebP image
slot. Browse a staged image with browse_native_game_assets, then use its
binding with install_native_game_asset. receiverId names one existing
non-template primitive or rigid model entity in the same scene. Use a separate
projector entity whose transform3d positions and rotates the projection box.
Its scale multiplies size. Parent the projector to the receiver to keep the
decal attached as it moves. Otherwise the projection volume stays in world
space. Each size axis is 0.001 to 1000 world units,
opacity is 0 to 1 (default 1), and integer renderOrder is 0 to 31 (default
0). Higher order draws later. Ties follow entity order.
A scene accepts at most 32 authored decals. Projection follows moving rigid
receivers and selected visible model nodes, respects foreground depth, and
supports image transparency. Skinned, morphed or instanced receivers, and
transparent or alpha-masked receiver materials, report a diagnostic instead of
projecting. A receiver with animator3d is invalid. Projection also reports a
diagnostic when its bounded node, triangle or output work exceeds the renderer
limit. Decals affect presentation only and never add collision surfaces.
environment.shadows is { enabled, mapSize, extent, cascades? }. Without
cascades, the one shadow-casting directional light draws a single map that
covers extent meters around the light. For scenes deeper than about 30 m, add
cascades: { count, split, maxDistance }. It splits the camera view into
count maps (2 to 4, default 3) up to maxDistance meters (default 200), so
near shadows stay sharp and far shadows stay continuous. split (0 to 1,
default 0.5) gives nearby cascades more resolution as it rises. extent is
ignored while cascades are on. Only one directional light can cast shadows.
Every mesh with castShadow (true by default on primitives and models) draws
again into each cascade it reaches. The nearest cascade re-renders every frame,
the second every other frame, and the rest every fourth frame or when the view
moves past their drift limit. Use 2 or 3 cascades, set maxDistance to how far
the player can judge a shadow, and set castShadow: false on terrain and
ground, which still receive shadows. Validation warns with
large_shadow_caster when a casting mesh 100 units or wider sits in a scene
with cascades. Each 2048 map holds 16 MiB of depth.
Point and spot lights cast shadows when their light3d.castShadow is true.
At most 4 of them may cast per scene. Validation rejects a fifth. If spawned
prefabs push a frame over the budget, the extra lights render unshadowed and
the capture stats report one diagnostic. Each local shadow re-renders the scene
(six times for a point light), so enable it only on lights that need it.
Every light accepts shadowBias (-0.01 to 0.01) and shadowNormalBias (0 to
1), both default 0. Raise shadowNormalBias to about 0.02 to 0.05, or set
shadowBias to about -0.0005, when lit surfaces show striped shadow acne. Too
much bias detaches shadows from their casters. Set lights with update_entity.
environment.sky sets the background and image-based lighting. Omit it, or use
{ kind: "color" }, for the solid background color without environment
lighting. { kind: "procedural", sunEntityId?, turbidity, rayleigh, groundColor, intensity } renders a physical sky. Its sun follows
sunEntityId, which must name a directional light in the same scene. Without
it, the sun follows the scene's first directional light. Rotate that light to
move the sun. turbidity (1 to 20, default 10) adds haze. rayleigh (0 to 4,
default 2) deepens blue and sunset tones. groundColor fills the lower
hemisphere. intensity (0 to 8, default 1) scales both the sky background and
its reflections. Each change of sun direction re-renders the sky cube and its
environment map, so a sun that turns every tick rebuilds them every tick. Keep
the sun still, or turn it in rare steps. Metal and glossy materials reflect the sky. Ambient light
still adds on top, so lower ambient.intensity when the sky lights the scene.
HDRI skies are not available yet: an HDRI asset cannot be installed.
environment.postProcessing grades the rendered image. It is presentation only
and never changes simulation or snapshots. enabled: false keeps the settings
and renders as if they were absent. The fields are:
exposure(1/32 to 16, default 1) multiplies scene light before tone mapping.toneMappingisaces(default),agx,neutralorlinear. AgX keeps saturated highlights from turning white. Neutral keeps base colors closest to their authored values. Linear scales by exposure and clips.bloom{ threshold, softness, radius, intensity }adds glow around light abovethreshold(0 to 8, default 0.85). Emissive materials and bright highlights bloom.softness(0 to 0.5) widens the threshold transition.radius(0 to 1) spreads the glow, andintensity(0 to 4) sets its strength.vignette{ intensity, radius, softness }darkens the corners.intensity(0 to 1) of 1 reaches black. Darkening starts atradius(0 at the center, 1 at the corner) and reaches full strength oversoftness.antialiasismsaa(default),fxaa,smaaornone.smaagives the cleanest edges after the other effects.fxaais cheaper and softer.
The passes run in this order: bloom, exposure and tone mapping, vignette,
antialiasing. Exposure and tone mapping alone cost nothing extra. Bloom,
vignette, or an antialias other than msaa renders through extra full-screen
passes, and the background color then passes through tone mapping too. Bloom
adds a bright pass and a chain of five blur levels, the costliest effect. SSAO,
depth of field, LUT grading and chromatic aberration are not available in 3D.
Input bindings
Action and axis names come from inputActions and inputAxes. The optional
document-level inputBindings maps each name to physical controls. Set it with
set_game {input_bindings}, or pass input_bindings: null to return to the
defaults. The op replaces the whole map:
actions:{ action: [binding] }. A binding is{kind: "key", code}with aKeyboardEvent.codesuch asKeyJ,{kind: "keyValue", key}with aKeyboardEvent.keysuch aszor+(letter case is ignored, and a key that akeybinding names by code does not also match),{kind: "mouseButton", button},{kind: "gamepadButton", button}in the standard gamepad layout,{kind: "gamepadAxis", axis, direction: "negative"|"positive", threshold?},{kind: "touchButton", label?}or{kind: "touchStick", direction}.axes(3D only):{ axis: [binding] }with{kind: "keys", negative, positive}code lists,{kind: "gamepadAxis", axis, deadZone?, invert?},{kind: "gamepadButtons", negative, positive}or{kind: "touchStick", axis: "x"|"y", invert?}. The binding with the largest magnitude sets the value.look(3D only): a list of{kind: "mouse", sensitivity?, invertY?},{kind: "gamepadStick", xAxis?, yAxis?, deadZone?, speed?, invertY?}and{kind: "touchDrag", sensitivity?, invertY?}.
A listed action or axis uses exactly the listed bindings, and an empty list
unbinds it. An unlisted one keeps the defaults generated from its name. In 2D,
arrows and WASD are left/right/up/down and Space is space, by key
position. Any other action is a keyValue binding of its name, so it follows
the player's keyboard layout. In 3D, fire is the left mouse button and F,
jump is Space, respawn is R, and moveX/moveZ read WASD and arrows.
Defaults add the gamepad d-pad and left stick for directions and movement,
button 0 for jump/space, the right trigger for fire, the right stick for
look, and touch buttons for every other action. Binding an undeclared action or
axis is a validation error.
Bindings only turn physical input into input frames. They never enter the
simulation, so playtests, replays and snapshots use the same pressed,
justPressed, axes and look frames. Gamepads are polled once per tick. The
3D players request mouse capture on click only when mouse look is bound.
Mouse actions work on the acquisition click and when capture is unavailable or
rejected. Explicit look: [] disables capture. HUD buttons own their presses
before gameplay mouse or touch controls, and letterbox bars do not hit the HUD.
On a touch screen, editor play, public players and both standalone players
show a stick when an action or axis has a touch stick
binding and one button per action with a touch button binding. The 3D player
also turns the camera by dragging on the right half. Input pressed while play
is paused is dropped. Player-facing rebinding is not stored yet.
HUD widget tree
ui on the document holds HUD widgets drawn in every scene. ui on a scene
holds widgets drawn above them in that scene. Set either with
set_ui {scene_id?, ui}, or ui: null to remove it. The op replaces the whole
tree: {safeArea?, focusNavigation?, nodes: [node]}. 2D and 3D documents
take the same tree and draw it with the same layout.
Every node has kind, id, and optionally parent, anchor, pivot,
offset, width, height, visible and opacity. anchor is a point
{x, y} in 0..1 of the parent's rectangle, or of the HUD rectangle for a root
node, so {x: 1, y: 0} is the top-right corner. pivot is the node's own point
placed there and defaults to anchor. offset moves the node in HUD pixels.
The 2D HUD rectangle is the camera view at pixelsPerUnit pixels per unit, so
a 16 by 9 camera at 32 pixels per unit gives 512 by 288. The 3D HUD rectangle
is presentation.hudWidth by hudHeight, stretched over the viewport. The
exported 3D player also renders the scene at hudWidth by
hudWidth / aspectRatio, so hudWidth sets the render resolution and the cost
of every full-screen pass. A HUD value that changes repaints the whole HUD and
uploads it as a texture. With
safeArea (default true) root anchors stay inside the device's safe-area
insets.
panel {color?, cornerRadius?}fills its rectangle and holds children placed by their anchors.image {assetId}draws an image asset. 3D documents bind no images, so a 3D image node fails validation.text {text, size?, color?, align?, fontId?}sizes itself from its text unless it has a width.bar {source?, value?, max?, color?, background?, direction?}fillsvalue / max(max defaults to 1).source: {kind: "health", entityId}reads the entity's health and its health behavior's maximum.button {action, text?, background?, size?, color?, fontId?}presses a declared input action while a mouse button or a finger holds it.stack {direction?, gap?, padding?, align?}lays children in a column or a row and sizes itself from them.grid {columns, gap?, padding?}lays children in cells as large as the largest child.
A child must come after its parent, and only panels, stacks and grids hold
children. Panels, images, bars and buttons need width and height. Ids are
unique across the document tree and each scene tree.
Scripts change nodes with {kind: "ui", id, text?, value?, max?, visible?}.
text applies to text and buttons, value and max to bars, and visible
to any node. A change lasts until the scene changes and is saved in snapshots.
A command for a missing node, or a field the node does not have, fails the
step. An onDestroy hook may return ui commands.
A button press reaches scripts as its action in pressed and justPressed,
exactly like a key, so replays and snapshots need nothing extra. In 3D a click
on a button reaches the HUD only while the mouse is not captured. With
focusNavigation: true, the gamepad D-pad moves focus between visible buttons
and button 0 presses the focused one. While the tree shows a button, those
gamepad buttons drive the HUD instead of the game's bindings. Existing hud
labels still draw, above the tree.
Distance culling and frame budgets (3D)
Both are presentation only. They never change simulation, snapshots or replay.
set_performance {performance}replaces documentperformance.nullremoves it.cullLayersmaps a layer name to{maxDistance}(at most 32 layers).budgetssetsdrawCalls,triangles,particlesandvoices, and the time budgetstickMs(tick p95, default 4) andframeMs(frame p95, default 16.6).update_entitywithset: {renderCulling: {layer?, maxDistance?}}hides that entity and its children when the game camera is farther than the distance from the entity's origin. The nearest setting in the parent chain wins, and an entity's ownmaxDistancewins over its layer's. A layer must be declared, or validation reportsmissing_cull_layer.- Distance is measured to the entity origin, not to its nearest surface. Cull props and small decoration. Do not cull large scenery such as terrain or buildings, because they disappear while the camera is still close to their edges. Never cull the player or anything the player must see to win. The editor camera shows every entity.
- Budgets left out use the player defaults: 1000 draw calls, 1,000,000
triangles, 4096 particles and 24 voices. The standalone player warns in the
browser console once each time a budget is exceeded. The standalone players
and 3D capture count live particles. The 2D player uses the default particle
budget because 2D documents have no
performance. nodetool game bench,nodetool game captureandnodetool game perfcheck these budgets and exit 1 on an overrun with--fail-on-budget. Loadnative-game-performancefor the targets, the budgets per game type and the measure-fix loop.
Editor panels the user can open
Point the user to these when they work in the game editor alongside you.
- Run 10 s diagnoses the draft in an independent session and reports the first failing tick and script time by entity. Replay to tick rewinds the active play session after a script error. Ask the assistant passes the script context to the game assistant.
- Editing an existing script source during play recompiles it without resetting the tick or script state. A compilation failure keeps the previous source. Changing script form between a function and lifecycle object requires Restart from tick 0. Other draft changes also require a restart.
- The Console panel lists play-session errors, script errors with their tick, reload compilation errors, script log lines, Run 10 s failures and validation errors.
- The Assets panel shows the
browse_native_game_assetscatalog with thumbnails. Generate and Regenerate rungenerate_game_assetfor image, speech and music slots. Use binds an older candidate and moves the slot's frame and tile bindings onto it. - Ctrl+K (Cmd+K on macOS) opens the command palette, where Edit keyboard shortcuts rebinds editor shortcuts.
- When the script pane reports a draft conflict, local typing stays unsaved until the user picks Keep my version or Use draft version.
- Restoring a revision asks for confirmation because it replaces the draft and clears its undo history. The newest 100 published revisions are retained.
3D forces and torques
Return {kind: "addForce", force: {x, y, z}} to accelerate the issuing
entity's dynamic body, {kind: "addTorque", torque: {x, y, z}} to turn it,
or {kind: "addForceAtPoint", force: {x, y, z}, point: {x, y, z}} to apply
a force at a world-space point. An offset point also produces torque. All
vectors use world space, even when the body is rotated. Each component must
be finite and between -1000000 and 1000000.
Loads accumulate within the current simulation tick and clear after its
physics step. Return the commands on subsequent ticks to keep applying them.
Use impulse for an immediate velocity change. Forces require the issuing
entity's active dynamic body. Static, kinematic, missing and inactive bodies
reject them. A nonzero load wakes the body, while a zero load keeps a sleeping
body asleep. These commands are available only in 3D scripts.
04 同仓库更多 Skills
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.
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/wan-2.6/text-to-video, alibaba/wan-2.6/image-to-video, wan-video/wan-2.6-t2v, wan-video/wan-2.6-i2v, wan-video/wan2.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.
video-workflow
Build a reusable NodeTool video workflow for batches or repeated inputs. For a single finished piece, use a storyboard skill.
video-clone
Analyze a reference video and recreate its shot structure with a new product or cast in NodeTool.
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.
veo-3-prompting
Prompt Google's Veo 3 video line — the five-element shot/setting/subject/action/dialogue 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/veo3.1 and its fast, lite, image-to-video, first-last-frame and reference-to-video routes, fal-ai/veo3/image-to-video, google/veo3.1 on atlascloud, google/veo-3 and google/veo-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.