The List tab could show one list at a time and was the only place to
see its items. A list now shows its items right where it lives, and a
new Variables tab shows all variables of an asset or placed object.
- InlineList draws the items of a list under its row in the inspector,
in struct fields and in the Variables tab. A list inside a list or a
struct field is drawn the same way, one step indented.
- The index column is the indent. Items sit under their list without an
own margin, and the index column is 16 px wide instead of 28 px.
- Above 20 items (INLINE_LIMIT) the rest collapses into "... N more -
open in tab". During a run the rows show the values from the game.
- ListHead puts "N items" and "+ Item" in one row. "N items" opens the
list in a tab. A separate Open button squeezed the counter to zero
width in the narrow inspector column.
- VariablesEditor shows every variable of a behavior asset, a variables
asset or a placed behavior. On an asset it edits the declarations. On
a placed object an edit only overrides that object, with the usual
reset and push buttons.
- Opening a list inside a tab adds a new tab and keeps the open one.
valuesKey gives each list its own document, so a list that is already
open is shown instead of opened twice.
- Titles read "Pickup · Variables" or "Listy · nums · [3]". The tab
closes when its asset or object is deleted.
- Lists on node inputs stay inline only, a node owns no variables.
- The List tab, ListEditor and the __gm.list hook are gone.
Tests:
- ./check.sh: 181 files, 2645 tests, CHECK EXIT 0
- GM_STEPS=1,21,22,32: ALL PASSED, 123 checks
- e2e 21 is rewritten for inline lists and the new tab, 22 and 32 are
adapted. Sum in README: 1486 (+6 in 21, -1 in 22), counted by hand.
- Counter-probes fail as expected: sub-lists not told apart (1 unit
test), sub-list replacing the open tab, all items drawn inline (60
rows instead of 20), no live values (0 rows at 24 items in a run).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A spawned object was only reachable in the run that spawned it. The
Spawn output could not go into a variable because "object" was a
port-only kind, so the next run had no way back to the instance.
- "Object" is now a variable kind. The reference lives for the run
only. The file stores the declaration and an empty value.
- The live reference sits in slots beside the variable bag
(graph/objectSlots.ts), not in it. The bag is the behavior's props,
and syncToModel would have written an object into the scene.
- A destroyed object, or one from a restarted or left scene, reads as
nothing. A dead reference is dropped from its slot.
- Keep (#229) hands the slots of kept variables on to the next call
of a sub-behavior.
- Properties of kind Behavior reads and writes another behavior's
object variable through the same slots.
- The inspector shows "(null)" for it and offers no input.
- A persistent Variables asset with an object variable shows a
warning under "Persistent". Saved values never keep the object,
every run starts it at (null).
Objects inside lists and struct fields are left out on purpose.
Emptiness is checked with Equals against an empty b. A node "Is
valid" does not exist yet.
The test helper graphEditor().variables() now also reads the kinds
of a Variables asset a node points to.
Tests:
- ./check.sh: 169 files, 2463 tests, CHECK EXIT 0
- e2e 6 + 23: ALL PASSED, 146 checks (new 23k, 2 checks)
- e2e 32: ALL PASSED, 18 checks (new 32f-ii)
- 10 unit tests in tests/core/object-variable.test.ts, one of them
runs a persistent asset over two runs
- 5 counter-probes (value in bag, isAlive always true, no Keep
hand-over, Properties without slots, object in saved values) each
fail their test
- e2e check total 1406, computed only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An asset could only be picked as a setting on the Spawn node. It could
not be wired in, held in a variable or put into a list. Finding an
object below the own one needed a scene-wide search by name.
- "Asset" is now a kind like Animation or Sound: port, variable, list
item and a new "Asset" literal node, with its own cube icon.
- Spawn and Is asset have an `asset` input. It uses the key of the old
setting, so a saved asset reads as the value of the input and older
projects keep spawning without a migration.
- New node "Child by name" searches below an object level by level.
A child wins over a grandchild of the same name. Nothing found or no
object gives null.
Tests:
- ./check.sh: 168 files, 2453 tests, CHECK EXIT 0.
- 9 new unit tests in tests/core/asset-kind.test.ts, catalog cases for
Spawn, Is asset, Child by name and the literal.
- e2e 6z-ii (2 checks): node rows and a real drag of the literal onto
`asset`. e2e 6: 119 checks passed. Spawn checks in 3 and 9 passed.
- Counter-checks: depth-first search fails 1, Spawn reading only the
old setting fails 3, runtime ignoring the parent fails 1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Play, Pause and Stop animation take the animation on a port instead of a
fixed setting, fed by the new Animation literal or a list item tagged
`$animation`. Playing an animation already running keeps its frame but
counts it as started last, so turning back to a walk that never stopped
shows that walk. The actor demo walks through one Play animation over
`ItemAt(walk, facing)` again and stops every animation to stand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Part (a) of the animation rework: model, commands, saving, runtime and nodes.
- AnimationAsset holds a root (sprite, text, container or scene) and rows; a row keys one
property of the root or of an object inside it, keys sit on whole frames.
- The last key decides the length (its frame counts as a frame of its own). The playhead runs
between frames, so numbers and colors blend every tick even at 10 fps; everything else holds.
- Playback gains `once-reset`, which puts back what was there before the start. A row can be
relative, adding to where its target stood.
- Play / Pause / Stop animation act on an object; each instance plays on its own, the later
start wins on a shared property, and a behavior writing the same property still writes last.
- A sprite no longer wears an animation through `texture`. "Create animation from texture"
builds a sprite plus a texture row keyed frame by frame instead.
- The Animation tab shows a placeholder until the timeline editor (part b) lands.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Load scene sets a flag on the World like Stop and Restart do; the swap
happens after the tick, and the last wish of a tick wins. Exactly one
thing crosses over: the SharedState, so global variables and global
machines keep what they know while everything local is built anew.
Loading the scene that is already running rebuilds it, which is what
"reset the level" means when the score must survive.
A run starts either from the start scene (a new project field, set from
the asset panel and marked with an arrow in the list) or from whatever
scene is open. The toggle next to Play says which question is asked and
remembers the answer in localStorage: it is a way of working, not part
of the game.
Sounds no longer end because a scene ends. A voice belongs to the
behavior that started it and stops right after On destroy, so the scene
swap is no longer a special case at all. For the enemy that plays its
explosion and destroys itself in the same breath there is now a
detached input on Play sound.
The scene selector stays usable while a run plays, because a double
click in the asset panel already did the same thing unguarded. It moves
what the editor looks at, not what the run plays.
Sounds follow the road fonts already paved: the file goes to the server
content-addressed under its sha256, the project keeps a named asset that
points at it, and nothing is ever re-encoded. The format is read from the
file's own signature, not from what the browser claims - the same .m4a
arrives as audio/mp4, audio/x-m4a or as nothing at all.
Playing is two nodes. "Play sound" takes the asset and may raise or lower
its volume; every call is its own voice, so two jumps in a row are two
jumps heard. Its loop input is a choice of three - from the asset, on,
off - because a checkbox has to spend one of its two positions on "not
set" and would lose a case: an asset that loops could then never be
played once.
Below the graph core stays browser-free: NodeContext hands out an asset
id, BehaviorSystem resolves it against the project and folds the asset's
own volume and loop into the call, and only viewport/SoundLibrary knows
about Web Audio. A run without a sink plays nothing instead of throwing.
The CI browser is muted, so the ear is no witness: the debug hook counts
started and stopped voices, and step 34 reads that counter.
A state machine is an asset whose graph holds one `State` node per state
(no ports, name as title), edited in a new "State machine" tab that reuses
the graph canvas: right-click places "State n", snap toggle shared with the
Behavior tab via SnapToggle. `statemachine` is a value kind like texture
(port, literal node, variable, struct field, tagged list item).
Behaviors use it through `State branch` (one exec output per state) and
`Set state` (one exec input per state; the interpreter now hands `run` the
entry port). States live per behavior instance in NodeContext.states, with
no initial state. Ports derive from the inline picker or a wired
`State machine` literal; BehaviorLookup became GraphLookup with
behavior(id) and machine(id).
The Graph tab is labelled "Behavior" (key unchanged). Unit tests
(state-machine, kinds, catalog, ports), e2e step 26 and README added.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A kind like colour or texture used to be spelled out in five parallel
lists, its port type, its empty value, its glyph, its label and its list
tag - 22 literal places for texture alone. core/properties/kinds.ts now
holds one row per kind and the rest is derived from it, so adding a kind
is one row. The five literal nodes collapse into literals.ts, the six
kind menus into one helper.
Number, Text, Boolean is now the one order everywhere; the variable and
port menus used to list Boolean before Text.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>