Play switches to scene tab; graph tab strictly follows selection

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-07 14:28:43 +02:00
1 parent 9060da615a
commit 551a50ffc9
4 files changed
+26 -6

No files matched your search

+5 -2
View File
@@ -54,8 +54,11 @@ shown on the node and editable in the inspector.
(types are checked; a data input holds one link and is replaced), drag nodes, right-click a
node or port to delete/disconnect, `Delete` removes the selected node, wheel zooms, dragging
the empty canvas pans. Everything is undoable.
- **Live view**: while playing with the Graph tab open, the exec edges and nodes that just ran
light up – the same canvas draws editor and game, so this costs nothing extra.
- **Selection-bound**: the Graph tab shows the behavior the current selection refers to
(asset, placed object or node); with nothing or a non-behavior selected it is empty.
- **Live view**: Play always switches to the Scene tab; switch back to Graph while playing and
the exec edges and nodes that just ran light up – the same canvas draws editor and game, so
this costs nothing extra.
- **Runtime**: `BehaviorSystem` creates one `GraphInterpreter` per behavior entity; graphs only
touch world entities. A runaway loop throws after 1000 steps and stops play with a notice.
+2
View File
@@ -256,6 +256,8 @@ class Editor {
this.world.addSystem(this.behaviors);
this.viewport.setWorld(this.world);
this.state.setMode('play');
// Play always starts on the scene; the graph tab stays reachable for live tracing.
this.showTab('scene');
this.state.setNotice(this.world.firstOfType('camera') ? 'Playing · Esc stops' : 'This scene has no camera – nothing is rendered · Esc stops');
if (this.world.stopRequested) this.stop();
}
+3 -3
View File
@@ -20,8 +20,8 @@ interface PortAnchor {
/**
* The node graph editor for one behavior asset. Shows the graph of whatever behavior the
* selection refers to (asset, placed object or node) and keeps showing it while unrelated
* things are selected, so the user can flip between Scene and Graph tabs freely.
* selection refers to (asset, placed object or node). When nothing or a non-behavior is
* selected the editor is empty – the graph strictly mirrors the selection.
*
* Interactions: drag nodes, drag from port to port to connect, right-click the canvas for
* the node palette, right-click a node or port for delete/disconnect, wheel to zoom, drag
@@ -100,7 +100,7 @@ export class GraphEditor extends Widget {
this.sync();
this.needsFit = true;
this.fitView();
} else if (!this.asset()) {
} else if (!wanted || !this.asset()) {
this.assetId = null;
this.sync();
} else {
+16 -1
View File
@@ -424,9 +424,13 @@ try {
const restored = await graphCounts();
check('undo restores node and edge', restored.nodes === before13.nodes + 1 && restored.edges === before13.edges + 1, JSON.stringify(restored));
// 13e. Live highlight: play with the graph open, hold ArrowRight → the exec edge is hot.
// 13e. Play always starts on the Scene tab; switching back to Graph while playing shows
// the live highlight: hold ArrowRight → the exec edge is hot.
await clickText('▶ Play');
await sleep(200);
check('play switches to the scene tab', (await evaluate('__gm.tab()')) === 'scene');
await evaluate(`__gm.showTab('graph')`);
await sleep(100);
await cdp.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'ArrowRight', code: 'ArrowRight', windowsVirtualKeyCode: 39 });
await sleep(300);
await shot('19-graph-live');
@@ -448,6 +452,17 @@ try {
await sleep(150);
const varsUndone = JSON.parse(await evaluate(`JSON.stringify(__gm.state.project.assets.find(a => a.id === 'mover').variables.map(v => v.key))`));
check('undo removes the variable', varsUndone.join(',') === 'speed', varsUndone.join(','));
// 13g. The graph strictly mirrors the selection: nothing / a non-behavior → empty editor.
await evaluate(`__gm.state.select(null)`);
await sleep(100);
check('graph editor empties when nothing is selected', (await evaluate(`__gm.graph.current()`)) === null);
await evaluate(`__gm.state.select({ kind: 'asset', assetId: 'cam' })`);
await sleep(100);
check('graph editor stays empty for a non-behavior asset', (await evaluate(`__gm.graph.current()`)) === null);
await evaluate(`__gm.state.select({ kind: 'asset', assetId: 'mover' })`);
await sleep(100);
check('graph editor rebinds when a behavior is selected again', (await evaluate(`__gm.graph.current()`)) === 'mover');
await evaluate(`__gm.showTab('scene')`);
check('no exceptions during run', !consoleLog.some((l) => l.startsWith('EXCEPTION')), consoleLog.filter((l) => l.startsWith('EXCEPTION')).slice(0, 3).join(' | '));