Let a nested behavior keep its variables between calls (#229)
game-maker / gate (push) Successful in 3m3s
game-maker / suite (push) Failing after 8m39s

A Run behavior call started with fresh variables every time, so a
nested behavior could not count, remember or keep a local state
machine across calls. A new "Keep state" switch on the node lets the
callee resume where its last call left off. It is off by default, so
existing projects run unchanged.

- RunBehavior gets the boolean parameter keepState and its node
  description explains it.
- GraphInterpreter keeps the state in keptCalls, keyed by the call
  path, the chain of Run behavior nodes. Two nodes calling the same
  behavior each keep their own state.
- One interpreter exists per behavior instance, so every object gets
  its own state without further code.
- Local state machines of the callee are kept like its variables.
- resumedVariables() matches kept values against today's
  declarations. A new variable starts at its default, a removed one
  drops out, and one whose kind changed starts over.
- The kept state lives as long as the object in the run. It is not
  written to save games.

Limit: a callee input named keepState collides with the switch. The
same already holds for the name behavior, so it is not caught.

Tests:
- ./check.sh: 163 files, 2388 tests, CHECK EXIT 0 (before 2377)
- 10 new unit tests in tests/core/keep-state.test.ts and 1 new
  node catalog case.
- e2e 19: ALL PASSED, 35 checks, with the new check 19c-ii for the
  checkbox, its click and undo.
- Counter probes: ignoring keepState fails 5 tests, state per
  behavior instead of per node fails 1, dropping the outer path
  fails 1, skipping the kind check fails 1, and without the node
  parameter the e2e check fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-09-25 14:41:17 +02:00
1 parent 03e7a78c3d
commit 7c70245c79
9 files changed
+314 -17

No files matched your search

+14
View File
@@ -128,6 +128,20 @@ export async function nestedBehavior(t) {
);
await shot('37-nested-behavior');
// 19c-ii. Keep state (#229) is a checkbox on the node, off until clicked, and one undo step.
const keepState = async () =>
(await asset('nb-caller')).graph.nodes.find((n) => n.id === 'nb-call').params.keepState;
const keepField = await json(`__gm.inspectorField('Keep state')`);
const keepBefore = await keepState();
if (keepField) await click(keepField.x, keepField.y);
const keepClicked = await keepState();
await undo();
check(
'the call has a Keep state checkbox: off, on by a click, off again after undo',
Boolean(keepField) && keepBefore !== true && keepClicked === true && (await keepState()) !== true,
`field=${JSON.stringify(keepField)} before=${keepBefore} clicked=${keepClicked} undone=${await keepState()}`,
);
// 19d. Declaring a port on one behavior reaches the node in the other one, live. This is the
// check that a port list derived from another graph is really derived and not copied.
check('the calling node has no second input yet', !(await portAt('nb-call', 'in2', false)));