@jgengine/editor 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +12 -0
  3. package/dist/AssetBrowser.d.ts +26 -1
  4. package/dist/AssetBrowser.js +54 -1
  5. package/dist/AssetDropZone.d.ts +9 -0
  6. package/dist/AssetDropZone.js +65 -0
  7. package/dist/CatalogsPanel.d.ts +4 -2
  8. package/dist/CatalogsPanel.js +162 -9
  9. package/dist/CityPreview.d.ts +11 -0
  10. package/dist/CityPreview.js +45 -0
  11. package/dist/CollectionsPanel.js +5 -5
  12. package/dist/DebugDraw.d.ts +3 -1
  13. package/dist/DebugDraw.js +37 -19
  14. package/dist/EditorApp.d.ts +9 -1
  15. package/dist/EditorApp.js +208 -37
  16. package/dist/EditorChrome.d.ts +25 -6
  17. package/dist/EditorChrome.js +310 -118
  18. package/dist/InspectorPanel.d.ts +17 -7
  19. package/dist/InspectorPanel.js +173 -27
  20. package/dist/LightingPanel.d.ts +11 -0
  21. package/dist/LightingPanel.js +99 -0
  22. package/dist/MaterialsWorkspacePanel.d.ts +12 -0
  23. package/dist/MaterialsWorkspacePanel.js +86 -0
  24. package/dist/ParentPickerMenu.d.ts +13 -0
  25. package/dist/ParentPickerMenu.js +33 -0
  26. package/dist/PerfProbe.js +69 -2
  27. package/dist/PrefabsPanel.js +5 -5
  28. package/dist/RuntimePlayBridge.d.ts +5 -3
  29. package/dist/RuntimePlayBridge.js +6 -13
  30. package/dist/ScatterPreview.d.ts +3 -1
  31. package/dist/ScatterPreview.js +10 -7
  32. package/dist/SchemaInspector.js +21 -8
  33. package/dist/ScriptingPanel.d.ts +15 -0
  34. package/dist/ScriptingPanel.js +46 -0
  35. package/dist/SelectionGizmo.js +114 -30
  36. package/dist/StandaloneEditor.d.ts +16 -9
  37. package/dist/StandaloneEditor.js +19 -20
  38. package/dist/TerrainPanel.d.ts +3 -2
  39. package/dist/TerrainPanel.js +16 -5
  40. package/dist/TerrainReadout.d.ts +17 -0
  41. package/dist/TerrainReadout.js +163 -0
  42. package/dist/TerrainReadoutHud.d.ts +9 -0
  43. package/dist/TerrainReadoutHud.js +20 -0
  44. package/dist/TerrainSculpt.js +98 -59
  45. package/dist/agent/AgentPanel.d.ts +5 -2
  46. package/dist/agent/AgentPanel.js +11 -6
  47. package/dist/agent/toolBridge.js +5 -0
  48. package/dist/assetImport.d.ts +36 -0
  49. package/dist/assetImport.js +66 -0
  50. package/dist/authoredComponentMeta.d.ts +25 -0
  51. package/dist/authoredComponentMeta.js +65 -0
  52. package/dist/gizmoPivot.d.ts +4 -0
  53. package/dist/gizmoPivot.js +34 -0
  54. package/dist/handlers/catalogs.d.ts +3 -0
  55. package/dist/handlers/catalogs.js +137 -0
  56. package/dist/handlers/collections.d.ts +3 -0
  57. package/dist/handlers/collections.js +57 -0
  58. package/dist/handlers/context.d.ts +36 -0
  59. package/dist/handlers/context.js +1 -0
  60. package/dist/handlers/document.d.ts +3 -0
  61. package/dist/handlers/document.js +350 -0
  62. package/dist/handlers/grids.d.ts +3 -0
  63. package/dist/handlers/grids.js +142 -0
  64. package/dist/handlers/hierarchy.d.ts +3 -0
  65. package/dist/handlers/hierarchy.js +55 -0
  66. package/dist/handlers/index.d.ts +8 -0
  67. package/dist/handlers/index.js +23 -0
  68. package/dist/handlers/minimap.d.ts +9 -0
  69. package/dist/handlers/minimap.js +32 -0
  70. package/dist/handlers/runtime.d.ts +3 -0
  71. package/dist/handlers/runtime.js +191 -0
  72. package/dist/handlers/terrain.d.ts +3 -0
  73. package/dist/handlers/terrain.js +204 -0
  74. package/dist/index.d.ts +3 -2
  75. package/dist/index.js +3 -2
  76. package/dist/listDocumentTriggers.d.ts +42 -0
  77. package/dist/listDocumentTriggers.js +49 -0
  78. package/dist/materialAssignments.d.ts +63 -0
  79. package/dist/materialAssignments.js +84 -0
  80. package/dist/mcp/fieldSpec.d.ts +32 -0
  81. package/dist/mcp/fieldSpec.js +173 -0
  82. package/dist/mcp/rpcRequest.d.ts +8 -5
  83. package/dist/mcp/rpcRequest.js +72 -83
  84. package/dist/mcp/tools.js +104 -763
  85. package/dist/networkSnapshot.d.ts +82 -0
  86. package/dist/networkSnapshot.js +73 -0
  87. package/dist/parentCandidates.d.ts +12 -0
  88. package/dist/parentCandidates.js +25 -0
  89. package/dist/perfPill.d.ts +21 -0
  90. package/dist/perfPill.js +20 -0
  91. package/dist/session.d.ts +167 -5
  92. package/dist/session.js +36 -879
  93. package/dist/shell/AnimationPanel.d.ts +17 -0
  94. package/dist/shell/AnimationPanel.js +134 -0
  95. package/dist/shell/AssetThumbnail.d.ts +12 -0
  96. package/dist/shell/AssetThumbnail.js +23 -0
  97. package/dist/shell/BottomDock.d.ts +25 -0
  98. package/dist/shell/BottomDock.js +40 -0
  99. package/dist/shell/CommandPalette.d.ts +12 -0
  100. package/dist/shell/CommandPalette.js +56 -0
  101. package/dist/shell/ConsolePanel.d.ts +8 -0
  102. package/dist/shell/ConsolePanel.js +45 -0
  103. package/dist/shell/ContentBrowser.d.ts +21 -0
  104. package/dist/shell/ContentBrowser.js +130 -0
  105. package/dist/shell/HierarchyPanel.d.ts +26 -0
  106. package/dist/shell/HierarchyPanel.js +387 -0
  107. package/dist/shell/NetworkWorkspacePanel.d.ts +11 -0
  108. package/dist/shell/NetworkWorkspacePanel.js +49 -0
  109. package/dist/shell/PlayModeBar.d.ts +12 -0
  110. package/dist/shell/PlayModeBar.js +27 -0
  111. package/dist/shell/ProfilerPanel.d.ts +10 -0
  112. package/dist/shell/ProfilerPanel.js +41 -0
  113. package/dist/shell/SceneToolbar.d.ts +43 -0
  114. package/dist/shell/SceneToolbar.js +122 -0
  115. package/dist/shell/StatusBar.d.ts +18 -0
  116. package/dist/shell/StatusBar.js +41 -0
  117. package/dist/shell/TopAppBar.d.ts +33 -0
  118. package/dist/shell/TopAppBar.js +64 -0
  119. package/dist/shell/ViewportOverlays.d.ts +23 -0
  120. package/dist/shell/ViewportOverlays.js +136 -0
  121. package/dist/shell/WorkspaceRail.d.ts +20 -0
  122. package/dist/shell/WorkspaceRail.js +31 -0
  123. package/dist/shell/cameraTelemetry.d.ts +27 -0
  124. package/dist/shell/cameraTelemetry.js +24 -0
  125. package/dist/shell/commandRegistry.d.ts +58 -0
  126. package/dist/shell/commandRegistry.js +133 -0
  127. package/dist/shell/consoleSink.d.ts +11 -0
  128. package/dist/shell/consoleSink.js +17 -0
  129. package/dist/shell/consoleStore.d.ts +27 -0
  130. package/dist/shell/consoleStore.js +43 -0
  131. package/dist/shell/fields.d.ts +39 -0
  132. package/dist/shell/fields.js +104 -0
  133. package/dist/shell/formatSavedRelative.d.ts +2 -0
  134. package/dist/shell/formatSavedRelative.js +14 -0
  135. package/dist/shell/glbThumbnailCache.d.ts +45 -0
  136. package/dist/shell/glbThumbnailCache.js +241 -0
  137. package/dist/shell/hierarchyA11y.d.ts +21 -0
  138. package/dist/shell/hierarchyA11y.js +35 -0
  139. package/dist/shell/icons.d.ts +13 -0
  140. package/dist/shell/icons.js +85 -0
  141. package/dist/shell/layoutStore.d.ts +68 -0
  142. package/dist/shell/layoutStore.js +156 -0
  143. package/dist/shell/pathFlythrough.d.ts +37 -0
  144. package/dist/shell/pathFlythrough.js +54 -0
  145. package/dist/shell/perfHistory.d.ts +70 -0
  146. package/dist/shell/perfHistory.js +130 -0
  147. package/dist/shell/renameObject.d.ts +3 -0
  148. package/dist/shell/renameObject.js +25 -0
  149. package/dist/shell/theme.d.ts +36 -0
  150. package/dist/shell/theme.js +37 -0
  151. package/dist/shell/ui.d.ts +84 -0
  152. package/dist/shell/ui.js +90 -0
  153. package/dist/shell/useGlbThumbnail.d.ts +8 -0
  154. package/dist/shell/useGlbThumbnail.js +19 -0
  155. package/dist/terrainReadoutStore.d.ts +19 -0
  156. package/dist/terrainReadoutStore.js +36 -0
  157. package/dist/uiStore.d.ts +33 -0
  158. package/dist/uiStore.js +13 -0
  159. package/dist/viewportContextMenu.d.ts +4 -2
  160. package/dist/viewportContextMenu.js +5 -1
  161. package/package.json +4 -4
  162. package/skills/jgengine/SKILL.md +35 -804
  163. package/skills/jgengine/api.md +190 -79
  164. package/skills/jgengine/capabilities.md +112 -2
  165. package/skills/jgengine/recipes/minimal-game.md +88 -0
  166. package/skills/jgengine-editor/SKILL.md +29 -362
  167. package/skills/jgengine-editor/api.md +519 -18
  168. package/skills/jgengine-editor/capabilities.md +91 -0
  169. package/skills/jgengine-editor/reference.md +113 -0
  170. package/skills/level-design/SKILL.md +54 -0
  171. package/skills/level-design/agents/openai.yaml +4 -0
  172. package/skills/level-design/references/application-playbook.md +192 -0
  173. package/skills/level-design/references/field-guide.md +337 -0
  174. package/skills/level-design/references/live-editor-loop.md +179 -0
  175. package/dist/OutlinerPanel.d.ts +0 -14
  176. package/dist/OutlinerPanel.js +0 -84
  177. package/skills/jgengine/reference-cartridge.md +0 -72
  178. package/skills/jgengine/reference-orphans.md +0 -9
  179. package/skills/jgengine/reference-systems.md +0 -60
@@ -0,0 +1,88 @@
1
+ # Recipe — minimal game (scaffold → authored scene → win)
2
+
3
+ **What this wires:** the default end-to-end path from nothing to a complete, genre-free
4
+ walk/interact/win game: a scaffold, one `defineGame` config off the happy-path kit, an
5
+ authored scene edited in the browser, one system, one store slot, an empty-canvas HUD,
6
+ and a win condition. Every new game starts here; add combat, multiplayer, or deeper
7
+ world features only after this loop plays.
8
+
9
+ ## 1. Scaffold
10
+
11
+ `npx jgengine create <name>` (inside the engine monorepo: `bun run new:game "<id-or-title>"`).
12
+ The file set is thin and already runs:
13
+
14
+ - `src/main.tsx` — mounts `GameHost` with `editor={() => import("@jgengine/editor")}`; the
15
+ host owns the F2+E / `?mode=editor` editor summon and the dev save endpoint.
16
+ - `src/game.config.ts` — the whole game definition (below).
17
+ - `src/editor.scene.json` + `src/editorLayers.ts` — the authored scene document, normalized
18
+ with `normalizeEditorLayers`. All placement lives here, never as coordinates in code.
19
+ - `src/loop.ts` and `src/game/ui/GameUI.tsx` — systems and HUD.
20
+
21
+ ## 2. Define the game — one import surface
22
+
23
+ ```ts
24
+ // game.config.ts — everything below comes from the kit; deeper paths are for outgrowing it.
25
+ import { defineGame } from "@jgengine/shell/gameKit";
26
+
27
+ import { editorLayers } from "./editorLayers";
28
+ import { GameUI } from "./game/ui/GameUI";
29
+ import { onNewPlayer, systems } from "./loop";
30
+
31
+ export const game = defineGame({
32
+ name: "my-game",
33
+ // Binding movement actions makes the shell drive the walk controller — the game walks day one.
34
+ input: { moveForward: ["KeyW"], moveBack: ["KeyS"], moveLeft: ["KeyA"], moveRight: ["KeyD"], interact: ["KeyE"] },
35
+ systems,
36
+ loop: { onNewPlayer },
37
+ GameUI,
38
+ editorLayers, // the authored scene auto-mounts; the player spawns at its player_spawn marker
39
+ });
40
+ // Solo game: omit `multiplayer` entirely — offline is the shell default.
41
+ ```
42
+
43
+ **World is optional and thin.** Omitting `world` is valid — a pure UI/rules game is not a
44
+ spatial place. When the game *is* a place, declare only substrate + laws with `world()` from
45
+ `@jgengine/core/world/place` (e.g. `{ id, ground: { mode: "flat", size: { x: Infinity, z: Infinity } }, physics: { gravity: -24 } }`,
46
+ or `mode: "board"` for 2D). Never seed, sky, or vegetation in `world` — the engine renders its
47
+ default sky until you author one in the editor, and foliage is editor scatter over the ground.
48
+
49
+
50
+ ```ts
51
+ // loop.ts — one store slot for run state, one system for the rules tick.
52
+ import { defineStore, defineSystem, authoredSpawnPosition, type GameContext } from "@jgengine/shell/gameKit";
53
+
54
+ import { editorLayers } from "./editorLayers";
55
+
56
+ export const run = defineStore("run", { collected: 0, won: false });
57
+
58
+ export const systems = [
59
+ defineSystem({
60
+ id: "rules",
61
+ tick: { type: "fixed" },
62
+ update(ctx: GameContext) {
63
+ // System-check win alternative: read/update `run` here (e.g. win at collected >= 3).
64
+ if (run.read(ctx).collected >= 3) run.update(ctx, (s) => ({ ...s, won: true }));
65
+ },
66
+ }),
67
+ ];
68
+
69
+ export function onNewPlayer(ctx: GameContext): void {
70
+ ctx.scene.entity.spawn("player", { id: ctx.player.userId, position: authoredSpawnPosition(editorLayers) ?? [0, 0, 0] });
71
+ }
72
+ ```
73
+
74
+ `GameUI` starts as an empty `HudCanvas` (`useHudLayout` + `HudCanvas` from the kit). That is
75
+ intentional: **every game owns its UI**. Write a short UI art direction, then build custom
76
+ panels for this pitch — do not ship stock drop-in widgets (`StatBar`, `Hotbar`, `Coins`, glass
77
+ frames) as the product face. Reach for engine pieces only as headless data, layout, or
78
+ interaction models under game-owned chrome (see `jgengine-ui`).
79
+
80
+ ## 3. Author, play, win
81
+
82
+ 1. `bun dev`, then F2+E opens the editor on `editor.scene.json` — move the spawn, place
83
+ props and trigger markers, Ctrl+S saves back to the document.
84
+ 2. The starter scene ships a goal marker with `{ on: "enter", action: "win" }` — walking
85
+ into it wins with zero game code. Author more rules the same way (triggers in the
86
+ editor), or read volumes yourself via the kit's `collectAuthoredTriggers(editorLayers)`.
87
+ 3. Prove it: `bun test` asserts the document (spawn, goal), and `jgengine-verify` covers
88
+ screenshot and gameplay evidence before any "it works" claim.
@@ -1,382 +1,49 @@
1
1
  ---
2
2
  name: jgengine-editor
3
- description: Open the scene editor to place spawns, zones, and ranges visually.
3
+ description: Author scene content and assets through the JGengine editor.
4
4
  ---
5
5
 
6
- # JGengine Editor
6
+ # JGengine editor
7
7
 
8
- ## When to use
8
+ ## Ownership
9
9
 
10
- Authoring or inspecting spatial game data: terrain context, player/mob/boss spawns, zone disks, aggro/leash/discover radii, corridor paths. Prefer this over guessing `x,y,z` in TS tables.
10
+ This skill owns scene-document authoring: objects, spawns, markers, terrain, paths, zones, foliage, materials, and asset placement. `editor.scene.json` is the source of truth consumed by runtime and gameplay. Use `jgengine-world` for rendering and querying authored data.
11
11
 
12
- ## Open (human / agent browser)
12
+ Never replace authorable scene content with hardcoded meshes, coordinate arrays, or game-specific placement code. If the document or editor lacks a required kind, file a `[FEATURE]` issue before a code fallback and record the gap in the PR.
13
13
 
14
- ```
15
- bun run dev:runner
16
- # http://localhost:<port>/?game=the-robots&mode=editor
17
- # http://localhost:<port>/?game=vice-isle&mode=editor
18
- ```
19
-
20
- The editor ships everywhere as a **lazy chunk** — production `/play` (and the public `/games/<id>` pages) download it only when summoned with F2+E or `?mode=editor`. In the dev runner, **Save (Ctrl+S)** writes the scene straight to `Games/<id>/src/editor.scene.json` through the dev server's `/__jgengine/save` endpoint; the file auto-loads on the next editor open as an overlay (same-id objects win over the game's derived `editorLayers`, new objects append) and is plain JSON a game can import as runtime data. Fold long-lived edits back into source tables when they stabilize. Outside the dev server (production pages) the Save button hides and Export JSON remains the only exit. The F2+D Tune tab has the same seam: **Save to source** rewrites changed tunable literals directly in `Games/<id>/src/*.ts`.
21
-
22
- ## Standalone games ship the editor too
23
-
24
- `npx jgengine create` scaffolds all of this outside the monorepo: the game's `main.tsx` summons the editor on F2+E or `?mode=editor` as a lazy `@jgengine/editor` chunk, and `vite.config.ts` mounts the published save middleware so Save (Ctrl+S) writes `src/editor.scene.json` and Tune's Save-to-source rewrites literals:
25
-
26
- ```ts
27
- import { standaloneSavePlugin } from "@jgengine/node/devSavePlugin";
28
- export default defineConfig({ plugins: [react(), tailwindcss(), standaloneSavePlugin()] });
29
- ```
30
-
31
- Monorepo-shaped hosts use the general form — `devSavePlugin((gameId) => srcDirFor(gameId))` — and `handleSaveRequest` is the transport-free core for a non-Vite dev server.
32
-
33
- ## Standalone — the editor without a game (any project)
34
-
35
- The same editor runs **gameless**, on any folder, for people who aren't building a jgengine game. Two zero-config entry points, one shared seam:
36
-
37
- ```
38
- npx jgengine editor [dir] # open the 3D editor on a folder (default cwd); Ctrl+S writes dir/editor.scene.json
39
- # [--assets <dir>] pick the model folder · [--port <n>] · [--out <workspace>]
40
- ```
14
+ ## Scene ownership boundary
41
15
 
42
- `jgengine editor` scaffolds a tiny Vite workspace (`.jgengine-editor/`) and serves the target folder: its `editor.scene.json` opens as the scene and every `.glb`/`.gltf` under it (or `--assets`) becomes a placeable asset. The **desktop app** exposes the identical editor at `?mode=editor` — no game loaded, blank world. Both mount `@jgengine/editor`'s `StandaloneEditor`, which drops the editor over a blank flat world instead of a game (a slim strip also lets you **📂 Open scene** / **🧊 Add assets** at runtime):
16
+ Every editor-visible object has an explicit provenance — `authored`, `generated`, `runtime`, or `transient` — and a single verdict from `classifyOwnership` (`@jgengine/core/scene/sceneOwnership`): **expose** (authorable), **bake** (offer import to the document when the provider can emit schema-valid data), or **reject** (read-only runtime content). Runtime/transient content with neither a `bake` capability nor a declared `reason` is a boundary violation — content bypassing the document with no authored home. Genuinely runtime-only or geometry-free content is declared with a serializable `SceneOwnershipManifest` (`scene-ownership.json`), which `check-content-gate` validates — the replacement for game-name exemptions.
43
17
 
44
- ```tsx
45
- import { StandaloneEditor, createBlankPlayable, blankWorld, downloadSaver } from "@jgengine/editor";
18
+ ## Headless fast path
46
19
 
47
- // Turn-key: blank world, built-in pickers, Save → dev-server endpoint or a browser download.
48
- <StandaloneEditor sceneId="my-scene" save={downloadSaver("scene.json")} />;
20
+ Inspect, mutate, and persist a game's scene in single commands — no GUI session and no reference reading needed for basic authoring:
49
21
 
50
- // Or build the gameless host yourself and mount EditorApp over it:
51
- const playable = createBlankPlayable({ assets: [{ id: "tree", url: "/models/tree.glb" }], world: blankWorld("seed") });
22
+ ```sh
23
+ bun packages/editor/src/mcp/cli.ts --game <id> --tools # verb inventory with descriptions
24
+ bun packages/editor/src/mcp/cli.ts --game <id> --rpc '{"method":"scene_summary"}'
25
+ bun packages/editor/src/mcp/cli.ts --game <id> \
26
+ --rpc '{"method":"set_transform","id":"boss","x":10,"y":0,"z":-5}' --save
52
27
  ```
53
28
 
54
- The server half is `@jgengine/node/editorHostPlugin` — a Vite plugin that serves the folder's scene + models at `/__jgengine/manifest`, streams model bytes, and writes Save back. `buildEditorManifest(sceneDir, assetsDir)` is its pure core (read the scene, scan the folder); `editorScaffold(engineVersion)` (from `@jgengine/jgengine/templates`) emits the workspace files:
55
-
56
- ```ts
57
- import { editorHostPlugin, buildEditorManifest } from "@jgengine/node/editorHostPlugin";
58
- import { editorScaffold } from "@jgengine/jgengine/templates";
59
-
60
- export default defineConfig({ plugins: [react(), editorHostPlugin({ dir: process.cwd() })] });
61
- const manifest = buildEditorManifest(sceneDir, assetsDir); // { scene, assets: [{ id, url, label }] }
62
- const files = editorScaffold("0.10.0"); // index.html, vite.config.ts, src/main.tsx, …
63
- ```
64
-
65
- ## Modes: edit · walk · play
66
-
67
- - **edit** — frozen sim, orbit inspection camera, gizmos and chrome.
68
- - **walk** — frozen sim, the game's own camera/movement; roam the world with markers drawn in place.
69
- - **play** — the real game (loop, HUD, camera) with a floating exit chip.
70
-
71
- Toolbar buttons, `F2+E` (edit ↔ play, same chord family as F2+D devtools), chip/F2+E returns to edit, or RPC: `{ method: "set_mode", mode: "play" }`. The editor session (document, undo, selection) survives mode switches. **F2+E summons the editor from any running `mode=play` game** — dev runner or the website's `/games/<id>` pages, dev and production alike, no URL change needed.
72
-
73
- ## Perf: the editor measures itself
74
-
75
- - Toolbar shows a live `fps · draws · tris` pill (red under 30fps).
76
- - `F2+D` opens the engine devtools panel (debug mode) — sim phase bars name the exact lag culprit.
77
- - Agents: `{ method: "editor_status" }` includes a `perf` sample and current `mode`; `{ method: "perf_report" }` returns the devtools snapshot (fps, sim phases, culprit hints). The perf sample separates **editor-authoring cost** from frame/sim cost — `raycastMs` (viewport picks), `rebuildMs` (preview-mesh rebuilds), `authoringMs` (their sum) — so "the editor feels laggy" resolves to a number. Sculpt/paint rebuilds only the stroke's **dirty region**, not the whole mesh. Never guess at lag — pull the report.
78
-
79
- ## Game opt-in (optional, thin)
80
-
81
- ```ts
82
- // Games/<id>/src/index.tsx — also export editorCatalogs for Data-panel tuning
83
- export { game } from "./game.config";
84
- export { editorLayers } from "./editorLayers";
85
- ```
86
-
87
- `editorLayers` → doc. Optional `editorCatalogs` seeds `document.catalogs` (`list|get|set_catalog_entry`; `findEditorCatalogEntry`).
88
-
89
- ## Author the scene, don't hardcode it — render it at runtime
29
+ Repeat `--rpc` to batch several mutations on one session; `--save` then writes the document back to `Games/<id>/src/editor.scene.json` in the same format as the GUI's Ctrl+S, and a failed RPC aborts the batch before anything is saved.
90
30
 
91
- Scene content (paths, foliage, terrain, gameplay spots) belongs in the **editor document**, not in
92
- bespoke render code with hardcoded coordinates. Author it in the 3D editor, save `editor.scene.json`,
93
- and render it generically at runtime with **`<AuthoredScene document={doc} field={ctx.world.ground} placeObjects />`**
94
- from `@jgengine/shell/scene` — draped paths, foliage (`resolveScatter`), studios/generators, and catalog props
95
- (`resolveAuthoredObjects` → object store / `objectModels`). Or onInit: `placeAuthoredObjects(ctx.scene.object,
96
- resolveAuthoredObjects(doc), heightAt)` / `placeAuthoredObjectsFromDocument` / `markerCatalogId`.
97
- `<AuthoredPaths>` / `<AuthoredObjects document field />` for one slice. Terrain: `environment({ sculpt })`.
98
- Never hand-roll path meshes or `markers.filter(meta.catalogId)` place loops.
31
+ ## Canonical workflow
99
32
 
100
- This rule is **gated**: `bun run check-content-gate` (wired into `check-types`) fails a game that hard-codes
101
- world geometry instead of rendering an editor document, and flags source files dense with hand-placed
102
- coordinate literals. Two shrinking baselines (`scripts/content-gate-baseline.json`,
103
- `scripts/coordinate-literal-baseline.json`) list today's offenders so `main` stays green; they may only
104
- lose entries. After migrating a game to `<AuthoredScene>`, reseed with `bun run check-content-gate --update`
105
- and commit the trimmed baselines. Legitimately procedural / geometry-free games live in the `EXEMPT` map in
106
- `scripts/check-content-gate.ts`. The end-to-end editor→`<AuthoredScene>` path this depends on is proven
107
- browserless by `scripts/editor-mvp.test.ts`.
108
-
109
- ## The F2 chord family — three modes, all agent-usable headless
110
-
111
- - **F2+D — debug mode**: engine devtools overlay (Perf/Tune/Logs/Net/Keys/Col). A plain F2 tap does nothing; F2 is only the chord holder.
112
- - **F2+C — canvas mode**: HUD layout (`document.ui.panels`) — drag/resize; TSX props fallback-only.
113
- - **F2+E — editor mode**: this scene editor.
114
-
115
- Agents never need a user-launched server: `bun run drive` boots the dev server **and** headless Chromium itself, and every `GamePlayerShell` page installs `window.__jgengineAgent` — one RPC surface covering all three modes (`--rpc` prefers it, falls back to the raw editor host). Unknown verbs delegate to the live editor host, so all editor verbs below work through it too.
116
-
117
- ```
118
- bun run drive <id> --rpc '{"method":"agent_status"}' # which modes are live
119
- bun run drive <id> --rpc '{"method":"debug_snapshot"}' # lean perf/logs/tunables report
120
- bun run drive <id> --rpc '{"method":"debug_report"}' # full devtools snapshot
121
- bun run drive <id> --rpc '{"method":"canvas_state"}' # HUD panels + placements
122
- bun run drive <id> --rpc '{"method":"canvas_move_panel","id":"minimap","anchor":"top-right"}' --shot hud
123
- bun run drive <id> --rpc '{"method":"editor_summon"}' --wait 2000 --rpc '{"method":"scene_summary"}'
124
- bun run drive <id> --mode editor --rpc '{"method":"set_transform","id":"boss","x":-90,"z":-650}' --rpc '{"method":"export_document"}'
125
- ```
126
-
127
- Canvas verbs: `canvas_state`, `canvas_set_editing`, `canvas_move_panel`, `canvas_resize_panel`, `canvas_list_panel_types`, `canvas_reset`. Debug: `debug_open`, `debug_snapshot`, `debug_report`. Editor extras: `save_scene` → `editor.scene.json`. Menu-gated: `--click`/`--key` first. Pure document edits: headless CLI below.
128
-
129
- ## Agent RPC (same verbs as UI)
130
-
131
- **Agent panel** (toolbar): shared GUI/RPC undo — `packAgentContext` / `routeToolCall` / `runAgentTurn` / `undoAgentPatch`; `JGENGINE_EDITOR_AGENT_URL` + `JGENGINE_EDITOR_AGENT_KEY` (see `reference.md`).
132
-
133
- Browser console / injected host:
134
-
135
- ```js
136
- window.__jgengineEditorHost.handle({ method: "scene_summary" })
137
- window.__jgengineEditorHost.handle({ method: "camera_goto", id: "boss_warrior" })
138
- window.__jgengineEditorHost.handle({ method: "set_transform", id: "boss_warrior", x: -90, z: -650 })
139
- window.__jgengineEditorHost.handle({ method: "export_document" })
140
- ```
141
-
142
- Headless (live editor + screenshots, or document-only CLI):
143
-
144
- ```
145
- bun run drive the-robots --mode editor --wait 3000 --rpc '{"method":"editor_status"}' --shot check
146
- bun run drive the-robots --mode editor --rpc '{"method":"set_mode","mode":"play"}' --wait 2000 --shot playing
147
- bun packages/editor/src/mcp/cli.ts --game the-robots --rpc '{"method":"list_layers"}'
148
- bun packages/editor/src/mcp/cli.ts --game the-robots --serve # POST localhost:17373/rpc
149
- bun packages/editor/src/mcp/cli.ts --game the-robots --stdio # MCP JSON-RPC on stdin/stdout
150
- ```
151
-
152
- The CLI/HTTP bridge validates every RPC payload and a game's `editorLayers` export at load time — malformed JSON, unknown `method`, or wrong-shaped document fields get path-specific diagnostics instead of crashing.
153
-
154
- Viewport: click anything to select — editor gizmos hit directly, world geometry snaps to the nearest marker/volume/path/note, repeat-click cycles stacked candidates, shift/ctrl-click multi-selects — then TransformControls (W move / E rotate marker / R scale volume: radius, cylinder height, or box half-extents). Multi-selection drags move every selected object. Snap button cycles ground / grid / off (grid also snaps rotation to 15°); `G` toggles the reference grid. Outliner groups by kind (notes included) with ×N dedup rows; `N` cycles instances of the selected row; ctrl-click adds to selection.
155
-
156
- Authoring: **+ Add** menu places markers, volumes (sphere/box/cylinder), notes, and draws paths (click points, Enter finish, Esc cancel; shift-click keeps placing). Select a path, click a vertex sphere to move/insert/delete points. `Ctrl+D` duplicates, `Ctrl+C/X/V` copy/cut/paste (system clipboard gets the JSON fragment too), `Ctrl+A` selects all visible, Delete removes, arrows/PgUp/PgDn nudge by grid step (Shift ×5), `F` frames the selection, `?` opens the shortcut sheet. Inspector edits labels, kind, display color, note text, radius/height/half-extents, and coalesces typed edits into single undo steps. **Import** loads an exported JSON back in (success/error toasts); Export downloads, `⧉` copies the JSON. Edits autosave to a per-game localStorage draft — reopening the editor offers Restore/Discard; the header shows an amber ● while the document differs from the game's authored layers. Layer visibility and snap prefs persist per game in localStorage.
157
-
158
- ## Vegetation volumes — density is one slider
159
-
160
- **+ Add → Vegetation (box/circle)** places a fill area; its inspector section has an **item** field (`grass`, or any render-catalog id like a tree/bush model) and a **density /m² slider** (plus exact number, scale range, spacing, reroll seed) with a live `≈ N placements over M m²` readout. Data rides `volume.meta` in the saved scene — no new schema.
161
-
162
- Consume in the game (`@jgengine/core/world/vegetation`):
163
-
164
- ```ts
165
- import { grassPatchesFromVegetation, resolveVegetation } from "@jgengine/core/world/vegetation";
166
-
167
- grass: grassPatchesFromVegetation(sceneDoc), // item "grass" → shell blade patches, density = blades/m²
168
- for (const p of resolveVegetation(sceneDoc)) // everything else → deterministic placements
169
- ctx.world.object.place(p.item, p.x, 0, p.z, { rotation: p.rotation, visual: { scale: p.scale } });
170
- ```
171
-
172
- Same volume, same seed → same field every run; drag the slider, save, done.
173
-
174
- ## Terrain sculpting — brushes on the live heightfield
175
-
176
- Toolbar **Terrain** (or press `T`) enters the sculpt tool. **Create terrain** lays an editable
177
- heightfield over the scene; brushes then reshape it with live feedback:
178
-
179
- - **Raise / Lower** — push ground up or dig it down · **Smooth** — average toward neighbours ·
180
- **Flatten** — level to a sampled or numeric height · **Noise** — fractal roughening (seeded, repeatable) ·
181
- **Ramp** — drag low→high to grade a straight slope.
182
- - Live controls: radius, strength, falloff (smooth/linear/none), shape (circle/square), spacing,
183
- invert modifier, flatten height, noise seed. A preview ring tracks the cursor.
184
- - A whole drag commits as **one** undoable stroke (compact vertex delta — the terrain document is
185
- never copied per move). `Ctrl+Z` / `Ctrl+Y` undo/redo strokes like any edit; strokes serialize with
186
- the scene under `document.terrain`.
187
-
188
- The sculpt engine is the reusable `@jgengine/core/world/terraform` seam — build, edit, and consume it
189
- outside the editor too:
190
-
191
- ```ts
192
- import {
193
- createTerrainSnapshot, // fresh flat heightfield over an Aabb
194
- editableTerrainFromSnapshot, // rebuild a live EditableTerrain over the game's ground
195
- beginTerraformStroke, // batch many stamps into one compact TerraformDelta
196
- applyDeltaToSnapshot, // redo a stroke onto a snapshot (copy-on-write)
197
- revertDeltaFromSnapshot, // undo a stroke onto a snapshot (copy-on-write)
198
- } from "@jgengine/core/world/terraform";
199
-
200
- const snapshot = createTerrainSnapshot({ bounds: { minX: -100, minZ: -100, maxX: 100, maxZ: 100 }, cellSize: 2 });
201
- const terrain = editableTerrainFromSnapshot(snapshot, ctx.world.ground);
202
- const stroke = beginTerraformStroke(terrain);
203
- stroke.stamp({ mode: "raise", center: [0, 0], radius: 12, strength: 1 });
204
- const delta = stroke.delta(); // one undoable step
205
- const next = applyDeltaToSnapshot(snapshot, delta); // serializable, back to document.terrain
206
- ```
207
-
208
- ### Material painting
209
-
210
- The terrain tool's **Paint** sub-mode paints material layers (grass/dirt/rock/sand/mud/snow/road/gravel)
211
- onto the same heightfield — the surface id is stored per cell in `document.terrain.surfaces` and colors the
212
- mesh. Click/drag paints the selected material; **Alt-click** samples (eyedropper); **Fill all** / **Clear
213
- paint** blanket the terrain; **Auto rules** paint by slope ("on steep slopes") or height ("on high ground").
214
- Each stroke/fill is one undoable `paintTerrain` command (compact `SurfaceDelta`). Consume with
215
- `beginSurfaceStroke`, `fillSurfaceDelta`, `autoPaintDelta`, and `surfaceAt` from `@jgengine/core/world/terraform`.
216
-
217
- ### Material layer stack + weighted blend
218
-
219
- Beyond one dominant surface per cell, terrain carries a reorderable **material layer stack**
220
- (`document.terrain.layers: TerrainMaterialLayer[]`), each layer a palette `surface` plus render params
221
- (`roughness`, `tiling`, `triplanar`, `tint`, `opacity`) — carried as data so a runtime game reads them
222
- straight off the snapshot. Per-cell **weighted blend** lets a cell mix layers (0.6 grass + 0.4 dirt);
223
- weights live in `document.terrain.weights` (flat `cols*rows*layers.length`, **lazy** — absent until a blend
224
- is painted, so single-layer terrains stay compact). Pre-2.0 documents auto-upgrade via
225
- `migrateTerrainSnapshot` (derives layers from painted surfaces). Undo is compact: `setTerrainLayers`
226
- (snapshot) and `blendTerrain` (`WeightDelta`). Consume with `setLayers`, `blendPaintDelta`,
227
- `beginBlendStroke`, `weightsAt`, and `migrateTerrainSnapshot` from `@jgengine/core/world/terraform`.
228
-
229
- ```ts
230
- import { editableTerrainFromSnapshot, beginBlendStroke } from "@jgengine/core/world/terraform";
231
- session.dispatch({ type: "setTerrainLayers", layers: [{ id: "grass", surface: "grass", roughness: 0.9 }, { id: "dirt", surface: "dirt" }] });
232
- const live = editableTerrainFromSnapshot(session.getState().document.terrain);
233
- const stroke = beginBlendStroke(live);
234
- stroke.stamp({ mode: "paint", center: [0, 0], radius: 8, surface: "dirt", strength: 1 });
235
- session.dispatch({ type: "blendTerrain", delta: stroke.delta() }); // one undoable weighted-blend step
236
- ```
237
-
238
- ### Runtime consumption — the sculpt seam
239
-
240
- An authored heightfield drives a game's live ground through `environment({ sculpt })`: pass the
241
- `document.terrain` snapshot and its offsets layer over the base terrain in `resolveEnvironmentField`, so
242
- **both the rendered mesh and player collision** reflect the sculpt through the one field every consumer
243
- already reads (see `Games/tower-guard`). `sculptedField(base, snapshot)` composes them directly.
244
-
245
- ## Foliage / scatter regions
246
-
247
- **+ Add → Foliage region (lasso)** draws a closed polygon on the terrain (click points, Enter to finish) —
248
- a scatter path of `kind: "scatter"`. Its inspector drives deterministic, **GPU-instanced** scatter that
249
- previews live in the viewport as you drag: **density /m²**, spacing, a weighted **species palette**
250
- (add/remove rows of item id + weight), scale range, **max slope** and **height** masks, **edge fade**
251
- (feather the border), align-to-slope, and a seed with a reroll button. Same seed → same field. Rules ride
252
- `path.meta`. **Clearance zones** keep foliage off gameplay — don't hand-carve the polygon around
253
- spawns/plots/paths. Tag any marker/volume with a **clearance** (m) in its inspector (spawns/objectives
254
- auto-clear by kind); `resolveScatter` repels foliage from every clearance zone in the document, and the
255
- region's **auto-avoid gameplay spots** toggle turns it off per region (manual `avoid` discs only). The
256
- matching terrain half: `environment({ clearings: clearanceZonesFrom(doc) })` flattens the ground under
257
- those same spots (a level pad even under a mound), so one clearance zone clears foliage *and* levels terrain
258
- (`Games/tower-guard` scatters one arena-wide region and lets the path + plots carve themselves out).
259
- Consume in a game with `resolveScatter(doc, terrain, options?)` from `@jgengine/core/world/scatterRegion`
260
- (`clearanceZonesFrom`, `ClearanceOptions` scope which kinds/ids clear),
261
- then render the instances with `<InstancedScatter instances={…} />` from `@jgengine/shell/scatter` — real
262
- per-species proxy models (trunked trees, stacked pines, round bushes, faceted rocks, grass tufts) grouped
263
- into per-chunk GPU-instanced draws that frustum-cull independently (`chunkScatterInstances`). The same
264
- renderer powers the editor preview. **Convert to objects**: `convertScatterToObjects` (RPC `convert_scatter`)
265
- detaches a region into individually-editable placed prop markers.
266
-
267
- Headless: `create_terrain`, `sculpt_terrain {mode,x,z,radius,strength,…}`, `paint_terrain {surface,x,z,radius}`,
268
- `fill_terrain {surface}`, `auto_paint {surface,minSlope,minHeight,…}`, `terrain_materials`, `terrain_layers`,
269
- `set_terrain_layers {layers}`, `blend_terrain {surface,x,z,radius?}`, `terrain_summary`,
270
- `add_foliage {points,density,item}`, `convert_scatter {pathId}`, and `scatter_summary` RPC verbs drive and
271
- assert terrain + foliage authoring without WebGL (`bun packages/editor/src/mcp/cli.ts`).
272
-
273
- ## Prefabs — reusable object stamps
274
-
275
- **Prefabs** tab (left aside) makes reuse across a scene, or across games, a first-class op:
276
- select objects, name them, **Make prefab** extracts them into a serializable fragment centered
277
- on its own bounds centroid (so it drops in consistently anywhere). **Insert** stamps a fresh,
278
- freshly-id'd instance at the camera focus point, tagging every inserted object's
279
- `meta.prefabId`/`meta.prefabInstanceId`. **Detach** breaks the link (content stays, tags clear)
280
- without touching anything else. Prefabs live in `document.prefabs` — export/import the document,
281
- or lift just that array, to reuse a prefab library in another game.
282
-
283
- ```ts
284
- import { createPrefabFragment, findEditorPrefab } from "@jgengine/core/editor/index";
285
-
286
- session.dispatch({ type: "createPrefab", id: "camp", name: "Camp", ids: ["tent", "fire"] });
287
- session.dispatch({ type: "insertPrefab", prefabId: "camp", at: { x: 100, y: 0, z: 0 } });
288
- session.dispatch({ type: "detachPrefabInstance", instanceId }); // breaks the link, keeps content
289
- session.dispatch({ type: "deletePrefab", prefabId: "camp" }); // library entry only; placed instances unaffected
290
- ```
291
-
292
- Headless: `create_prefab {id,name,ids}`, `insert_prefab {prefabId,x?,y?,z?}` (defaults to camera
293
- focus), `detach_prefab_instance {instanceId}`, `delete_prefab {prefabId}`, `list_prefabs`.
294
-
295
- ## Collections — named selection sets and locked production groups
296
-
297
- **Sets** tab bookmarks the current selection under a name for later restore/add-to/remove-from —
298
- and doubles as a production group with **lock** (blocks `translate`/`setTransform`/`remove`/
299
- `removeMany` on its members — moving or deleting a locked group is refused at the session level,
300
- not just the UI), **color**, and **visible** flags. Collections live in `document.collections` and
301
- survive export/import; removing a member object prunes it from every collection automatically.
302
-
303
- ```ts
304
- import { findEditorCollection, isEditorObjectLocked } from "@jgengine/core/editor/index";
305
-
306
- session.dispatch({ type: "createCollection", id: "pack", name: "Wolf pack", memberIds: ["a", "b"] });
307
- session.dispatch({ type: "setCollectionFlags", id: "pack", patch: { locked: true, color: "#f59e0b" } });
308
- session.dispatch({ type: "selectCollection", id: "pack" }); // restores the selection
309
- ```
310
-
311
- Headless: `list_collections`, `create_collection {id,name,memberIds?}`, `rename_collection`,
312
- `delete_collection`, `set_collection_members`, `add_to_collection`, `remove_from_collection`,
313
- `set_collection_flags {id,color?,locked?,visible?}`, `select_collection {id}`.
314
-
315
- ## Batch property edit and drag-drop material assignment
316
-
317
- Multi-select then dispatch `batchSetProperties` to patch color/label/meta across every kind in
318
- one undo step — the primitive behind "replace selected, edit once":
319
-
320
- ```ts
321
- session.dispatch({ type: "batchSetProperties", ids: selection, patch: { color: "#0ff", meta: { tier: 2 } } });
322
- ```
323
-
324
- The asset browser's **Materials** palette (top of the Assets panel) is drag-source-only chips —
325
- drop one onto an outliner row to `assignMaterial` (stamps `meta.materialId`) that object, or drop
326
- it in the viewport: hits a tagged object → assigns; hits bare terrain → paints that material at
327
- the drop point (same undoable stroke as the Paint tool). Headless: `batch_set_properties
328
- {ids,color?,label?,meta?}`, `assign_material {ids,materialId}`.
329
-
330
- ## Scene hierarchy — parent / child
331
-
332
- The outliner has a **By kind** and a **Hierarchy** view (nested tree, expand/collapse). Any object can be
333
- **parented** under another via the inspector's *parent* dropdown (or the `set_parent` RPC) — moving or
334
- translating a parent carries its whole subtree by the same delta, and cycles are refused. Parenting rides
335
- `parentId` on the object itself, so it serializes, undoes, and translates through the existing commands.
336
- Helpers in `@jgengine/core/editor/index`: `editorRoots`, `editorChildren`, `editorParentOf`,
337
- `collectDescendants`, `wouldCreateCycle`. Headless: `set_parent {ids, parentId}` and `hierarchy`.
338
-
339
- ## Responsiveness — selector subscriptions + virtualization
340
-
341
- The editor stays smooth on large scenes via two seams (`@jgengine/editor`):
342
- - `useStoreSelector(store, selector, isEqual?)` subscribes a component to a **slice** of the session/UI
343
- store through `useSyncExternalStore`, so it rerenders only when that slice changes — UI-only churn
344
- (gizmo mode, snapping, active tool) no longer rerenders the outliner/inspector. `shallowArrayEqual`
345
- is the selection-list comparator. Adopters: `OutlinerPanel`, `PrefabsPanel`, `CollectionsPanel`,
346
- and `EditorChrome`'s `InspectorPanel`.
347
- - `virtualWindow(scrollTop, viewportHeight, rowHeight, rowCount, overscan?)` is the pure windowing math
348
- behind the outliner's fixed-height virtual list — a scene with thousands of objects only mounts the
349
- visible handful of rows. The status bar shows live object + foliage-instance counts next to the fps pill.
350
-
351
- ## Parametric studios — the editor is an extension seam
352
-
353
- The editor is not a fixed tool: **anyone can register a new "studio"** (a parametric asset with a
354
- slider inspector) with one call and **zero engine edits**. Two seams, one inspector generator.
355
-
356
- **Scene kind** — geometry authored as a path / volume / marker. `registerSceneKind` (`@jgengine/core/scene/sceneKinds`) supplies a `schema`, an optional pure `resolve`, and `+ Add` metadata; the editor **auto-generates the whole slider inspector** from the schema (`range | number | bool | select | color | text | seed | weightedList`) — no per-kind JSX. Pair it with `registerSceneKindRenderer` (`@jgengine/shell/scene/sceneKindRenderers`) and `AuthoredScene` renders it. Built-in kinds: `scatter`, `water`, `grass_field`. Generic primitives to compose (never re-roll): `placeAlongPath` (`world/pathInstances`), `sagCurve`/`catenaryCurve` (`world/catenary`), `readNamedSockets` (`scene/modelSockets`).
357
-
358
- **Generator asset** — a slider-driven prop placed as a marker. `registerAssetGenerator` (`@jgengine/core/scene/assetGenerator`) turns `(params, seed) → parts`; a placed instance stores only `{ assetId, params, seed }` and `resolveGeneratorAsset` re-resolves it at runtime (never baked). Built-in: `building`. The inspector shows the generator's schema on any marker whose `meta.assetId` names one. Declare the generator's front via `GeneratedAsset.forward` (default +Z, `DEFAULT_FORWARD` in `@jgengine/core/scene/facing`) so a product-shot stage or placement tool can auto-orient it — see `jgengine-ui` → "Cinematic studio shots" (`StudioStage`'s `faceCamera`).
359
-
360
- Full copyable walkthrough (pole-line + bookcase, zero engine edits): **`examples/studios/README.md`**; enforced by `scripts/studioSeam.test.ts`.
361
-
362
- ### Drive studio sliders headlessly (agents)
363
-
364
- Parametric params live in `meta`; patch via `set_meta` / `set_path` / `set_marker` / `set_note` (merge + schema validate). Catalogs: same SchemaInspector + coalesce via `list|get|set_catalog_entry`:
365
-
366
- ```
367
- bun run drive <id> --mode editor --rpc '{"method":"set_meta","id":"grove","patch":{"density":0.6,"seed":"r2"}}'
368
- bun packages/editor/src/mcp/cli.ts --game tower-guard --rpc '{"method":"set_catalog_entry","catalogId":"towers","entryId":"tower_archer","patch":{"damage":12}}'
369
- ```
33
+ 1. Inspect the current scene and available editor capabilities.
34
+ 2. Make authoring changes through the GUI or the headless fast path above; do not hand-edit the JSON.
35
+ 3. Save the scene document (`--save` headless, Ctrl+S in the GUI) and verify the intended objects/layers are present.
36
+ 4. Make gameplay reference stable authored ids, path kinds, markers, or zones rather than copying coordinates (player spawns read the document's `player_spawn` marker via `authoredSpawnPosition` from `@jgengine/core/world/authoredSpawn`).
37
+ 5. Render through shared authored-scene primitives and verify with `jgengine-verify`.
370
38
 
371
- ## Core APIs (`editor/`)
39
+ Scaffolded games start editor-wired: `npx jgengine create` (also what `bun run new:game` runs in-repo) uses one template that ships `src/editor.scene.json` + `editorLayers.ts`, passes the document to play mode and F2+E through `defineGame({ editorLayers })` (auto-mounted `AuthoredScene`), and spawns at the authored marker — extend the shipped document instead of re-wiring or hardcoding. To promote a scene authored in the standalone editor (`npx jgengine editor`) into a runnable game, `npx jgengine create "<Name>" --from-scene <folder>` bakes that folder's `editor.scene.json` in as the new game's scene (adding a `player_spawn` at the origin only if the authored scene lacks one), so the built game walks the scene that was just authored. Once promoted, an editor asset import (drop a `.glb`/`.gltf`) no longer scans a model folder: the host detects the typed `src/game/assets.ts` catalog, copies the bytes into the game's `public/<basePath>/imported/`, and rewrites `assets.ts` to add a durable `extras` entry (`buildCatalog({ extras })`), so the shipped game resolves the imported id through its own catalog. The rewrite is idempotent — re-importing the same id collapses to one entry — and falls back to the folder-scan import if `assets.ts` can't be safely rewritten.
372
40
 
373
- - `@jgengine/core/editor/index` — document, session, commands, undo
374
- - `@jgengine/core/editor/types` — markers, volumes, paths
375
- - `@jgengine/core/editor/document` — normalize/merge/export
376
- - `@jgengine/core/editor/commands` — `createEditorSession`
377
- - `@jgengine/editor` — `EditorApp`, host RPC, bridge server, agent panel (dev-only package)
41
+ Use `capabilities.md` to discover editor/runtime imports and `api.md` for signatures. Open [reference.md](reference.md) for RPC/CLI operations, embedded-agent behavior, scene schema details, and troubleshooting.
378
42
 
379
- ## Do not
43
+ ## Traps
380
44
 
381
- - Statically import `@jgengine/editor` from `GameHost` or game entry code — summon it only as a lazy chunk (`await import("@jgengine/editor")`)
382
- - Treat mesh modeling as in-scope (placement/world tools only)
45
+ - Selection and camera state are not authored world data. Terrain-readability guides (contours, surface-draped grid, elevation HUD) are editor visualization built from the live heightfield via headless `@jgengine/core/world/terrainGuides`; their toggles persist as editor prefs, never scene content.
46
+ - Runtime-generated decoration does not become editor-owned merely because it is visual; decide ownership explicitly.
47
+ - Asset-facing, units, pivot, footprint, and placement policy belong in asset/catalog metadata, not corrective game transforms.
48
+ - Paths and markers should drive both rendering and gameplay from the same document.
49
+ - Visual presence summaries prove content exists; only an inspected shot proves composition and finish.