Camera view frame, viewport clipping, overlay layer for editor symbols

- Camera assets get width/height properties; play mode renders only the
  camera frame (letterboxed, masked, culled) and nothing without a camera
- Remove demo arrow-key/camera-follow systems from play mode
- Clip world and gizmos to the viewport (selection frame no longer leaks
  over the left panel)
- Blur focused widget on pointerdown outside it so inspector edits commit
  before selection changes
- Separate objects/overlay layers so camera symbols always render on top
- Add tools/seed-demo.mjs for reproducible E2E runs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-07 11:13:08 +02:00
1 parent 5a46124454
commit aaa8154adf
11 files changed
+307 -142

No files matched your search

+4 -9
View File
@@ -4,8 +4,6 @@ import { RemoveSceneObjectCommand } from '../core/commands/sceneObjectCommands';
import { listScenes } from '../core/model/Project';
import { HttpProjectStore } from '../core/store/HttpProjectStore';
import type { ProjectStore } from '../core/store/ProjectStore';
import { CameraFollowSystem } from '../runtime/systems/CameraFollowSystem';
import { PlayerControlSystem } from '../runtime/systems/PlayerControlSystem';
import { World } from '../runtime/World';
import { Dialog, HStack, Menu, type MenuItem, Splitter, ui, VStack, type Widget } from '../ui';
import { Viewport } from '../viewport/Viewport';
@@ -52,7 +50,7 @@ function installDebugHook(app: Application, state: EditorState, viewport: Viewpo
const p = node.getGlobalPosition();
return { x: p.x + node.width / 2, y: p.y + node.height / 2 };
};
(window as unknown as { __gm: unknown }).__gm = { state, locate, viewportCenter };
(window as unknown as { __gm: unknown }).__gm = { state, locate, viewportCenter, viewportInfo: () => viewport.debugInfo(), layerOf: (id: string) => viewport.layerOf(id) };
}
class Editor {
@@ -187,14 +185,11 @@ class Editor {
const scene = this.state.activeScene;
if (!scene) return;
ui().focus.blur();
const selected = this.state.selection?.kind === 'object' ? this.state.selection.objectId : null;
const control = new PlayerControlSystem(selected);
this.world = World.instantiate(this.state.project, scene.id)
.addSystem(control)
.addSystem(new CameraFollowSystem(() => control.controlled));
// No systems yet: the world is static. Behaviour arrives with scripting in a later phase.
this.world = World.instantiate(this.state.project, scene.id);
this.viewport.setWorld(this.world);
this.state.setMode('play');
this.state.setNotice('Arrow keys move the sprite · Esc stops');
this.state.setNotice(this.world.firstOfType('camera') ? 'Playing · Esc stops' : 'This scene has no camera – nothing is rendered · Esc stops');
}
private stop(): void {