@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.
- package/CHANGELOG.md +27 -1
- package/README.md +12 -0
- package/dist/AssetBrowser.d.ts +26 -1
- package/dist/AssetBrowser.js +54 -1
- package/dist/AssetDropZone.d.ts +9 -0
- package/dist/AssetDropZone.js +65 -0
- package/dist/CatalogsPanel.d.ts +4 -2
- package/dist/CatalogsPanel.js +162 -9
- package/dist/CityPreview.d.ts +11 -0
- package/dist/CityPreview.js +45 -0
- package/dist/CollectionsPanel.js +5 -5
- package/dist/DebugDraw.d.ts +3 -1
- package/dist/DebugDraw.js +37 -19
- package/dist/EditorApp.d.ts +9 -1
- package/dist/EditorApp.js +208 -37
- package/dist/EditorChrome.d.ts +25 -6
- package/dist/EditorChrome.js +310 -118
- package/dist/InspectorPanel.d.ts +17 -7
- package/dist/InspectorPanel.js +173 -27
- package/dist/LightingPanel.d.ts +11 -0
- package/dist/LightingPanel.js +99 -0
- package/dist/MaterialsWorkspacePanel.d.ts +12 -0
- package/dist/MaterialsWorkspacePanel.js +86 -0
- package/dist/ParentPickerMenu.d.ts +13 -0
- package/dist/ParentPickerMenu.js +33 -0
- package/dist/PerfProbe.js +69 -2
- package/dist/PrefabsPanel.js +5 -5
- package/dist/RuntimePlayBridge.d.ts +5 -3
- package/dist/RuntimePlayBridge.js +6 -13
- package/dist/ScatterPreview.d.ts +3 -1
- package/dist/ScatterPreview.js +10 -7
- package/dist/SchemaInspector.js +21 -8
- package/dist/ScriptingPanel.d.ts +15 -0
- package/dist/ScriptingPanel.js +46 -0
- package/dist/SelectionGizmo.js +114 -30
- package/dist/StandaloneEditor.d.ts +16 -9
- package/dist/StandaloneEditor.js +19 -20
- package/dist/TerrainPanel.d.ts +3 -2
- package/dist/TerrainPanel.js +16 -5
- package/dist/TerrainReadout.d.ts +17 -0
- package/dist/TerrainReadout.js +163 -0
- package/dist/TerrainReadoutHud.d.ts +9 -0
- package/dist/TerrainReadoutHud.js +20 -0
- package/dist/TerrainSculpt.js +98 -59
- package/dist/agent/AgentPanel.d.ts +5 -2
- package/dist/agent/AgentPanel.js +11 -6
- package/dist/agent/toolBridge.js +5 -0
- package/dist/assetImport.d.ts +36 -0
- package/dist/assetImport.js +66 -0
- package/dist/authoredComponentMeta.d.ts +25 -0
- package/dist/authoredComponentMeta.js +65 -0
- package/dist/gizmoPivot.d.ts +4 -0
- package/dist/gizmoPivot.js +34 -0
- package/dist/handlers/catalogs.d.ts +3 -0
- package/dist/handlers/catalogs.js +137 -0
- package/dist/handlers/collections.d.ts +3 -0
- package/dist/handlers/collections.js +57 -0
- package/dist/handlers/context.d.ts +36 -0
- package/dist/handlers/context.js +1 -0
- package/dist/handlers/document.d.ts +3 -0
- package/dist/handlers/document.js +350 -0
- package/dist/handlers/grids.d.ts +3 -0
- package/dist/handlers/grids.js +142 -0
- package/dist/handlers/hierarchy.d.ts +3 -0
- package/dist/handlers/hierarchy.js +55 -0
- package/dist/handlers/index.d.ts +8 -0
- package/dist/handlers/index.js +23 -0
- package/dist/handlers/minimap.d.ts +9 -0
- package/dist/handlers/minimap.js +32 -0
- package/dist/handlers/runtime.d.ts +3 -0
- package/dist/handlers/runtime.js +191 -0
- package/dist/handlers/terrain.d.ts +3 -0
- package/dist/handlers/terrain.js +204 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/listDocumentTriggers.d.ts +42 -0
- package/dist/listDocumentTriggers.js +49 -0
- package/dist/materialAssignments.d.ts +63 -0
- package/dist/materialAssignments.js +84 -0
- package/dist/mcp/fieldSpec.d.ts +32 -0
- package/dist/mcp/fieldSpec.js +173 -0
- package/dist/mcp/rpcRequest.d.ts +8 -5
- package/dist/mcp/rpcRequest.js +72 -83
- package/dist/mcp/tools.js +104 -763
- package/dist/networkSnapshot.d.ts +82 -0
- package/dist/networkSnapshot.js +73 -0
- package/dist/parentCandidates.d.ts +12 -0
- package/dist/parentCandidates.js +25 -0
- package/dist/perfPill.d.ts +21 -0
- package/dist/perfPill.js +20 -0
- package/dist/session.d.ts +167 -5
- package/dist/session.js +36 -879
- package/dist/shell/AnimationPanel.d.ts +17 -0
- package/dist/shell/AnimationPanel.js +134 -0
- package/dist/shell/AssetThumbnail.d.ts +12 -0
- package/dist/shell/AssetThumbnail.js +23 -0
- package/dist/shell/BottomDock.d.ts +25 -0
- package/dist/shell/BottomDock.js +40 -0
- package/dist/shell/CommandPalette.d.ts +12 -0
- package/dist/shell/CommandPalette.js +56 -0
- package/dist/shell/ConsolePanel.d.ts +8 -0
- package/dist/shell/ConsolePanel.js +45 -0
- package/dist/shell/ContentBrowser.d.ts +21 -0
- package/dist/shell/ContentBrowser.js +130 -0
- package/dist/shell/HierarchyPanel.d.ts +26 -0
- package/dist/shell/HierarchyPanel.js +387 -0
- package/dist/shell/NetworkWorkspacePanel.d.ts +11 -0
- package/dist/shell/NetworkWorkspacePanel.js +49 -0
- package/dist/shell/PlayModeBar.d.ts +12 -0
- package/dist/shell/PlayModeBar.js +27 -0
- package/dist/shell/ProfilerPanel.d.ts +10 -0
- package/dist/shell/ProfilerPanel.js +41 -0
- package/dist/shell/SceneToolbar.d.ts +43 -0
- package/dist/shell/SceneToolbar.js +122 -0
- package/dist/shell/StatusBar.d.ts +18 -0
- package/dist/shell/StatusBar.js +41 -0
- package/dist/shell/TopAppBar.d.ts +33 -0
- package/dist/shell/TopAppBar.js +64 -0
- package/dist/shell/ViewportOverlays.d.ts +23 -0
- package/dist/shell/ViewportOverlays.js +136 -0
- package/dist/shell/WorkspaceRail.d.ts +20 -0
- package/dist/shell/WorkspaceRail.js +31 -0
- package/dist/shell/cameraTelemetry.d.ts +27 -0
- package/dist/shell/cameraTelemetry.js +24 -0
- package/dist/shell/commandRegistry.d.ts +58 -0
- package/dist/shell/commandRegistry.js +133 -0
- package/dist/shell/consoleSink.d.ts +11 -0
- package/dist/shell/consoleSink.js +17 -0
- package/dist/shell/consoleStore.d.ts +27 -0
- package/dist/shell/consoleStore.js +43 -0
- package/dist/shell/fields.d.ts +39 -0
- package/dist/shell/fields.js +104 -0
- package/dist/shell/formatSavedRelative.d.ts +2 -0
- package/dist/shell/formatSavedRelative.js +14 -0
- package/dist/shell/glbThumbnailCache.d.ts +45 -0
- package/dist/shell/glbThumbnailCache.js +241 -0
- package/dist/shell/hierarchyA11y.d.ts +21 -0
- package/dist/shell/hierarchyA11y.js +35 -0
- package/dist/shell/icons.d.ts +13 -0
- package/dist/shell/icons.js +85 -0
- package/dist/shell/layoutStore.d.ts +68 -0
- package/dist/shell/layoutStore.js +156 -0
- package/dist/shell/pathFlythrough.d.ts +37 -0
- package/dist/shell/pathFlythrough.js +54 -0
- package/dist/shell/perfHistory.d.ts +70 -0
- package/dist/shell/perfHistory.js +130 -0
- package/dist/shell/renameObject.d.ts +3 -0
- package/dist/shell/renameObject.js +25 -0
- package/dist/shell/theme.d.ts +36 -0
- package/dist/shell/theme.js +37 -0
- package/dist/shell/ui.d.ts +84 -0
- package/dist/shell/ui.js +90 -0
- package/dist/shell/useGlbThumbnail.d.ts +8 -0
- package/dist/shell/useGlbThumbnail.js +19 -0
- package/dist/terrainReadoutStore.d.ts +19 -0
- package/dist/terrainReadoutStore.js +36 -0
- package/dist/uiStore.d.ts +33 -0
- package/dist/uiStore.js +13 -0
- package/dist/viewportContextMenu.d.ts +4 -2
- package/dist/viewportContextMenu.js +5 -1
- package/package.json +4 -4
- package/skills/jgengine/SKILL.md +35 -804
- package/skills/jgengine/api.md +190 -79
- package/skills/jgengine/capabilities.md +112 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-editor/SKILL.md +29 -362
- package/skills/jgengine-editor/api.md +519 -18
- package/skills/jgengine-editor/capabilities.md +91 -0
- package/skills/jgengine-editor/reference.md +113 -0
- package/skills/level-design/SKILL.md +54 -0
- package/skills/level-design/agents/openai.yaml +4 -0
- package/skills/level-design/references/application-playbook.md +192 -0
- package/skills/level-design/references/field-guide.md +337 -0
- package/skills/level-design/references/live-editor-loop.md +179 -0
- package/dist/OutlinerPanel.d.ts +0 -14
- package/dist/OutlinerPanel.js +0 -84
- package/skills/jgengine/reference-cartridge.md +0 -72
- package/skills/jgengine/reference-orphans.md +0 -9
- 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:
|
|
3
|
+
description: Author scene content and assets through the JGengine editor.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# JGengine
|
|
6
|
+
# JGengine editor
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Ownership
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
import { StandaloneEditor, createBlankPlayable, blankWorld, downloadSaver } from "@jgengine/editor";
|
|
18
|
+
## Headless fast path
|
|
46
19
|
|
|
47
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
43
|
+
## Traps
|
|
380
44
|
|
|
381
|
-
-
|
|
382
|
-
-
|
|
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.
|