@jgengine/editor 0.10.0 → 0.11.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 +1 -1
- package/dist/EditorApp.js +64 -5
- package/dist/EditorChrome.js +14 -2
- package/dist/PerfProbe.js +36 -2
- 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/mcp/rpcRequest.js +10 -0
- package/dist/mcp/tools.js +149 -0
- package/dist/perfPill.d.ts +21 -0
- package/dist/perfPill.js +20 -0
- package/dist/session.d.ts +90 -1
- package/dist/session.js +139 -1
- package/dist/terrainReadoutStore.d.ts +19 -0
- package/dist/terrainReadoutStore.js +36 -0
- package/dist/uiStore.d.ts +6 -0
- package/dist/uiStore.js +3 -0
- package/package.json +4 -4
- package/skills/jgengine/SKILL.md +32 -804
- package/skills/jgengine/api.md +20 -2
- package/skills/jgengine/capabilities.md +8 -0
- package/skills/jgengine-editor/SKILL.md +20 -368
- package/skills/jgengine-editor/api.md +122 -0
- package/skills/jgengine-editor/capabilities.md +45 -0
- package/skills/jgengine-editor/reference.md +26 -0
- package/skills/jgengine/reference-orphans.md +0 -9
- package/skills/jgengine/reference-systems.md +0 -60
package/skills/jgengine/api.md
CHANGED
|
@@ -6,32 +6,50 @@
|
|
|
6
6
|
|
|
7
7
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
8
8
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
9
|
-
- `VERSION` (const): const VERSION: "0.
|
|
9
|
+
- `VERSION` (const): const VERSION: "0.11.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
10
10
|
|
|
11
11
|
## @jgengine/core/authoring
|
|
12
12
|
|
|
13
13
|
- `ActionCodesMap` (type): type ActionCodesMap<TAction extends string = string, TCode extends string = string> = Record< TAction, ActionCodes<TCode> > — Maps each game action name to the input codes (hold/toggle keys, repeat rate) that trigger it.
|
|
14
|
+
- `AuthoredProvenance` (interface): interface AuthoredProvenance — A first-class entry in the scene document — the editor fully owns and persists it.
|
|
14
15
|
- `Drop` (interface): interface Drop — A resolved loot outcome — one item or currency grant with its rolled count.
|
|
15
16
|
- `GameDefinition` (interface): interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> — Fully-resolved game description produced by {@link defineGame} — assets, scene, and opted-in subsystems.
|
|
16
17
|
- `GameDefinitionConfig` (type): type GameDefinitionConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> = Omit<GameDefinition<TAssetRef, TMultiplayer>, "scene" | "assets"> & { assets?: AssetCatalog<TAssetRef>; } — Input to {@link defineGame} — a `GameDefinition` with `scene` derived and `assets` optional.
|
|
17
18
|
- `GameLoop` (interface): interface GameLoop<TContext = unknown> — Lifecycle hooks a game implements to drive init, per-tick simulation, and player join/leave.
|
|
18
19
|
- `GameServerConfig` (type): type GameServerConfig = "persistent" | { mode: string; [key: string]: unknown } — Hosting mode for a game's multiplayer server: `"persistent"`, or a custom mode with its own options.
|
|
20
|
+
- `GeneratedProvenance` (interface): interface GeneratedProvenance — An object derived from an authored document object — instances scattered from a painted layer, walls extruded from an authored footprint. Edits belong on the source object, not the generated instance.
|
|
19
21
|
- `InventoryDeclaration` (interface): interface InventoryDeclaration — Shape of one named inventory a game declares — slot count, accepted item types, HUD binding.
|
|
20
22
|
- `LootEntry` (interface): interface LootEntry — One possible drop in a {@link LootTableDef} — an item, currency, or generated item, its count range, and its odds.
|
|
21
23
|
- `LootTableDef` (interface): interface LootTableDef — A named, validated loot table — its roll count, weighted-vs-independent mode, and candidate entries.
|
|
24
|
+
- `OwnershipDecision` (type): type OwnershipDecision = "expose" | "bake" | "reject" — The boundary's resolution for one object.
|
|
25
|
+
- `OwnershipDiagnostic` (interface): interface OwnershipDiagnostic — One boundary problem found while auditing a set of declarations.
|
|
26
|
+
- `OwnershipVerdict` (interface): interface OwnershipVerdict — The full verdict for one object: its decision plus the flags and diagnostic the editor needs to act on it.
|
|
22
27
|
- `PhysicsConfig` (interface): interface PhysicsConfig — World gravity and jump tuning, plus scene-object collision opt-ins, for the game's physics step.
|
|
28
|
+
- `ProviderCapabilities` (interface): interface ProviderCapabilities — What a provider lets the editor do with one of its objects. Absent flags default to `false`.
|
|
29
|
+
- `RuntimeProvenance` (interface): interface RuntimeProvenance — An object owned entirely by runtime or procedural code with no document entry. Not authorable unless its provider can bake it into schema-valid authored data.
|
|
30
|
+
- `SCENE_OWNERSHIP_MANIFEST_VERSION` (const): const SCENE_OWNERSHIP_MANIFEST_VERSION: 1 — Current {@link SceneOwnershipManifest} schema version.
|
|
31
|
+
- `SceneOwnershipDeclaration` (interface): interface SceneOwnershipDeclaration — A provider's explicit ownership declaration for one editor-visible object.
|
|
32
|
+
- `SceneOwnershipManifest` (interface): interface SceneOwnershipManifest — Serializable record of a world's ownership declarations — persisted next to the scene document or shipped by a game.
|
|
33
|
+
- `SceneProvenance` (type): type SceneProvenance = | AuthoredProvenance | GeneratedProvenance | RuntimeProvenance | TransientProvenance — The provenance of one editor-visible object — authored, generated, runtime, or transient.
|
|
34
|
+
- `SceneProvenanceKind` (type): type SceneProvenanceKind = "authored" | "generated" | "runtime" | "transient" — Scene ownership boundary — gives every editor-visible object an explicit provenance and a single verdict (expose / bake / reject) so the editor never presents unauthored content as broken authored content.
|
|
23
35
|
- `SpawnPointDistanceBias` (type): type SpawnPointDistanceBias = "near" | "far" | "none" — Preference for picking a spawn point relative to `avoid` positions: closer, farther, or unweighted.
|
|
24
36
|
- `SpawnPointSelectionOptions` (interface): interface SpawnPointSelectionOptions — Semantic options for selecting a spawn point without exposing weighting internals.
|
|
37
|
+
- `TransientProvenance` (interface): interface TransientProvenance — An ephemeral simulation object — a spawned agent, projectile, or particle that never persists.
|
|
25
38
|
- `WorldFeature` (type): type WorldFeature = | ({ kind: "biomes" } & BiomesWorldConfig) | ({ kind: "voxel" } & VoxelWorldConfig) | ({ kind: "plots" } & PlotsWorldConfig) | ({ kind: "tilemap" } & TilemapWorldConfig) | EnvironmentWorldFeature | { kind: "flat" } — A declared world shape — biomes, voxel grid, plots, tilemap, environment, or flat — passed to `defineGame`.
|
|
39
|
+
- `auditManifest` (function): function auditManifest(manifest: SceneOwnershipManifest): OwnershipDiagnostic[] — Audit every declaration in a manifest, keying diagnostics by each object's provenance id. An empty result means the manifest declares all of its content cleanly — every object is authored, generated, bakeable, or a reasoned runtime/transient object.
|
|
26
40
|
- `biomes` (function): function biomes(config: BiomesWorldConfig): WorldFeature — Declares a biome-painted world — the whole-world alternative to a single `environment()` terrain.
|
|
27
41
|
- `building` (function): function building(config: BuildingEnvironmentConfig = {}): BuildingEnvironmentDescriptor — Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style.
|
|
42
|
+
- `classifyOwnership` (function): function classifyOwnership(declaration: SceneOwnershipDeclaration): OwnershipVerdict — Resolve one declaration into a single boundary verdict.
|
|
43
|
+
- `collectOwnershipDiagnostics` (function): function collectOwnershipDiagnostics(entries: Iterable<readonly [string, SceneOwnershipDeclaration]>): OwnershipDiagnostic[] — Audit a keyed set of declarations and return one diagnostic per object that breaks the boundary contract (a `reject` verdict carrying a `violation`). Deterministic: diagnostics come back in the iteration order of `entries`.
|
|
28
44
|
- `contextVerb` (function): function contextVerb(label: string, command: string, args?: Record<string, unknown>): ContextVerb — Builds a {@link ContextVerb} for a right-click menu entry.
|
|
29
45
|
- `defineGame` (function): function defineGame<TAssetRef extends ModelAssetRef, TMultiplayer>(config: GameDefinitionConfig<TAssetRef, TMultiplayer>): GameDefinition<TAssetRef, TMultiplayer> — Task-first entry point for authoring a game: fills in `scene` and default `assets`, validates `name`, OR-merges `features` from installed systems, and composes `loop` from `systems` + any classic hooks.
|
|
30
46
|
- `environment` (function): function environment(config: EnvironmentWorldConfig = {}): EnvironmentWorldFeature — Composes an `environment()` world feature from terrain, sky, weather, vegetation, water, structures, roads, and pads.
|
|
31
47
|
- `flat` (function): function flat(): WorldFeature — Declares an empty flat world — the minimal `WorldFeature` for games with no terrain of their own.
|
|
32
48
|
- `grass` (function): function grass(config: GrassEnvironmentConfig = {}): GrassEnvironmentDescriptor — Declares a grass vegetation patch for `environment()` — area, blade sizing, density, and colors.
|
|
49
|
+
- `isSceneOwnershipManifest` (function): function isSceneOwnershipManifest(value: unknown): value is SceneOwnershipManifest — Narrow unknown parsed JSON to a {@link SceneOwnershipManifest}. Structural only — verifies the version, the declarations array, and each declaration's provenance shape; it does not judge whether declarations satisfy the boundary (use {@link auditManifest} for that).
|
|
33
50
|
- `lootTable` (function): function lootTable(def: LootTableDef): LootTableDef — Validates a loot table definition and returns it unchanged, for use with {@link createLootRegistry}.
|
|
34
51
|
- `ocean` (function): function ocean(config: OceanEnvironmentConfig = {}): OceanEnvironmentDescriptor — Declares an ocean water body for `environment()` — bounds, level, and wave tuning.
|
|
52
|
+
- `ownershipKey` (function): function ownershipKey(provenance: SceneProvenance): string — Stable identity string for a provenance (e.g. `runtime:town/building-3`), suitable for keying an object in an editor tree or a diagnostics table.
|
|
35
53
|
- `plots` (function): function plots(config: PlotsWorldConfig = {}): WorldFeature — Declares a subdivided-plots world — farming, base-building, and other parcel-based layouts.
|
|
36
54
|
- `rain` (function): function rain(config: RainEnvironmentConfig = {}): RainEnvironmentDescriptor — Declares a rainfall weather effect for `environment()` — area, density, speed, wind, and drop width/opacity.
|
|
37
55
|
- `road` (function): function road(config: RoadEnvironmentConfig): RoadEnvironmentDescriptor — Declare a road ribbon for an `environment()` world; the shell drapes and renders it over the terrain.
|
|
@@ -136,7 +154,7 @@
|
|
|
136
154
|
|
|
137
155
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
138
156
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
139
|
-
- `VERSION` (const): const VERSION: "0.
|
|
157
|
+
- `VERSION` (const): const VERSION: "0.11.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
140
158
|
|
|
141
159
|
## @jgengine/core/runtime/adapter
|
|
142
160
|
|
|
@@ -19,3 +19,11 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
19
19
|
## runtime-save — save/load the whole game world through a pluggable backend, autosave or save points
|
|
20
20
|
|
|
21
21
|
- `createRuntimeSave` (function) · `import { createRuntimeSave } from "@jgengine/core/runtime/runtimeSave"`
|
|
22
|
+
|
|
23
|
+
## scene-ownership — audit a whole ownership manifest for boundary violations
|
|
24
|
+
|
|
25
|
+
- `auditManifest` (function) · `import { auditManifest } from "@jgengine/core/authoring"`
|
|
26
|
+
- `classifyOwnership` (function) · `import { classifyOwnership } from "@jgengine/core/authoring"`
|
|
27
|
+
- `collectOwnershipDiagnostics` (function) · `import { collectOwnershipDiagnostics } from "@jgengine/core/authoring"`
|
|
28
|
+
- `isSceneOwnershipManifest` (function) · `import { isSceneOwnershipManifest } from "@jgengine/core/authoring"`
|
|
29
|
+
- `ownershipKey` (function) · `import { ownershipKey } from "@jgengine/core/authoring"`
|
|
@@ -1,382 +1,34 @@
|
|
|
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
|
-
```
|
|
14
|
+
## Scene ownership boundary
|
|
19
15
|
|
|
20
|
-
|
|
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.
|
|
21
17
|
|
|
22
|
-
##
|
|
18
|
+
## Canonical workflow
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
1. Inspect the current scene and available editor capabilities.
|
|
21
|
+
2. Make authoring changes through the GUI or `bun packages/editor/src/mcp/cli.ts`; do not hand-edit the JSON.
|
|
22
|
+
3. Save the scene document and verify the intended objects/layers are present.
|
|
23
|
+
4. Make gameplay reference stable authored ids, path kinds, markers, or zones rather than copying coordinates.
|
|
24
|
+
5. Render through shared authored-scene primitives and verify with `jgengine-verify`.
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
import { standaloneSavePlugin } from "@jgengine/node/devSavePlugin";
|
|
28
|
-
export default defineConfig({ plugins: [react(), tailwindcss(), standaloneSavePlugin()] });
|
|
29
|
-
```
|
|
26
|
+
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.
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
## Traps
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
```
|
|
41
|
-
|
|
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):
|
|
43
|
-
|
|
44
|
-
```tsx
|
|
45
|
-
import { StandaloneEditor, createBlankPlayable, blankWorld, downloadSaver } from "@jgengine/editor";
|
|
46
|
-
|
|
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")} />;
|
|
49
|
-
|
|
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") });
|
|
52
|
-
```
|
|
53
|
-
|
|
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
|
|
90
|
-
|
|
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.
|
|
99
|
-
|
|
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
|
-
```
|
|
370
|
-
|
|
371
|
-
## Core APIs (`editor/`)
|
|
372
|
-
|
|
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)
|
|
378
|
-
|
|
379
|
-
## Do not
|
|
380
|
-
|
|
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)
|
|
30
|
+
- 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.
|
|
31
|
+
- Runtime-generated decoration does not become editor-owned merely because it is visual; decide ownership explicitly.
|
|
32
|
+
- Asset-facing, units, pivot, footprint, and placement policy belong in asset/catalog metadata, not corrective game transforms.
|
|
33
|
+
- Paths and markers should drive both rendering and gameplay from the same document.
|
|
34
|
+
- Visual presence summaries prove content exists; only an inspected shot proves composition and finish.
|