/** * The dev view: the page that plays the Creator's role for a CLI-lane project. * * The whole feature rests on one fact — the visual editor is ALREADY in every scaffolded project. * `VENDORED_DIRS` in `scaffold/project.ts` ships `engine/editor/` and `engine/debug/`, and * `GameEngine` constructs `EditorManager` unconditionally, building the transform gizmo, the object * inspector and the scene hierarchy into a hidden `#debug-container`. What the pro lane lacked was * a parent frame speaking the Creator's `postMessage` protocol. This page is that frame, and * nothing under `game/` changes to support it. * * Rendered from TypeScript rather than shipped as a `.html` asset because the package builds with * plain `tsc`, which copies no static files. `smoke/harness.ts` and `scaffold/project-files.ts` * both do the same. * * ── One iframe, two tabs ───────────────────────────────────────────────────────────────────── * * **Game** and **Editor** are the same loaded world in two engine modes, not two pages. A second * iframe would mean a second WebGL context and a second copy of the world on a creator's laptop, * and switching would cost a full reload each way — which defeats the point of having both in one * window. So a tab switch is three postMessages, and play state survives a trip to the editor. * * The exact sequences are in `applyTab()`, and each line of them is load-bearing: * - `SET_EDITOR_MODE {enabled:false}` alone does NOT give you the game back. It restores the * camera and player controls (`EditorManager.disableEditorMode`) but leaves the HUD hidden — * hiding it is `EditorTabHandler`'s job, keyed on the *tab*, so undoing it needs * `SET_EDITOR_TAB {tab:'prompt'}`. * - `LOAD_GAME {skipMenu:true}` leaves the engine in READY, not PLAYING (`showEditorPreview`). * The Game tab therefore has to `START_GAME` the first time; only afterwards is unpausing the * right move. * - Pointer lock cannot be taken from here — a parent-frame message grants the iframe no user * activation. `enterPlayingMode` knows that and shows its own overlay, so the creator's first * click inside the game captures the mouse. Same as the web Creator. * * ── Four things that fail silently if changed ──────────────────────────────────────────────── * * 1. `?source=creator` on the iframe URL. Without it `isCreatorMode` is false * (`game/src/engine/CreatorMode.ts`), the engine never registers its message listener, and * every message below is discarded with no error anywhere. * 2. `GAME_TEMPLATE_READY` must arrive before `LOAD_GAME` is posted. The engine registers its * listener only after `await initI18n()`, and a `LOAD_GAME` landing before that is DROPPED, * not queued — the symptom is a game that never loads, pointing at the wrong culprit. * 3. `REQUEST_ASSETS` / `ADD_OBJECT` / `MARK_OBJECT_MODIFIED` / `GAME_STATE_CHANGED` use a FLAT * envelope (`{ type, assets }`), not the `{ type, data }` one the rest of the protocol uses. * That is the Creator's existing shape (`useIframeMessages.ts:1149`, `:332`) and the engine * reads the fields off the message directly. * 4. The autosave poll, rather than an event. `TransformControlsManager`'s mouseUp reaches * `EditorManager.commitTransformChange()`, which only mutates a `Set` — the engine posts * nothing. Polling `CHECK_SCENE_CHANGES` is what the Creator does too, just on tab switch * instead of on a timer, and it covers drags, deletes, adds and inspector edits with one path. */ export interface EditorShellOptions { /** Port vite serves the game on. */ gamePort: number; /** The game this project owns. */ gameId: string; /** What to call it — see project/game-name.ts. Leads the tab title so tabs tell projects apart. */ gameName: string; /** Which api-server the project talks to. */ environment: string; /** * Which published line this CLI is — the one fact a creator running both installs needs at a * glance, and the reason the badge is the first thing after the wordmark. */ cliLine: 'dev' | 'latest'; cliVersion: string; } export declare function renderEditorShell(options: EditorShellOptions): string;