Hold in tests that a graph edit reaches a running behavior (#216)

The issue said a behavior that runs in the editor keeps the graph
it started with, until it is switched off and on. Measured before
building anything, that is no longer so. An edit runs from the next
frame on. Since when, was not found out.

- No code changes. A command raises the project event, EditorRun
  refreshes, and syncFromModel of the behavior system empties the
  graph cache.
- Decided in the issue: the instance carries on and is not started
  over. Its variables and timers stand, and On start does not fire
  again. A restart would write what On start writes or spawns into
  the saved scene with every edit.
- Starting over stays what it was: Run in editor off and on, or
  Play.
- Module 43 of the e2e no longer switches the behavior off and on
  before it measures the changed layers of a ray cast.

Tests:
- Gate on melektaus-atx for this change alone: 212 files, 3264
  tests (3260 + 4), exit 0. The gate right before this commit ran
  on the tree of this and the next commit together: 213 files,
  3278 tests, exit 0.
- e2e 18,20,43 on melektaus-atx: 57 checks, all pass. The check in
  43 is the old one, it now holds without the workaround.
- Four unit tests in editor-run.test.ts: a changed node setting, a
  node wired in and one cut off, a flow hung on On start that does
  not fire until the switch goes off and on, and an undo.
- One counter-probe: without the emptied cache the wiring test
  fails. The setting test does not, settings change in place.
- No full run and no smokes. The sum of e2e checks stays 1676.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-02 17:59:19 +02:00
1 parent c7dc0e4733
commit b45bbcc976
2 files changed
+77 -9

No files matched your search

+77
View File
@@ -5,6 +5,12 @@
import { describe, expect, it } from 'vitest';
import { CreateAssetCommand, SetAssetPropertyCommand } from '../../src/core/commands/assetCommands';
import { CreateContainerCommand } from '../../src/core/commands/containerCommands';
import {
AddNodeCommand,
ConnectCommand,
RemoveNodeCommand,
SetNodeParamCommand,
} from '../../src/core/commands/graphCommands';
import { instanceChildId } from '../../src/core/model/instance';
import { ClearOverrideCommand, SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands';
import type { BehaviorAsset, ContainerAsset, VariablesAsset } from '../../src/core/model/Asset';
@@ -316,6 +322,77 @@ describe('shared variables of an editor run', () => {
});
});
/** The point of the switch is changing a graph and watching, so an edit must not wait for a restart (#216). */
describe('a graph edited while its behavior runs in the editor (#216)', () => {
function running() {
const f = scene();
f.state.execute(new SetOverrideCommand(f.sceneId, f.logic, 'runInEditor', true));
f.frame(0.1);
const nodeOf = (type: string) => f.behavior.graph.nodes.find((n) => n.type === type)!;
return { ...f, nodeOf, boxY: () => f.object(f.box).position.y };
}
it('runs with a changed node setting from the next frame on', () => {
const f = running();
expect(f.boxX()).toBeCloseTo(10);
f.state.execute(new SetNodeParamCommand(f.behavior.id, f.nodeOf('Move').id, 'scale', 1000));
f.frame(0.1);
expect(f.boxX()).toBeCloseTo(110);
});
it('runs a node wired in while it runs, and no longer one that was cut off', () => {
const f = running();
const down = new AddNodeCommand(f.behavior.id, 'Move', 0, 0, { dy: 1, scale: 50 });
f.state.execute(down);
const port = (node: { id: string }, key: string) => ({ node: node.id, port: key });
const [update, owner, move] = [f.nodeOf('OnUpdate'), f.nodeOf('Owner'), f.nodeOf('Move')];
f.state.execute(new ConnectCommand(f.behavior.id, port(move, 'exec'), port(down.node, 'exec')));
f.state.execute(new ConnectCommand(f.behavior.id, port(update, 'dt'), port(down.node, 'dt')));
f.state.execute(new ConnectCommand(f.behavior.id, port(owner, 'object'), port(down.node, 'object')));
f.frame(0.1);
expect(f.boxX()).toBeCloseTo(20);
expect(f.boxY()).toBeCloseTo(5);
f.state.execute(new RemoveNodeCommand(f.behavior.id, move.id));
f.frame(0.1);
expect(f.boxX()).toBeCloseTo(20);
expect(f.boxY()).toBeCloseTo(5);
});
/** Decided in #216: the instance carries on, so what `On start` writes is not written a second time. */
it('carries on rather than starting over: a flow hung on On start afterwards does not fire', () => {
const f = running();
const start = new AddNodeCommand(f.behavior.id, 'OnStart', 0, 0);
const down = new AddNodeCommand(f.behavior.id, 'Move', 0, 0, { dy: 1, scale: 50 });
f.state.execute(start);
f.state.execute(down);
const port = (node: { id: string }, key: string) => ({ node: node.id, port: key });
f.state.execute(new ConnectCommand(f.behavior.id, port(start.node, 'exec'), port(down.node, 'exec')));
f.state.execute(
new ConnectCommand(f.behavior.id, port(f.nodeOf('Owner'), 'object'), port(down.node, 'object')),
);
f.frame(0.1);
expect(f.boxX()).toBeCloseTo(20);
expect(f.boxY()).toBe(0);
// Off and on is what starts it over: the world is built anew, and `On start` runs.
f.state.execute(new ClearOverrideCommand(f.sceneId, f.logic, 'runInEditor'));
f.frame(0.1);
f.state.execute(new SetOverrideCommand(f.sceneId, f.logic, 'runInEditor', true));
f.frame(0.1);
expect(f.boxY()).not.toBe(0);
});
it('takes an undo of the edit back the same way', () => {
const f = running();
f.state.execute(new SetNodeParamCommand(f.behavior.id, f.nodeOf('Move').id, 'scale', 1000));
f.state.undo();
f.frame(0.1);
expect(f.boxX()).toBeCloseTo(20);
});
});
describe('the game nodes in the editor run (#167)', () => {
it('ignores Pause game and says so: the scene keeps moving', () => {
const f = scene();
-9
View File
@@ -382,15 +382,6 @@ export async function queries(t) {
settings.includes(`Layers: ${names[0]}`),
JSON.stringify(settings),
);
// A running instance keeps the graph it started with – measured here: the setting stands in the
// project at once, what the node casts does not. So the behavior is switched off and on, which
// ends the instance and builds a new one on the graph as it now stands.
await select({ kind: 'object', holderId: 'scene1', objectId: 'q-eyebox-go', objectIds: ['q-eyebox-go'] });
for (let i = 0; i < 2; i++) {
const runRow = await inView(t, '__gm.inspectorRect()', "__gm.inspectorField('Run in editor')");
await click(runRow.x, runRow.y);
await settle();
}
await sleep(600);
const through = reached();
check(