Let each variable of a behavior choose whether calls keep it (#229)
game-maker / gate (push) Successful in 3m13s
game-maker / suite (push) Failing after 8m46s

The first version of #229 put a Keep state switch on the Run behavior
node. The user wanted it where the variable is declared instead. Whether
a value lives on belongs to the variable, not to every call site.

- The node switch keepState is gone again. RunBehavior, CallResult and
  nested.ts are back to what they were before #229.
- BehaviorAsset.keptVariables lists the kept names. It lives on the
  asset and not in PropertyDef, because that is the schema of every
  inspector row.
- A variable added from now on starts with Keep on. Variables in
  existing projects start with Keep off, so old projects run unchanged.
- SetVariableKeptCommand flips the switch as one undo step. Add, remove
  and rename carry the list along. Loading drops names that are not
  declared.
- The interpreter keeps only the listed variables, per Run behavior
  node and per object as before. The others start at their defaults.
- Local state machines in a called behavior start empty on every call
  again, as before #229. Global and scene machines are unchanged.
- The inspector shows a Keep button in each variable row of a behavior.
  Its state is part of the asset signature, so a click redraws the row.
  Without that the button kept its old look until the inspector was
  reopened.

Found: the project Garden of Sacrifices still has keepState = true on
the call Player -> Character Movement. It is ignored now. Direction
there predates the switch, so Keep is off until set by hand.

Tests:
- ./check.sh: 163 files, 2392 tests, CHECK EXIT 0 (before 2388)
- tests/core/keep-state.test.ts rewritten, 15 tests
- e2e 19: ALL PASSED, 35 checks. 19c-ii now clicks the Keep button
  of a new variable: on when added, off by a click, on again, off
  after undo.
- Counter-checks, each fails its own test: keep list ignored (2),
  states kept (1), new variable not kept (8), rename drops the list
  (1), no Keep button (e2e 19c-ii).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-09-25 15:50:59 +02:00
1 parent 7c70245c79
commit 454d202109
14 files changed
+333 -149

No files matched your search

+28 -10
View File
@@ -128,19 +128,37 @@ 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();
// 19c-ii. Keep (#229) is a switch on each variable of the called behavior, not on the node: a new
// variable starts kept, a click on its Keep button lets it go, and undo keeps it again.
const kept = async () => JSON.stringify((await asset('nb-double')).keptVariables ?? []);
const nodeKeys = Object.keys((await asset('nb-caller')).graph.nodes.find((n) => n.id === 'nb-call').params);
await t.select({ kind: 'asset', assetId: 'nb-double' });
await clickText('+ Variable');
await clickText('Number');
const keptAdded = await kept();
const keepButton = await locate('Keep');
if (keepButton) await click(keepButton.x, keepButton.y);
const keptClicked = await kept();
// A second click has to flip it back: a button drawn for the old switch would clear it again.
if (keepButton) await click(keepButton.x, keepButton.y);
const keptReclicked = await kept();
await undo();
const keptUndone = await kept();
await undo();
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()}`,
'a variable has a Keep switch: on when added, off by a click, on by the next, off after undo',
!nodeKeys.includes('keepState') &&
keptAdded === '["var1"]' &&
Boolean(keepButton) &&
keptClicked === '[]' &&
keptReclicked === '["var1"]' &&
keptUndone === '[]' &&
(await kept()) === '[]',
`node=${nodeKeys} added=${keptAdded} button=${JSON.stringify(keepButton)} clicked=${keptClicked} reclicked=${keptReclicked} undone=${keptUndone} after=${await kept()}`,
);
// Back to the call, which is where 19d looks.
await t.select({ kind: 'node', assetId: 'nb-caller', nodeId: 'nb-call', nodeIds: ['nb-call'] });
// 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.