From 551a50ffc97bbd06f0c763afcde16c8cbc52ab11 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 7 Sep 2026 14:28:43 +0200 Subject: [PATCH] Play switches to scene tab; graph tab strictly follows selection Co-Authored-By: Claude Fable 5.1 --- README.md | 7 +++++-- src/editor/App.ts | 2 ++ src/editor/graph/GraphEditor.ts | 6 +++--- tools/e2e.mjs | 17 ++++++++++++++++- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0cf74a4c..366588cf 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/editor/App.ts b/src/editor/App.ts index 4473d2ef..1636e5ea 100644 --- a/src/editor/App.ts +++ b/src/editor/App.ts @@ -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(); } diff --git a/src/editor/graph/GraphEditor.ts b/src/editor/graph/GraphEditor.ts index 9334a85c..6c4dfaa0 100644 --- a/src/editor/graph/GraphEditor.ts +++ b/src/editor/graph/GraphEditor.ts @@ -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 { diff --git a/tools/e2e.mjs b/tools/e2e.mjs index c8be0f5f..5cbe4f14 100644 --- a/tools/e2e.mjs +++ b/tools/e2e.mjs @@ -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(' | '));