@jgengine/editor 0.18.1 → 0.21.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 (80) hide show
  1. package/CHANGELOG.md +504 -7
  2. package/dist/ClipPreviewLayer.d.ts +2 -3
  3. package/dist/ClipPreviewLayer.js +24 -26
  4. package/dist/CreatorApp.d.ts +12 -0
  5. package/dist/CreatorApp.js +107 -0
  6. package/dist/DebugDraw.js +3 -1
  7. package/dist/EditorApp.d.ts +14 -2
  8. package/dist/EditorApp.js +29 -14
  9. package/dist/EditorChrome.d.ts +9 -1
  10. package/dist/EditorChrome.js +61 -35
  11. package/dist/InspectorPanel.d.ts +3 -1
  12. package/dist/InspectorPanel.js +12 -8
  13. package/dist/LightingPanel.js +5 -1
  14. package/dist/MaterialAssetEditor.d.ts +13 -0
  15. package/dist/MaterialAssetEditor.js +109 -0
  16. package/dist/MaterialPreview.d.ts +16 -0
  17. package/dist/MaterialPreview.js +41 -0
  18. package/dist/MaterialsWorkspacePanel.d.ts +14 -2
  19. package/dist/MaterialsWorkspacePanel.js +78 -4
  20. package/dist/RuntimePlayBridge.js +1 -6
  21. package/dist/SelectionGizmo.js +23 -14
  22. package/dist/SimulationInspector.d.ts +9 -0
  23. package/dist/SimulationInspector.js +47 -0
  24. package/dist/TerrainSculpt.js +3 -1
  25. package/dist/agent/toolBridge.js +2 -0
  26. package/dist/authoringPlacement.d.ts +21 -0
  27. package/dist/authoringPlacement.js +34 -0
  28. package/dist/handlers/context.d.ts +1 -0
  29. package/dist/handlers/document.d.ts +1 -1
  30. package/dist/handlers/document.js +31 -1
  31. package/dist/handlers/hierarchy.d.ts +1 -1
  32. package/dist/handlers/hierarchy.js +4 -0
  33. package/dist/handlers/navmesh.d.ts +2 -0
  34. package/dist/handlers/navmesh.js +16 -4
  35. package/dist/handlers/runtime.js +20 -17
  36. package/dist/handlers/terrain.js +52 -12
  37. package/dist/index.d.ts +2 -1
  38. package/dist/index.js +1 -0
  39. package/dist/materialControls.d.ts +32 -0
  40. package/dist/materialControls.js +79 -0
  41. package/dist/mcp/fieldSpec.d.ts +1 -1
  42. package/dist/mcp/fieldSpec.js +13 -4
  43. package/dist/mcp/importOptionalGameModule.d.ts +2 -0
  44. package/dist/mcp/importOptionalGameModule.js +13 -0
  45. package/dist/mcp/loadGameCatalogs.js +5 -12
  46. package/dist/mcp/rpcRequest.d.ts +3 -3
  47. package/dist/mcp/rpcRequest.js +10 -4
  48. package/dist/mcp/tools.js +13 -4
  49. package/dist/modelAnimationAuthoring.d.ts +49 -6
  50. package/dist/modelAnimationAuthoring.js +118 -64
  51. package/dist/session.d.ts +64 -0
  52. package/dist/session.js +58 -7
  53. package/dist/shell/AnimationGraphPanel.d.ts +16 -0
  54. package/dist/shell/AnimationGraphPanel.js +180 -0
  55. package/dist/shell/AnimationPanel.d.ts +3 -1
  56. package/dist/shell/AnimationPanel.js +9 -7
  57. package/dist/shell/PlayModeBar.d.ts +2 -1
  58. package/dist/shell/PlayModeBar.js +2 -2
  59. package/dist/shell/SceneToolbar.d.ts +2 -1
  60. package/dist/shell/SceneToolbar.js +5 -4
  61. package/dist/shell/TopAppBar.d.ts +2 -1
  62. package/dist/shell/TopAppBar.js +2 -2
  63. package/dist/shell/WorkspaceRail.js +1 -1
  64. package/dist/shell/animGraphPreview.d.ts +58 -0
  65. package/dist/shell/animGraphPreview.js +95 -0
  66. package/dist/shell/clipPreview.d.ts +9 -0
  67. package/dist/useDocumentSave.d.ts +20 -0
  68. package/dist/useDocumentSave.js +33 -0
  69. package/package.json +6 -5
  70. package/skills/ce-handoff/SKILL.md +2 -2
  71. package/skills/jgengine/SKILL.md +28 -6
  72. package/skills/jgengine/api.md +139 -99
  73. package/skills/jgengine/capabilities.md +28 -0
  74. package/skills/jgengine/recipes/minimal-game.md +10 -2
  75. package/skills/jgengine-editor/SKILL.md +12 -2
  76. package/skills/jgengine-editor/api.md +120 -16
  77. package/skills/jgengine-editor/capabilities.md +40 -0
  78. package/skills/jgengine-editor/reference.md +161 -3
  79. package/skills/jgengine-editor/references/live-sync.md +15 -0
  80. package/skills/level-design/SKILL.md +1 -1
@@ -8,6 +8,38 @@ in-memory session and, when every RPC succeeds, writes the session document to
8
8
  the GUI's Ctrl+S through `devSavePlugin`). Without `--save`, `--rpc` mutations are discarded on
9
9
  exit — use it for read-only inspection only, or capture `export_document` yourself.
10
10
 
11
+ The source-only CLI writer validates the full document before replacing any saved bytes, matching the dev save endpoint. Validation failures report document paths and leave the existing file intact.
12
+
13
+ Scene and catalog modules are optional. A present module with a broken dependency, initialization error, or invalid export returns load diagnostics; the CLI and stdio host stop before opening a session or saving. Only absent optional files resolve to empty content.
14
+
15
+ `dispatch` with `setEnvironment` validates only the candidate environment before changing the session, undo history, or live document revision. Supported presets are `day`, `dusk`, and `night`; custom palette, sun, intensity, and fog fields remain explicit authored values. Invalid inputs report `$.environment` paths. Validated nested fields are copied, so mutating the command afterward cannot change the document. Passing `environment: undefined` removes the bag and remains undoable. Repair unsupported authored values through an explicit editor command; preserve the author's palette and fog rather than substituting a default.
16
+
17
+ ## Atomic authoring batches
18
+
19
+ Use `document_revision` to inspect the current revision, then send one `push_document_patch` for a complete placement operation. Commands use the `EditorCommand` shapes from `@jgengine/core/editor/commands`, rather than RPC verb shapes. Earlier commands' ids are available to later commands in the same batch:
20
+
21
+ ```json
22
+ {
23
+ "method": "push_document_patch",
24
+ "patch": {
25
+ "type": "commands",
26
+ "baseRevision": 0,
27
+ "commands": [
28
+ { "type": "addMarker", "marker": { "id": "placed_a", "kind": "prop", "catalogId": "my-model", "position": { "x": 1, "y": 0, "z": 2 } } },
29
+ { "type": "addPath", "path": { "id": "route_a", "kind": "route", "points": [{ "x": 0, "y": 0, "z": 0 }, { "x": 1, "y": 0, "z": 2 }] } },
30
+ { "type": "createCollection", "id": "region_a", "name": "Region A", "memberIds": ["placed_a", "route_a"] }
31
+ ]
32
+ }
33
+ }
34
+ ```
35
+
36
+ `my-model` is a model in the game's own catalog. Send this JSON through `--rpc-file <file> --save`, the live bridge, or the embedded agent's existing tool. Human code can use `session.transaction(commands)` with the same commands and undo contract.
37
+
38
+ - All commands stage before commit. A malformed command, missing/locked target, parent cycle, or cross-kind object-id collision rejects the whole batch, preserving the document, selection, undo/redo history, revision, and live subscribers. Errors include `commands[N]`; the RPC also returns `result.commandIndex` and the unchanged `result.revision`.
39
+ - A successful edit emits one session notification, publishes one document revision, and forms one undo/redo step. Selection-only changes do not publish or consume document history. `undo`/`redo` commands cannot run inside a transaction.
40
+ - Existing same-kind object ids upsert without automatic renaming. Repeating an already-current transform or stable-id placement succeeds without publishing another revision or consuming history. After a revision conflict, inspect the latest document before retrying against its revision. `force` skips only the revision check; validation and rollback still apply.
41
+ - Explicit copy operations such as `duplicate`, `addFragment`, and `insertPrefab` retain their existing generated-id behavior. Use direct `addMarker`/`addVolume`/`addPath`/`addNote` commands when gameplay needs caller-owned stable ids.
42
+
11
43
  # Editor agent panel (embedded)
12
44
 
13
45
  Toolbar **Agent** opens a dockable chat panel in `EditorChrome`. Tool calls use the same editor RPC verbs as the MCP/CLI bridge and the GUI — one session undo stack, interleaved with human edits.
@@ -45,6 +77,7 @@ Each `toolCalls[].name` is an editor RPC method (`set_transform`, `select`, …)
45
77
  The bridge is trustworthy so agents author instead of hardcoding — every path is honest about failure:
46
78
 
47
79
  - **Rejected mutations return `ok:false` with a reason.** A locked/cyclic `set_transform`, a `set_parent` that would form a cycle, a collection/prefab verb targeting a missing id, and a batch verb that matches nothing all fail loudly — never a phantom `{ok:true}`.
80
+ - **Command patches are atomic.** `push_document_patch` commands share `EditorSession.transaction` with core live sync: a bad command rolls back the entire batch, and an accepted batch has one undo step and document publication. See [atomic authoring batches](#atomic-authoring-batches).
48
81
  - **One decode/migrate boundary.** `decodeEditorDocument` (`@jgengine/core/editor`) validates every field with a path-specific diagnostic (`$.markers[2].position`) and migrates forward; `import_document` and a `push_document_patch` **snapshot** both clear it, so a malformed or old document fails or migrates loudly rather than corrupting a live session.
49
82
  - **Document-global id uniqueness.** Placeable ids (markers/volumes/paths/notes) are one namespace: adds re-id on collision, and a single imported document that reuses an id is rejected with its path — a duplicate-id import is impossible. Combine paths (`mergeEditorDocuments`, duplicate, overlay) re-id instead.
50
83
  - **Schema-validated input.** `decodeEditorBridgeRequest` type-checks each field a method understands against the per-method schema before it reaches `handle` — a fuzzed value (string where a number belongs, scalar where an object belongs) is rejected at the `--rpc`/HTTP/stdio/agent-tool boundary, never cast in blind. Missing/unknown fields are left to `handle`'s guards so the boundary stays forward-compatible.
@@ -73,7 +106,7 @@ bun packages/editor/src/mcp/cli.ts --game <id> \
73
106
  ```
74
107
 
75
108
  - **Required:** `id`, `kind`, and `x`/`z` (`y` optional, defaults to `0`). A game reads its own custom kinds off the document (`editorLayers.markers.filter(m => m.kind === "stash")`), so no engine change is needed to introduce one.
76
- - **Optional:** `color`, `label`, `rotationY`, `meta` — `meta` is validated against the kind's registered schema when one exists (custom kinds skip validation), same as `set_marker`.
109
+ - **Optional:** `color`, `label`, `rotationY`, `catalogId`, `meta` — `catalogId` is the entity kind a `mob`/`boss` marker spawns (without it the marker spawns nothing); `set_marker` patches it too. `meta` is validated against the kind's registered schema when one exists (custom kinds skip validation), same as `set_marker`.
77
110
  - **Id collisions re-id** exactly as `add_path`; the response's `result` carries the id the marker landed under. Use `place_asset` instead when the marker should carry a mesh (it stamps `catalogId`/`meta.assetId`); `add_marker` is for logical, mesh-free markers.
78
111
 
79
112
  ### Click-to-place custom kinds in the GUI
@@ -93,6 +126,44 @@ export const STASH_KIND = definePlaceableMarkerKind({
93
126
  - **Load path:** register from a module the editor loads. Importing your `editorKinds.ts` (side effect) from `editorLayers.ts` covers it — `loadGameLayers` imports `editorLayers.ts` for every editor session, and the game runtime imports it too, so one registration serves both.
94
127
  - `fields` also gives `add_marker`/`set_marker`/`set_meta` a schema to validate that kind's `meta` against; omit `fields` for a bare placeable marker.
95
128
 
129
+ ## Placement animation
130
+
131
+ A placed rigged asset's animation override lives at `marker.meta.animation` and reaches the game through `markerAnimation` → `ModelConfig.animation`. It is `"auto"`, `"none"` or a config with `states`, `oneShots` and an optional stored `graph` (an `AnimGraph`, validated with `parseAnimGraph` on read).
132
+
133
+ - **Inspector → Animation** picks the mode and binds idle/walk/run and one-shot clips. Custom playback exposes rate and Hold pose; choosing Single clip explicitly replaces locomotion states and a stored graph, then exposes seek time and looping. Playback edits preserve the remaining configuration. One-shot lists retain all variants; Ctrl/Cmd-click selects several clips.
134
+ - The first locomotion edit after single-clip playback seeds the rig's detected role clips, preserving deeper playback settings. Missing idle keeps an explicitly chosen single clip active; without one, playback stays at bind pose and reports the incomplete mapping. Missing walk keeps the idle pose while moving and displays a repair hint. A stored graph remains authoritative.
135
+ - **Animation workspace → Graph** (rail button, or the bottom dock's Animation tab) shows the graph the selected placement plays: layers, states and transitions, with the source (stored, built from states, or derived from clip roles).
136
+ - The scrub slider replays the graph runtime from its entry states. Parameter sliders come from the graph's blend points and conditions.
137
+ - Trigger buttons record a press at the playhead, so scrubbing back and forth replays it. The rig at the camera focus is posed from the runtime's output through the shell's `createGraphPose`.
138
+ - Editing a crossfade, or **Store graph**, writes the graph to `meta.animation.graph` as one undoable edit. **Use derived graph** drops it.
139
+ - Scripted: `set_meta` with `{ "animation": { "graph": { ... } } }` on the marker; see the `jgengine-world` recipe `character-animation.md` for the graph shape.
140
+
141
+ For a held single-clip pose, use `set_meta` with `{ "animation": { "clip": "Idle", "paused": true, "time": 0.75, "timeScale": 1, "loop": true } }`. Read it with `get_marker`, inspect it in play, adjust through the inspector, and save through the host's normal Save action. Reopen the document and read the same marker id to verify the saved values. `time` seeks single clips; graph playback has its own timeline. A placement without a moving entity remains at speed zero unless the game supplies animation parameters.
142
+
143
+ A game-owned player spawn can use the same marker's `catalogId` to associate the rig's catalog clips with the Animation inspector. In `defineGame`, set `scenePlacement: { excludeKinds: [...ENTITY_MARKER_KINDS, "player_spawn"] }` (`ENTITY_MARKER_KINDS` comes from `@jgengine/core/world/authoredObjects`). The canonical spawn marker remains available to the game's existing entity-spawn and `markerAnimation` composition, while automatic object placement skips that kind and keeps ordinary props. This exclusion list replaces the default mob/boss list. It controls placement eligibility. Live editor synchronization removes previously authored static objects that become excluded; outside synchronization, existing store objects remain. Game-owned entities are unaffected.
144
+
145
+ ## Character movement tuning
146
+
147
+ `createMovementSchema` (`@jgengine/core/editor/movementCatalog`) creates ordinary Data-tab controls from game-supplied defaults. Export a named `EditorCatalogDefinition` with that schema and named rows, then use `set_catalog_entry` or the GUI to adjust them. Only supplied controls appear: walk speed, signed world gravity, jump velocity, step height, acceleration, braking, air control, sprint multiplier, crouch multiplier and optional maximum climb grade. No movement style or capsule/backend is selected for the game.
148
+
149
+ ```ts
150
+ const readMovement = createAuthoredMovementReader(
151
+ () => getDocumentLiveSync()?.getDocument() ?? editorLayers,
152
+ "player_motion",
153
+ "player",
154
+ );
155
+ const authored = bindAuthoredMovement(readMovement, {
156
+ movement: existingMovement,
157
+ physics: existingPhysics,
158
+ });
159
+ ```
160
+
161
+ Pass `authored.movement` to the playable configuration and `authored.physics` to the game definition's physics. These stable objects expose live numeric getters; policy callbacks, collision settings and backend remain the game's choices. The reader scans only when the immutable document changes. `authored.diagnostics` reports invalid persisted values with document paths and repair guidance; invalid values retain the explicit game fallback. Supply `readMovement().config.walkSpeed` when resolving the game's selected player catalog entry, preserving its other stats and poses. Walk speed applies when that entity spawns or reseats; this API does not overwrite existing entities every frame.
162
+
163
+ Use the normal edit → play → inspect → adjust → save → reload loop. `get_catalog_entry` inspects saved controls; a fresh `import_document` or reopened scene must produce the same reader result. Runtime tuning requires this explicit binding; merely adding a Data row does not change gameplay. Capsule dimensions and terrain/controller backend configuration remain outside this catalog contract.
164
+
165
+ `maxClimbGrade` limits heightfield rise per horizontal distance, including airborne travel. Expose it only when the game requires that policy; the optional `climbGradeHeight` sampler remains a game-owned callback retained by the binding. This is independent of a capsule's slope angle and does not select collision geometry.
166
+
96
167
  ## Pure API (`@jgengine/editor`)
97
168
 
98
169
  ```ts
@@ -171,8 +242,30 @@ save); never hardcode entity stats where the document should own them.
171
242
 
172
243
  ## Asset import
173
244
 
245
+ A prop marker can store `meta.hiddenNodes` as an array of exact imported node names through an undoable `setMarker` metadata edit. Runtime props and the Materials model preview share `modelWithAuthoredNodeVisibility` (`@jgengine/core/editor/modelNodeVisibility`): omitted metadata preserves game model defaults, while `[]` clears that selection. JSON import and editor transactions reject malformed or blank names. Node hiding belongs to the cloned individual model; it does not make originally invisible source nodes visible. Nonempty selections bypass automatic prop batching, and generic scatter rejects them. Entity/player models use explicit `ModelConfig.hiddenNodes`; this marker adapter does not hydrate entity templates.
246
+
174
247
  Dropping a `.glb`/`.gltf` into the editor persists it durably, but where depends on the project shape (`editorHostPlugin`, `@jgengine/node`). A **standalone folder workspace** copies the bytes into the scanned asset folder and re-lists them under a scan-stable id (`importEditorAsset`); the id comes from the file's workspace-relative path, so a placement referencing it resolves after reload. A **promoted game** (one with a typed `src/game/assets.ts` catalog — `isPromotedProject`) instead copies the bytes into `public/<basePath>/imported/` and rewrites `assets.ts` to add a durable `extras` entry to its `buildCatalog({ extras })` call (`importPromotedAsset` → `upsertCatalogExtra`), so the **shipped** game serves and resolves the asset through its own typed catalog rather than a dev-only route. The id matches what a folder rescan would produce, the rewrite is idempotent (re-import of the same id collapses to a single entry), and an unparseable or multi-`buildCatalog` source throws so the host falls back to the folder-scan import rather than corrupt the file.
175
248
 
249
+ ## Material assets and named slots
250
+
251
+ The Materials workspace authors `document.materialAssets` through `upsert_material_asset {asset,coalesce?}`. Each versioned `MaterialAsset` has a stable ID, sparse surface fields, explicit capability declarations, texture metadata and provenance (`@jgengine/core/material/materialAsset`). `materialCapabilitiesForAsset(asset)` derives the shader requirements for its fields, maps and fabric/hair adapters; artist edits union those with existing declarations, while RPC validation still rejects undeclared requirements. Empty fields inherit the imported value; resetting a field removes its override. Renaming does not change the ID. Editable wool, cotton, silk, metal, glass, plastic, stone and hair-card starting points describe response; games own textures, models and art direction.
252
+
253
+ Use `assign_material_asset {ids,materialId,selector}` with a mesh name, material slot name or slot index. Every supplied selector field must match. Separate character skin, eyes, clothing and metal parts with separate assignments. Empty selectors fail rather than flatten a whole imported model. `list_material_assets` reads the library; `remove_material_asset {id}` removes unused assets; `clear_material_assets {ids}` removes selected objects' assignments and restores imported materials. Changes join the scene undo/redo and save path. The workspace exposes roughly six family-specific groups, full physical fields and per-map UV/sampler settings. Neutral and document-light previews use the runtime material renderer; the latter uses the authored environment and fixed sun, while game lighting can add other lights or motion.
254
+
255
+ Before assigning, select the model and open Materials, then call `list_material_slots {id}` with its authored marker ID. It reports canonical loaded mesh/slot names, local slot indices, material types, UV sets and tangents, including for a document with no material assets yet. `ready` returns observed slots; `loading` and `unavailable` return `ok:false` with an explicit status/reason instead of pretending empty data is loaded. Headless CLI cannot load the renderer. The typed host `getMaterialSlots`/`reportMaterialSlots` seam shares the workspace observation, retains up to 64 inspected model sources and rejects stale reports after a marker or catalog URL changes. Choosing another library asset edits its controls; assign it to a slot to change the selected model.
256
+
257
+ For generated building-kit models, reuse this same document library through `defineBuildingKit({ materialAssets: document.materialAssets, parts })`. Copy a selected marker's `authoredMaterialAssignments(marker.meta)` into the corresponding model variant's `materialAssignments`; do not create a second kit material catalog. The shell applies named assignments before instanced harvesting, preserving omitted native slots. This path rejects newly assigned height/displacement maps before allocation; choose an individual model renderer for them. Palette materials remain the separate broad surface path; the kit does not author scene placement or repair navigation.
258
+
259
+ Texture roles define the actual shader channels: roughness G, metalness B, AO R, sheen roughness A, clearcoat roughness G, specular intensity A, transmission R and thickness G. Anisotropy RG stores direction and B strength. Color/emissive/sheen-color/specular-color maps use sRGB; normal and numeric maps use linear data. Alpha maps use G; base-color alpha remains in its color map. Supply real runtime URLs and declare UV set, transform, normal convention, sampler, compression and physical size. Compression metadata does not transcode an image; physical size documents metres while UV transforms control rendering. Repack unsupported channels instead of silently relabelling them.
260
+
261
+ The installed renderer is Three r182. Its [physical material](https://threejs.org/docs/pages/MeshPhysicalMaterial.html) supports sheen, anisotropy, clearcoat, specular/IOR, transmission/volume and iridescence. Check the installed shader before adding controls: online docs can cover later releases. Transmission needs content behind the surface, adds rendering work and should use opacity 1. Alpha coverage cuts or blends the surface; it does not implement refraction. Directional response needs suitable UVs/normals or authored tangents, and must be reviewed after rotating the mesh or light.
262
+
263
+ [HDRP's fabric distinction](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.0/manual/understand-fabrics.html) separates diffuse cotton/wool fuzz from directional silk. The fuzzy adapter uses native grazing sheen; the woven adapter adds UV-oriented thread normals and roughness variation. Thread scale is cycles per UV, weave direction is radians, normal strength changes slope and variation changes roughness. Neither creates fibre silhouettes, cloth deformation or collision. Explicit surface values override appearance-adapter defaults.
264
+
265
+ [Unreal shading models](https://dev.epicgames.com/documentation/en-us/unreal-engine/shading-models-in-unreal-engine) have dedicated hair and cloth responses, including hair scatter/tangent/backlit inputs. [Unreal's groom workflow](https://dev.epicgames.com/documentation/en-us/unreal-engine/hair-rendering-and-simulation-in-unreal-engine) also owns strand geometry and simulation. JGengine's hair cards/strand meshes use an explicit approximation: directional physical highlights, authored coverage and card backlighting. It provides no hair BSDF, multiple scattering, groom, collision or simulation, and its skin family does not supply subsurface scattering. Similar control names do not imply parity. Review front-lit, grazing and backlit views with the actual authored geometry.
266
+
267
+ For resource review, `materialResourceMetrics` (`@jgengine/shell/render/materialAsset`) estimates loaded texture-view bytes and identifies masked, blended and transmission materials; shared uploads can make that byte estimate an upper bound. Opt in to `measureProjectedOverdraw(renderer,scene,camera,{width,height})` (`@jgengine/shell/render/projectedOverdraw`) to sample actual projected mesh fragments with native alpha discard. It requires float rendering/blending/readback, stalls the GPU, restores scene/renderer ownership and disposes its temporary resources. Depth rejection is disabled, so overlap is an upper bound rather than production early-Z cost; shadow, refraction and postprocessing passes and GPU timing are excluded. Queue and coverage flags overlap. Compare normal renderer draw counters separately, and use hardware measurements for performance claims.
268
+
176
269
  ## Grid / tile layers
177
270
 
178
271
  Grid-addressed content (rooms, tactics maps, farms, nav/rule layers) lives on the scene document as
@@ -213,9 +306,74 @@ undoable `setMinimapBake` command. Runtime feeds the stored `background`/`bounds
213
306
  mounted). It is **not** a headless CLI/MCP verb: with no viewport the sampler is null and
214
307
  `bake_minimap` returns `{ ok:false, error:"bake_minimap needs the live editor viewport" }`. To bake
215
308
  a committed scene offline, run a deterministic node script that rebuilds the base field with
216
- `groundFieldFor(world)` and calls `bakeMinimapFromDocument` (see
217
- `scripts/bake-claudecraft-minimap.ts`).
309
+ `groundFieldFor(world)` and calls `bakeMinimapFromDocument`.
218
310
  - GUI: the Terrain panel's **Bake minimap** button calls `bake_minimap` and surfaces any `ok:false`
219
311
  error inline.
220
312
  - RPC/CLI verb: `bake_minimap` (optional `padding`, `resolution`, `waterLevel`). The bake path uses
221
313
  no `Date`/`Math.random`, so the same scene + sampler always bakes byte-identical output.
314
+
315
+ ## Terrain and viewport iteration
316
+
317
+ `create_terrain` rejects invalid dimensions and more than 1,000,000 vertices before allocation; increase `cellSize` for larger maps. Brush coordinates, radius and strength must be finite, radius positive, strength nonnegative. A ramp requires both `toX` and `toZ`; blend strength is 0–1.
318
+
319
+ `blend_terrain` adds a missing material layer and paints it as one authored action: one notification and one undo. A missed or invalid brush changes neither the layer stack nor undo/redo. `set_terrain_layers` and `EditableTerrain.setLayers` preserve blends by stable layer id across additions, reorder and material parameter edits. Removing a painted layer normalizes surviving weights; replacing every painted layer clears the obsolete buffer.
320
+
321
+ Viewport placement honors grid snap for marker, zone, note and path points on X/Z, then resamples the authored sculpt composed over the shared base ground field. Paths select along visible segments as well as vertices. Multi-selection gizmos translate the whole group; rotate/scale remain available for individual supported objects.
322
+
323
+ Terrain-only scenes work with `camera_frame` / Frame all: the authored footprint and sculpt relief contribute to document bounds without a temporary marker. Native runner captures use `drive editor --param editor=standalone` and the same editor RPCs.
324
+
325
+ Grid placement resamples the authored sculpt composed over the live base ground field at snapped XZ for markers, volumes, notes and draft path points. It uses the same terrain/path policy as the ground overlays (including the ground field’s off-map behavior), and flat Y=0 when no ground sampler is mounted. Nonfinite surface heights reject placement without changing the document or clearing the tool.
326
+
327
+ ## Static prefab export
328
+
329
+ Keep source parts in `EditorPrefab.fragment` and author export settings with `set_prefab_static_bake`
330
+ (or the `setPrefabStaticBake` command): `prefabId` and `bake: { assetId, collisionBoxes?, clearances? }`.
331
+ `bake: null` clears settings. Save/reopen and undo/redo retain settings alongside the editable parts;
332
+ an invalid setting rejects the whole command without changing history. Each box uses local
333
+ `min`/`max` XYZ triples; clearances add a unique `id`. Clearances require explicit solid boxes and
334
+ may touch their edges but must not overlap them. These are authored constraints, not automatic
335
+ proof that an entrance is navigable for a particular character.
336
+
337
+ ```json
338
+ {"method":"set_prefab_static_bake","prefabId":"courtyard-arch","bake":{"assetId":"own:arch","collisionBoxes":[{"min":[-2,0,-1],"max":[-1,3,1]},{"min":[1,0,-1],"max":[2,3,1]}],"clearances":[{"id":"entry","min":[-1,0,-1],"max":[1,2,1]}]}}
339
+ ```
340
+
341
+ Export pinned source bytes offline with `bakeStaticPrefab` from
342
+ `@jgengine/assets/staticPrefabBake` (Node/Bun only; see `jgengine-assets`). Register the returned
343
+ `dims`, `collisionMesh` and `anchor: "origin"` with the output URL in the game's asset catalog.
344
+ The shared model resolver preserves that authored origin for rendering and collider consumption.
345
+ World placements remain catalog markers in `editor.scene.json`; baking never replaces or moves
346
+ them. Validate the game's real walker/route clearance before advertising walkable interiors.
347
+
348
+ ## Production creator
349
+
350
+ The game owns its menu and scene rules. The shared creator owns named durable documents, editor mounting, Save and isolated playtests:
351
+
352
+ ```tsx
353
+ import { createCreatorDocumentStorage, type CreatorConfig, type CreatorPolicy } from "@jgengine/core/editor/creatorStorage";
354
+ import { GameHost } from "@jgengine/shell/GameHost";
355
+ import type { PlayableGame } from "@jgengine/shell/registry";
356
+
357
+ const policy: CreatorPolicy = {
358
+ maxDocuments: 8, maxBytes: 128_000, maxObjects: 48,
359
+ maxPathPoints: 128, maxGridCells: 0, maxTerrainVertices: 0,
360
+ allowedKinds: ["player_spawn", "prop", "checkpoint", "finish"],
361
+ allowedAssets: ["own:platform"], allowedCatalogIds: ["platform"],
362
+ validate(document) { validateGameRules(document); },
363
+ };
364
+ const creator: CreatorConfig<PlayableGame> = {
365
+ policy,
366
+ storage: createCreatorDocumentStorage({ storage: localStorage, key: "my-game:creator:v1", policy }),
367
+ initialDocument: () => authoredStartingDocument,
368
+ createPlayable: document => buildGameFromDocument(document),
369
+ };
370
+
371
+ <GameHost playable={game} editor={() => import("@jgengine/editor")}
372
+ creator={creator} creatorOpen={creatorOpen} onCreatorOpenChange={setCreatorOpen} />;
373
+ ```
374
+
375
+ Keep `creator` and its callbacks stable across renders. `createPlayable` recomposes rendering, world and gameplay from the supplied document; do not close over the bundled starting scene. `initialDocument` supplies game-authored content, not an SDK genre preset. Creator saves are version 1 named records and reject optimistic revision conflicts. Saving at the maximum safe integer revision rejects with `Invalid scene revision` before writing; the last valid catalog stays readable. The injected storage promise must resolve only after durable success. The browser adapter writes one bounded catalog value and surfaces access/quota failures; hosted storage can implement the same list/load/save interface with transactional revisions.
376
+
377
+ The approved Add kinds and asset palette are filtered in the editor. The same validator applies before commands and transactions commit, on import, and on save. External asset URLs, prefab/directive expansion and undeclared simulation budgets are rejected. `maxSimulationParticles`, `maxFireCells`, `maxForceFields` and `maxPopulation` opt into bounded authored simulation (weather reserves 5,300 precipitation particles plus 256 rain-impact slots; collision effects reserve capacity for 32 concurrent child bursts alongside standing emitters); `validate` can restrict species, triggers or required markers. Save/export contain the authored scene; gameplay mutations stay in the captured playtest runtime and are discarded on Return to editor. Returning to saved scenes reopens the last durable save. Unsaved edits stay through Play/Return, and are discarded when leaving the editor catalog entry.
378
+
379
+ The repeatable snapshot → Play → Return workflow follows the iteration principle in [UEFN playtesting](https://dev.epicgames.com/documentation/en-us/fortnite/playtesting-your-island-in-unreal-editor-for-fortnite). Each Play captures a new validated snapshot; authoring stays locked during isolated play, and Save always writes the editable document.
@@ -0,0 +1,15 @@
1
+ # Live-sync reconnect
2
+
3
+ `DocumentLiveSync.pullPatches(revision)` retains up to 64 patches. A cursor older
4
+ than that history receives one current snapshot with the requested base revision
5
+ and the authority's current revision. Apply it with `applyDocumentPatch`; preserve
6
+ the emitted revision so the next command patch joins the same stream. Snapshot
7
+ recovery replaces the whole document and accounts for deleted authored content.
8
+
9
+ `pull_runtime_deltas` retains up to 128 ephemeral deltas. When a requested cursor
10
+ has a gap, the RPC returns an empty `deltas` array and a current `snapshot` even
11
+ without `includeSnapshot`. Replace the cached runtime snapshot; do not merge it
12
+ into old entities, since evicted removals must disappear. Continue polling from
13
+ its `seq`. A current cursor receives ordinary deltas; `includeSnapshot` can also
14
+ request the snapshot explicitly. Neither channel changes authored content unless
15
+ an explicit write-back command does so.
@@ -11,7 +11,7 @@ Own the spatial realization of game design: player metrics, topology, routes, la
11
11
 
12
12
  All authorable world content belongs in `editor.scene.json`, changed through the editor GUI or RPC/CLI. Never hand-edit the document or keep geometry, paths, spawns, zones, objectives, terrain, foliage, encounters, or coordinate arrays in code. File an editor `[FEATURE]` issue before any fallback.
13
13
 
14
- For a greenfield build, read [references/application-playbook.md](references/application-playbook.md) and produce its greenfield contract (promise, pillars, anti-pillars, core decision, and falsifier) before writing code. Audits may read only the playbook's audit sections. Read [references/live-editor-loop.md](references/live-editor-loop.md) whenever live captures, an editor-agent handoff, or iterative scene improvement is involved. Read [references/field-guide.md](references/field-guide.md) for metric families, topology, pacing, genre lenses, accessibility, and research.
14
+ For a greenfield build, read [references/application-playbook.md](references/application-playbook.md) §1 and produce its metric sheet, topology graph, beat sheet, and encounter cards before art; §3–5 hold the templates. Audits may read only the playbook's audit sections. Read [references/live-editor-loop.md](references/live-editor-loop.md) when a separate editor agent or live editor session applies the changes, or when repairing an existing scene from captures. Read [references/field-guide.md](references/field-guide.md) for metric families, topology, pacing, genre lenses, accessibility, and research.
15
15
 
16
16
  ## Choose the mode
17
17