d6fd000 wrote the behaviors that played an animation on the player
itself. A player is plain data, and those behaviors are entities of
the run with their parents and children. The actor smoke reads the
world as JSON and threw "Converting circular structure to JSON" as
soon as its graph had played a walk animation.
- The starters live in a WeakMap next to the player and are read
with startersOf. The player carries no field for them any more.
- The animation system reads them from there when a pass ends. What
a behavior hears does not change.
- Only the hook the check scripts read the world through was hit.
The editor and a running game never serialise a player.
Tests:
- Gate on melektaus-atx: 219 files, 3400 tests, CHECK EXIT 0. It
ran on the working tree, which also held the fix to e2e 47 and
the kept panel sizes that are not committed yet. Seven of the
tests belong to those.
- One new unit test: a player played by an asker that reaches back
to its holder stays plain data, through JSON and structuredClone,
and names the asker once. Its counter-probe bites.
- Smokes on melektaus-atx: 24, 19 and 21 checks, all pass. Before
the fix the third stopped after 9 checks.
- e2e 6,17-26 on melektaus-atx: 480 checks, all pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A behavior that played an animation on the parent of its owner never
heard it finish. On animation finished listened to the owner and to
what stands below it, and the parent stands above.
- Play animation notes on the player which behavior asked for it.
- On animation finished hears, as before, every animation on the
owner or below it, and now also every animation the behavior
played itself, on whatever object. Its output object names that
object.
- Two behaviors that both played the same animation on the same
object each hear its end once.
- A behavior that is called by Run behavior or heard by an On node
counts as the behavior it sits in.
- The description of the node and the engine prompt say so.
Tests:
- Gate on melektaus-atx: 218 files, 3392 tests, CHECK EXIT 0.
- Two new unit tests: the behavior hears the animation on the parent
of its owner while a bystander does not, and two starters hear it
once each.
- Both counter-probes bite.
- e2e 6,17-26 on melektaus-atx: 480 checks, all pass.
- No new e2e check, and nothing was looked at in the browser. The
rule for called and heard behaviors has no test of its own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A graph could start, pause and stop an animation and hear it finish,
but it could not ask in between whether one runs or how far it is.
Two data nodes answer that now.
- Animation playing takes an object and an animation and gives
playing, progress and time left (s). It answers at the moment it is
read: right after Play animation in the same pass it says on, and in
the frame On animation finished fires it says off.
- With Animation at None it answers for the running animation started
last on the object.
- Animation length gives the seconds of one pass, from the timeline
and the frame rate alone.
- The rule is a pure function in src/core/model/animation.ts
(playerStatus, leadingPlayer). The nodes reach an animation's
length through a new field animation on GraphLookup.
- animationPort is exported so both nodes show the same picker as
Play animation.
Decided in the issue: a Once (reset) counts as running while it blends
out, and progress and time left also hold for Loop and Ping-pong, at
the place of the playhead. On Ping-pong progress falls again on the
way back and time left counts to the next turn.
Decided here, the issue says nothing on it:
- A paused animation does not count as running. Progress and time
left stand still.
- One that Stop animation fades out does not count as running, and
its time left is 0.
- Time left of a Once (reset) includes its blend out. While it blends
out progress is 1 and time left is the rest of the fade.
Finding, not fixed: on the node the name of the chosen animation
overlaps the output label progress in the same row. That comes from
the fixed node width in NodeView and was seen in the Fit view only.
Tests:
- Gate: 198 files, 2903 tests (2891 + 12), exit 0
- e2e 1-6: 282 checks passed, 06 has +2 for block 6z-ix
- Nine unit counter-probes fail, 1 to 4 tests each
- The browser counter-probe fails 6z-ix with ["false","false"]
- e2e total 1615 is computed, not measured in a full run. The smokes
were not run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A behavior variable switched private is hidden from the placed object
and from the Properties node, but the row dialog of an animation kept
offering it. Rows that exist already stay as they are, as decided in
the issue.
- offeredDefs in src/core/model/animation.ts is what the dialog offers
now: what the target can animate, less private variables and less
what another row animates already. The filter moved there from
RowDialog.ts, so it can be tested without the editor.
- animatableDefsOf is unchanged. The label and the runtime read it, so
a row on a variable that went private keeps playing and keeps its
label.
- Deviation from the proposal: under Change target the row's own
variable stays on offer, private or not. The dialog would open with
no property and a disabled button otherwise. Other private variables
are not offered there either.
- The command does not refuse a private variable. Only the dialog does
not offer it.
Finding, not fixed: e2e 56 fails when it runs alone, in 56a-ii from
#257 (typing "9" did not arrive: focus is BODY). With 55 before it the
module passes, so a full run does not show it. It was not measured on
HEAD without this change.
Tests:
- Gate: 198 files, 2883 tests (2882 + 1), exit 0
- e2e 55,56: 27 checks passed, 56 has +56e
- e2e 25: 31 checks passed, it uses the same dialog
- Three unit counter-probes each fail the new test
- The browser counter-probe fails 56e, mood shows up in the dialog
- e2e total 1610 is computed, not measured in a full run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The timeline offered X and Y as position rows but never Z. The list
of position rows was simply written without it when animations came
in with #97. A swinging weapon could therefore not go behind its
character.
- Z is the third position row, after X and Y, on every placed root
or object. A scene and a behavior still offer no position.
- Play and preview already read and write any position row by name,
so the one entry in POSITION_KEYS is all the code change.
- The draw order is sorted on every render, and play writes z back
into the scene each frame. A keyed Z therefore takes effect at
once. A blended 4.5 sits above 4 and below 5.
Tests:
- ./check.sh: 165 files, 2416 tests, CHECK EXIT 0.
- 3 new unit tests in tests/core/animation.test.ts: Z is offered
after X and Y and not on a behavior or scene, a running game moves
the object from behind to in front of another one, and play,
scene write-back and preview agree on z.
- e2e 25: ALL PASSED, 29 checks. The new check reads X, Y, Z as the
first rows of the row dialog. Check sum 1396 -> 1397.
- Counter-check: without z in the list all 3 new unit tests fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Until now only the editor toolbar could pause a running game. A pause
menu inside the game had no way to stop the world while its own graph
kept running, and there was no slow motion.
- Five new nodes: Pause game, Resume game, Is game paused, Set time
scale and Time scale. Pause and resume take effect on the next frame.
- A game pause stops world time, physics, animations, particles and
shader time. Only behaviors with the new "Run while paused" switch
keep running.
- Set time scale 0 freezes the world but graphs keep running, as
agreed in the issue.
- Play and Stop reset the time scale to 1, Load scene keeps it. The
pause belongs to the scene, so Load scene and Restart unpause.
- The toolbar pause stays separate and also freezes the menu. When the
game pauses itself the status line reads "The game paused itself".
- While paused, collision events are not reported, sounds keep
playing, and the editor run ignores both nodes with a log line.
Findings:
- The first status line was longer than any before and got cut with
an ellipsis. It is shorter now.
- The e2e harness did not know the key "p". It does now.
Tests:
- ./check.sh: 133 files, 1968 tests, exit 0 (before 132 / 1952)
- npm run build: exit 0
- GM_STEPS=18 fresh: ALL PASSED, 33 checks (before 26)
- 16 new unit tests: 8 catalog cases, game-pause.test.ts, editor run
- 7 new e2e checks in block 18i, driven by real keys in the game
- Six counter-checks all bite: behavior filter, collision lock, world
time, editor run, clock reset and the game clock in the browser
- Check total 1217 in README is computed (1210 + 7), CI measures it
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stop animation gets a blend (s) port that fades the animation back to what
lies underneath. Play animation gets blend out (s) for Once (reset) and a
restart port that starts a running, paused or fading animation from frame 0.
Co-Authored-By: Claude Opus 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>
An animation asset is a list of pictures (whole textures or frames) with
frames per second and Loop/Once/Ping-pong playback. A sprite wears it
through its texture property, so the picker, instance overrides and the
Properties node work unchanged; there is no dedicated play node.
A different animation starts from its first frame, the same one set again
runs on, so a graph may choose Run or Idle every frame. The clock lives on
the entity, not in props, which the world writes back as overrides. The
AnimationSystem ticks before behaviors, and On animation finished fires
once when a Once animation reaches its end.
The Animation tab plays a preview above a frame strip: reorder by dragging,
Delete, duplicate, + Frames, pause/resume, and wheel zoom about the pointer
with pan and Fit. Scene and Container tabs show the first frame; edit mode
has no clock. Right-clicking a texture creates an animation from its frames.
Button now measures its label with the bold font it draws in, which cut
the Animation tab and red dialog buttons short.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>