@jgengine/shell 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/CombatPresentation.d.ts +5 -0
  3. package/dist/CombatPresentation.js +8 -0
  4. package/dist/GameHost.d.ts +27 -1
  5. package/dist/GameHost.js +83 -4
  6. package/dist/GamePlayer.d.ts +5 -0
  7. package/dist/GamePlayer.js +5 -0
  8. package/dist/GamePlayerShell.d.ts +5 -0
  9. package/dist/GamePlayerShell.js +63 -359
  10. package/dist/GameUiPreview.js +3 -0
  11. package/dist/Shell3dPresentation.d.ts +63 -0
  12. package/dist/Shell3dPresentation.js +302 -0
  13. package/dist/ShellChrome.d.ts +51 -0
  14. package/dist/ShellChrome.js +51 -0
  15. package/dist/ShellHudPresentation.d.ts +41 -0
  16. package/dist/ShellHudPresentation.js +22 -0
  17. package/dist/audio/audioEngine.d.ts +4 -0
  18. package/dist/audio/audioEngine.js +111 -26
  19. package/dist/audio/audioWire.d.ts +22 -4
  20. package/dist/audio/audioWire.js +46 -3
  21. package/dist/audio/loopParams.d.ts +20 -0
  22. package/dist/audio/loopParams.js +28 -0
  23. package/dist/camera/GameCameraRig.js +6 -1
  24. package/dist/camera/GameOrbitCamera.js +7 -1
  25. package/dist/camera/cameraCollision.d.ts +28 -0
  26. package/dist/camera/cameraCollision.js +34 -0
  27. package/dist/camera/cameraRigs.d.ts +3 -1
  28. package/dist/camera/cameraRigs.js +24 -10
  29. package/dist/camera/index.d.ts +1 -0
  30. package/dist/camera/index.js +1 -0
  31. package/dist/camera/rigMath.d.ts +27 -0
  32. package/dist/camera/rigMath.js +45 -0
  33. package/dist/defineGame.d.ts +15 -1
  34. package/dist/defineGame.js +26 -4
  35. package/dist/devtools/DevtoolsOverlay.d.ts +4 -4
  36. package/dist/devtools/DevtoolsOverlay.js +28 -12
  37. package/dist/devtools/agentBridge.js +4 -0
  38. package/dist/devtools/perfDiagnose.js +1 -1
  39. package/dist/diagnostics/RuntimeDiagnostics.d.ts +7 -0
  40. package/dist/diagnostics/RuntimeDiagnostics.js +23 -12
  41. package/dist/environment/EnvironmentScene.js +17 -3
  42. package/dist/environment/RoadJunctions.d.ts +12 -0
  43. package/dist/environment/RoadJunctions.js +69 -0
  44. package/dist/environment/RoadRibbons.d.ts +8 -2
  45. package/dist/environment/RoadRibbons.js +26 -8
  46. package/dist/environment/terrainGroundColor.d.ts +9 -0
  47. package/dist/environment/terrainGroundColor.js +22 -0
  48. package/dist/gameKit.d.ts +27 -0
  49. package/dist/gameKit.js +29 -0
  50. package/dist/map/MapMarkerBeacons.d.ts +3 -2
  51. package/dist/postfx/PostProcessing.d.ts +9 -1
  52. package/dist/postfx/PostProcessing.js +11 -4
  53. package/dist/presentationResolve.d.ts +39 -0
  54. package/dist/presentationResolve.js +53 -0
  55. package/dist/registry.d.ts +1 -0
  56. package/dist/render/PartMotion.d.ts +18 -0
  57. package/dist/render/PartMotion.js +91 -0
  58. package/dist/render/SceneModels.d.ts +9 -2
  59. package/dist/render/SceneModels.js +31 -180
  60. package/dist/render/assetBase.d.ts +17 -0
  61. package/dist/render/assetBase.js +35 -0
  62. package/dist/render/measureBounds.d.ts +34 -0
  63. package/dist/render/measureBounds.js +127 -0
  64. package/dist/render/modelLoad.js +4 -1
  65. package/dist/render/resolveModel.d.ts +15 -0
  66. package/dist/render/resolveModel.js +43 -2
  67. package/dist/render/useModelAnimation.d.ts +10 -0
  68. package/dist/render/useModelAnimation.js +198 -0
  69. package/dist/scatter/InstancedScatter.js +5 -0
  70. package/dist/scatter/scatterProxies.js +24 -0
  71. package/dist/scene/AuthoredScene.d.ts +3 -1
  72. package/dist/scene/AuthoredScene.js +7 -2
  73. package/dist/scene/CityRenderer.d.ts +2 -0
  74. package/dist/scene/CityRenderer.js +1031 -0
  75. package/dist/scene/GrassFieldRenderer.js +53 -5
  76. package/dist/scene/PoleLineRenderer.d.ts +2 -0
  77. package/dist/scene/PoleLineRenderer.js +287 -0
  78. package/dist/scene/SoilPatchRenderer.js +70 -7
  79. package/dist/scene/WaterRenderer.js +34 -10
  80. package/dist/scene/builtinSceneKindRenderers.js +4 -0
  81. package/dist/scene/cityWindowMaterial.d.ts +16 -0
  82. package/dist/scene/cityWindowMaterial.js +81 -0
  83. package/dist/scene/sceneKindRenderers.d.ts +2 -0
  84. package/dist/settings/appliedSettings.d.ts +2 -1
  85. package/dist/settings/appliedSettings.js +4 -2
  86. package/dist/structures/GeneratedBuilding.d.ts +4 -0
  87. package/dist/structures/GeneratedBuilding.js +17 -7
  88. package/dist/structures/TransformGizmo.d.ts +11 -1
  89. package/dist/structures/TransformGizmo.js +9 -5
  90. package/dist/terrain/GrassField.d.ts +11 -3
  91. package/dist/terrain/GrassField.js +16 -6
  92. package/dist/terrain/grassGeometry.d.ts +33 -1
  93. package/dist/terrain/grassGeometry.js +83 -21
  94. package/dist/terrain/grassMaterial.d.ts +18 -0
  95. package/dist/terrain/grassMaterial.js +66 -13
  96. package/dist/terrain/heightfieldGeometry.d.ts +34 -0
  97. package/dist/terrain/heightfieldGeometry.js +109 -0
  98. package/dist/terrain/index.d.ts +1 -0
  99. package/dist/terrain/index.js +1 -0
  100. package/dist/terrain/soilPatchMaterial.d.ts +15 -2
  101. package/dist/terrain/soilPatchMaterial.js +46 -8
  102. package/dist/terrain/terrainDetailMaterial.js +15 -2
  103. package/dist/water/Ocean.d.ts +7 -1
  104. package/dist/water/Ocean.js +16 -3
  105. package/dist/water/OceanConfig.d.ts +9 -0
  106. package/dist/water/OceanConfig.js +6 -0
  107. package/dist/water/OceanMaterial.d.ts +12 -0
  108. package/dist/water/OceanMaterial.js +9 -0
  109. package/dist/water/OceanShader.d.ts +2 -2
  110. package/dist/water/OceanShader.js +78 -10
  111. package/dist/world/PlaceScene.d.ts +11 -0
  112. package/dist/world/PlaceScene.js +27 -0
  113. package/dist/world/WorldHud.d.ts +3 -1
  114. package/dist/world/WorldHud.js +2 -2
  115. package/dist/world/WorldScene.js +32 -4
  116. package/dist/worldSky.d.ts +7 -1
  117. package/dist/worldSky.js +11 -1
  118. package/package.json +4 -4
  119. package/skills/game-design/SKILL.md +28 -0
  120. package/skills/game-design/agents/openai.yaml +4 -0
  121. package/skills/game-design/references/application-playbook.md +170 -0
  122. package/skills/game-design/references/field-guide.md +288 -0
  123. package/skills/jgengine/SKILL.md +8 -4
  124. package/skills/jgengine/api.md +182 -81
  125. package/skills/jgengine/capabilities.md +104 -2
  126. package/skills/jgengine/recipes/minimal-game.md +88 -0
  127. package/skills/jgengine-ui/SKILL.md +17 -10
  128. package/skills/jgengine-ui/api.md +316 -51
  129. package/skills/jgengine-ui/capabilities.md +135 -2
  130. package/skills/jgengine-ui/recipes/portable-minimap.md +150 -0
  131. package/skills/jgengine-ui/reference.md +59 -9
  132. package/skills/jgengine-ui/references/visual-scorecard.md +1 -1
  133. package/skills/jgengine-world/SKILL.md +13 -0
  134. package/skills/jgengine-world/api.md +453 -21
  135. package/skills/jgengine-world/capabilities.md +112 -2
  136. package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
  137. package/skills/jgengine-world/reference.md +43 -4
  138. package/skills/level-design/SKILL.md +54 -0
  139. package/skills/level-design/agents/openai.yaml +4 -0
  140. package/skills/level-design/references/application-playbook.md +192 -0
  141. package/skills/level-design/references/field-guide.md +337 -0
  142. package/skills/level-design/references/live-editor-loop.md +179 -0
  143. package/dist/cartridge/runtime.d.ts +0 -15
  144. package/dist/cartridge/runtime.js +0 -556
  145. package/dist/cartridge/spec.d.ts +0 -188
  146. package/dist/cartridge/spec.js +0 -19
  147. package/dist/cartridge/validate.d.ts +0 -2
  148. package/dist/cartridge/validate.js +0 -78
  149. package/dist/cartridge.d.ts +0 -145
  150. package/dist/cartridge.js +0 -246
  151. package/skills/jgengine/reference-cartridge.md +0 -72
@@ -4,7 +4,7 @@
4
4
 
5
5
  Reach for these before hand-rolling. Each row is *the thing you need* → *the primitive that already does it*.
6
6
 
7
- ## action-bar — drop-in cooldown/cost/hotkey action bar over the headless action model
7
+ ## action-bar — optional skinned action-bar composition over the headless model — games own chrome
8
8
 
9
9
  - `ActionBar` (function) · `import { ActionBar } from "@jgengine/react"`
10
10
 
@@ -28,6 +28,22 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
28
28
 
29
29
  - `ActionTooltip` (function) · `import { ActionTooltip } from "@jgengine/react"`
30
30
 
31
+ ## camera-transparent-decor — let author decor pass through the orbit spring-arm; a child opts back in with jgCameraCollide
32
+
33
+ - `isCameraOccluderTransparent` (function) · `import { isCameraOccluderTransparent } from "@jgengine/shell/camera"`
34
+
35
+ ## character-paperdoll — equip-slot paperdoll — flanking columns around a portrait, activatable slots
36
+
37
+ - `Paperdoll` (function) · `import { Paperdoll } from "@jgengine/react"`
38
+
39
+ ## character-sheet — drop-in paperdoll + derived-stat sheet in a HudFrame — game supplies slots/stats
40
+
41
+ - `CharacterSheet` (function) · `import { CharacterSheet } from "@jgengine/react"`
42
+
43
+ ## character-statlist — derived-stat readout — grouped small-caps label→value rows, token-themed
44
+
45
+ - `StatList` (function) · `import { StatList } from "@jgengine/react"`
46
+
31
47
  ## clock-format — format a signed time gap like a race split (+/- m:ss.ff)
32
48
 
33
49
  - `formatDelta` (function) · `import { formatDelta } from "@jgengine/core/ui"`
@@ -61,6 +77,67 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
61
77
 
62
78
  - `useEventMeter` (function) · `import { useEventMeter } from "@jgengine/react"`
63
79
 
80
+ ## heightfield-mesh-update — in-place partial update of a displaced ground plane mesh
81
+
82
+ - `displaceHeightfieldGeometry` (function) · `import { displaceHeightfieldGeometry } from "@jgengine/shell/terrain"`
83
+
84
+ ## hud-canvas — the HUD root surface — design-resolution scaling plus live panel-placement editing (F2+C)
85
+
86
+ - `HudCanvas` (function) · `import { HudCanvas } from "@jgengine/react"`
87
+
88
+ ## hud-clock — in-game time-of-day readout bound to the sim clock
89
+
90
+ - `Clock` (function) · `import { Clock } from "@jgengine/react"`
91
+
92
+ ## hud-coins — currency counter widget bound to a currency id
93
+
94
+ - `Coins` (function) · `import { Coins } from "@jgengine/react"`
95
+
96
+ ## hud-frame — optional framed panel skins (glass/plate/retro/themed) — games own product chrome
97
+
98
+ - `HudFrame` (function) · `import { HudFrame } from "@jgengine/react"`
99
+
100
+ ## hud-hotbar — optional inventory hotbar building block — games own slot chrome
101
+
102
+ - `Hotbar` (function) · `import { Hotbar } from "@jgengine/react"`
103
+
104
+ ## hud-icons — painted-icon treatment for hotbar/action slots — glyph + gradient + count badge
105
+
106
+ - `IconSchool` (type) · `import { IconSchool } from "@jgengine/react"`
107
+
108
+ ## hud-layout — persistent, live-editable HUD panel layout state for `HudCanvas`
109
+
110
+ - `useHudLayout` (function) · `import { useHudLayout } from "@jgengine/react"`
111
+
112
+ ## hud-layout-persist — inject a host callback so canvas HUD edits commit into the scene document
113
+
114
+ - `HudLayoutPersistProvider` (function) · `import { HudLayoutPersistProvider } from "@jgengine/react"`
115
+
116
+ ## hud-layout-persist-hook — read the host panel-commit port from a HUD layout widget
117
+
118
+ - `useHudLayoutPersist` (function) · `import { useHudLayoutPersist } from "@jgengine/react"`
119
+
120
+ ## hud-panel — anchored HUD slot — drop any widget in a named, player-movable panel
121
+
122
+ - `HudPanel` (function) · `import { HudPanel } from "@jgengine/react"`
123
+
124
+ ## hud-slots — atomic themed equipment/weapon/armor/potion slot grids with painted icons
125
+
126
+ - `SlotItem` (interface) · `import { SlotItem } from "@jgengine/react"`
127
+
128
+ ## hud-stat-bar — optional stat-bound bar building block (health, mana, stamina) — prefer game-owned chrome
129
+
130
+ - `StatBar` (function) · `import { StatBar } from "@jgengine/react"`
131
+
132
+ ## hud-theme — game-authored token object for bars, frames, slots, and minimap ring
133
+
134
+ - `HudThemePalette` (interface) · `import { HudThemePalette } from "@jgengine/react"`
135
+
136
+ ## hud-vitals — atomic purpose-named vitals bars (token-themed parts, not a finished HUD)
137
+
138
+ - `BarTokens` (interface) · `import { BarTokens } from "@jgengine/react"`
139
+ - `barTokens` (function) · `import { barTokens } from "@jgengine/react"`
140
+
64
141
  ## key-hint — keyboard/mouse control hint that hides itself on touch
65
142
 
66
143
  - `KeyHint` (function) · `import { KeyHint } from "@jgengine/react"`
@@ -69,6 +146,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
69
146
 
70
147
  - `Keycap` (function) · `import { Keycap } from "@jgengine/react"`
71
148
 
149
+ ## live-markers — a self-ticking MarkerSet kept in sync from the live scene each HUD tick — pass a rebuild(markers, ctx) that clears+repopulates, no hand-rolled setInterval
150
+
151
+ - `useLiveMarkers` (function) · `import { useLiveMarkers } from "@jgengine/react"`
152
+
72
153
  ## minimap — framed circular minimap with terrain bake, fog, markers, and facing arrow
73
154
 
74
155
  - `Minimap` (function) · `import { Minimap } from "@jgengine/react"`
@@ -81,6 +162,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
81
162
 
82
163
  - `MinimapPanel` (function) · `import { MinimapPanel } from "@jgengine/react"`
83
164
 
165
+ ## minimap-track-hud — horizontal linear track minimap (corridor/route progress) — colored zone spans and gate/player pips positioned by 0..1 fraction, sharing chrome with the radial Minimap
166
+
167
+ - `MinimapTrack` (function) · `import { MinimapTrack } from "@jgengine/react"`
168
+
84
169
  ## nameplates — billboarded name + HP bar over nearby entities
85
170
 
86
171
  - `WorldNameplates` (function) · `import { WorldNameplates } from "@jgengine/shell/world/WorldHud"`
@@ -89,6 +174,46 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
89
174
 
90
175
  - `formatOrdinal` (function) · `import { formatOrdinal } from "@jgengine/core/ui"`
91
176
 
177
+ ## panel-close — close a window while remembering its last position
178
+
179
+ - `closePanel` (function) · `import { closePanel } from "@jgengine/core/ui"`
180
+
181
+ ## panel-close-top — close the focused (topmost) closable window — the ESC behavior
182
+
183
+ - `closeTopPanel` (function) · `import { closeTopPanel } from "@jgengine/core/ui"`
184
+
185
+ ## panel-focus — raise a window to the top of the focus/z stack
186
+
187
+ - `focusPanel` (function) · `import { focusPanel } from "@jgengine/core/ui"`
188
+
189
+ ## panel-host — render a manager's open windows as draggable, closable, z-stacked dialogs
190
+
191
+ - `PanelHost` (function) · `import { PanelHost } from "@jgengine/react"`
192
+
193
+ ## panel-hotkey — resolve a keybind (code or key) to the panel id it toggles
194
+
195
+ - `panelByHotkey` (function) · `import { panelByHotkey } from "@jgengine/core/ui"`
196
+
197
+ ## panel-move — override a window's on-surface position (the drag commit)
198
+
199
+ - `movePanel` (function) · `import { movePanel } from "@jgengine/core/ui"`
200
+
201
+ ## panel-open — open a window, raise its z, and enforce exclusive-group membership
202
+
203
+ - `openPanel` (function) · `import { openPanel } from "@jgengine/core/ui"`
204
+
205
+ ## panel-ordered-open — open panels' defs in z/render order for a window host
206
+
207
+ - `orderedOpen` (function) · `import { orderedOpen } from "@jgengine/core/ui"`
208
+
209
+ ## panel-state — serializable open/z/position state for a set of toggleable game windows
210
+
211
+ - `createPanelState` (function) · `import { createPanelState } from "@jgengine/core/ui"`
212
+
213
+ ## panel-toggle — toggle a window open/closed from a single keybind
214
+
215
+ - `togglePanel` (function) · `import { togglePanel } from "@jgengine/core/ui"`
216
+
92
217
  ## placement-ghost — render valid/invalid placement preview mesh
93
218
 
94
219
  - `PlacementGhost` (function) · `import { PlacementGhost } from "@jgengine/shell/structures"`
@@ -113,7 +238,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
113
238
 
114
239
  - `useSelectionView` (function) · `import { useSelectionView } from "@jgengine/react"`
115
240
 
116
- ## selection-panel — drop-in selection panel: primary summary beside a portrait strip / group chips
241
+ ## selection-panel — optional selection-panel composition: summary + portrait strip / group chips
117
242
 
118
243
  - `SelectionPanel` (function) · `import { SelectionPanel } from "@jgengine/react"`
119
244
 
@@ -133,10 +258,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
133
258
 
134
259
  - `TransformGizmo` (const) · `import { TransformGizmo } from "@jgengine/shell/structures"`
135
260
 
261
+ ## use-panels — headless toggleable-window manager with keybind + ESC handling over the core panel model
262
+
263
+ - `usePanels` (function) · `import { usePanels } from "@jgengine/react"`
264
+
136
265
  ## vital-bar — compact current/max vital meter for an entity summary
137
266
 
138
267
  - `VitalBar` (function) · `import { VitalBar } from "@jgengine/react"`
139
268
 
269
+ ## window — standalone draggable, closable window primitive over HudFrame — no manager required
270
+
271
+ - `Window` (function) · `import { Window } from "@jgengine/react"`
272
+
140
273
  ## world-object-highlights — ground-ring highlight over every selected placed object
141
274
 
142
275
  - `WorldObjectHighlights` (function) · `import { WorldObjectHighlights } from "@jgengine/shell/world/WorldHud"`
@@ -0,0 +1,150 @@
1
+ # Add the minimap to an existing React game
2
+
3
+ Use this recipe when a React game already owns its entities in an array, ECS
4
+ query, Zustand/Redux-style store, or another observable collection. JGengine
5
+ only needs a display view of those records; do not copy them into a
6
+ `MarkerSet`.
7
+
8
+ ## Install
9
+
10
+ Keep the React and React DOM versions already used by the project, then add the
11
+ two focused JGengine packages:
12
+
13
+ ```sh
14
+ bun add @jgengine/core @jgengine/react
15
+ ```
16
+
17
+ The minimap imports neither Three.js nor React Three Fiber and does not require
18
+ `GameProvider`.
19
+
20
+ ## Static entity arrays
21
+
22
+ Project an existing array directly. Keep the array stable with the same memo or
23
+ selector the project already uses.
24
+
25
+ ```tsx
26
+ import { useMemo } from "react";
27
+ import { Minimap } from "@jgengine/react/map";
28
+
29
+ interface Unit {
30
+ id: string;
31
+ x: number;
32
+ z: number;
33
+ team: "ally" | "enemy";
34
+ }
35
+
36
+ export function MapHud({
37
+ units,
38
+ cameraTarget,
39
+ }: {
40
+ units: readonly Unit[];
41
+ cameraTarget: { x: number; z: number };
42
+ }) {
43
+ const markers = useMemo(
44
+ () =>
45
+ units.map((unit) => ({
46
+ id: unit.id,
47
+ position: [unit.x, 0, unit.z] as const,
48
+ kind: unit.team,
49
+ })),
50
+ [units],
51
+ );
52
+
53
+ return (
54
+ <Minimap
55
+ markers={markers}
56
+ center={[cameraTarget.x, cameraTarget.z]}
57
+ worldRadius={120}
58
+ className="game-minimap"
59
+ />
60
+ );
61
+ }
62
+ ```
63
+
64
+ Only `id` and `position` are required. `kind`, `label`, `heading`, and `meta`
65
+ are optional display data; lifecycle timestamps are not part of this view.
66
+
67
+ ## Observable external store
68
+
69
+ Create the adapter once per store. The source caches its projected marker array
70
+ until the external store emits, so React snapshot reads do not remap the world
71
+ on every frame.
72
+
73
+ ```tsx
74
+ import { useMemo } from "react";
75
+ import { createMarkerSource } from "@jgengine/core/world/markers";
76
+ import { Minimap } from "@jgengine/react/map";
77
+
78
+ interface Unit {
79
+ id: string;
80
+ x: number;
81
+ z: number;
82
+ team: string;
83
+ name: string;
84
+ }
85
+
86
+ interface ExistingWorldStore {
87
+ subscribe(listener: () => void): () => void;
88
+ getState(): { units: readonly Unit[] };
89
+ }
90
+
91
+ export function ExistingGameMinimap({
92
+ world,
93
+ cameraTarget,
94
+ }: {
95
+ world: ExistingWorldStore;
96
+ cameraTarget: { x: number; z: number };
97
+ }) {
98
+ const markers = useMemo(
99
+ () =>
100
+ createMarkerSource({
101
+ subscribe: (listener) => world.subscribe(listener),
102
+ getSnapshot: () => world.getState().units,
103
+ project: (unit) => ({
104
+ id: unit.id,
105
+ position: [unit.x, 0, unit.z] as const,
106
+ kind: unit.team,
107
+ label: unit.name,
108
+ }),
109
+ }),
110
+ [world],
111
+ );
112
+
113
+ return (
114
+ <Minimap
115
+ markers={markers}
116
+ center={[cameraTarget.x, cameraTarget.z]}
117
+ worldRadius={120}
118
+ />
119
+ );
120
+ }
121
+ ```
122
+
123
+ For SSR, make the store's first client snapshot match the server snapshot. When
124
+ they differ structurally, pass `getServerSnapshot` to `createMarkerSource` and
125
+ read the same serialized initial units used to hydrate the external store.
126
+
127
+ ## Ownership
128
+
129
+ The existing project still owns entities, updates, persistence, simulation,
130
+ camera, renderer, input, scheduling, and the subscription. JGengine owns only
131
+ the cached entity-to-marker projection, core world-to-map projection math, and
132
+ the React SVG presentation. Save and restore the project's unit state as usual;
133
+ markers are derived data and need no parallel save format.
134
+
135
+ Use `kindStyles`, `className`, and `style` to skin the production `Minimap`.
136
+ When the project owns every chrome element, compose `MinimapChrome` inside its
137
+ own SVG instead of adopting the framed default.
138
+
139
+ ## Common traps
140
+
141
+ - Keep `getSnapshot()` array identity stable until `subscribe()` emits. This is
142
+ the React external-store contract.
143
+ - Create `createMarkerSource` outside render or in `useMemo`; recreating it each
144
+ render discards its projection cache.
145
+ - Pass world coordinates, not Three.js screen coordinates. Marker positions are
146
+ `[x, y, z]`; the minimap projects X/Z.
147
+ - Do not poll the source from a render loop. Notify subscribers when the
148
+ caller-owned collection changes.
149
+ - Do not add `createdAt` or `expiresAt` unless the project intentionally adopts
150
+ `MarkerSet` lifecycle behavior.
@@ -4,9 +4,11 @@ This reference defines the required visual and interaction quality for JGengine
4
4
 
5
5
  ## The rule
6
6
 
7
- A game must visually own its viewport. It must not resemble a dashboard, landing page, documentation page, or ordinary responsive web app.
7
+ A game must visually own its viewport **and its entire interface**. Presentation is game content: each title designs a custom HUD, menus, feedback, and art direction. It must not resemble a dashboard, landing page, documentation page, ordinary responsive web app, **or another JGengine game's default widget stack**.
8
8
 
9
- HTML and React are valid implementation tools. Website visual grammar is not the default.
9
+ The engine ships both headless data/layout/accessibility seams *and* good drop-in building blocks — inventory grids, toggleable windows, character sheets, bars, action bars. `@jgengine/react` offers all of them. Composing and reskinning those building blocks into this game's arrangement is using the engine correctly. What's incomplete is shipping a whole *unarranged, unskinned generic face* — a page that looks like a dashboard or another game's untouched default stack, with no art direction, terminology, or placement of its own.
10
+
11
+ HTML and React are valid implementation tools. Website visual grammar is not the default. Engine default chrome is not the default either.
10
12
 
11
13
  ## 1. Start with a concise UI art direction
12
14
 
@@ -49,11 +51,14 @@ Identify the screens the game actually needs:
49
51
 
50
52
  - boot/loading
51
53
  - title or attract screen
54
+ - **main menu** (the game's front-end — see the Main menu section)
52
55
  - mode selection
56
+ - character create / character select where the game has classes, characters, or profiles
53
57
  - onboarding/tutorial
54
58
  - gameplay HUD
55
59
  - pause
56
60
  - settings
61
+ - credits
57
62
  - map/inventory/dialogue where relevant
58
63
  - victory/results
59
64
  - failure/retry
@@ -95,13 +100,13 @@ The active game should behave like an application mode:
95
100
  Recommended layer contract:
96
101
 
97
102
  ```tsx
98
- <GamePlayer>
103
+ <GameHost>
99
104
  <WorldLayer />
100
105
  <HudLayer />
101
106
  <ControlLayer />
102
107
  <ScreenLayer />
103
108
  <SystemLayer />
104
- </GamePlayer>
109
+ </GameHost>
105
110
  ```
106
111
 
107
112
  - `WorldLayer`: game renderer
@@ -142,9 +147,11 @@ Design-resolution fit is on by default for every game: each `HudCanvas` auto-sca
142
147
 
143
148
  **Overflow is an error, not a style note.** `HudCanvas` measures every `HudPanel` against the viewport at runtime; offenders land in a `data-hud-overflow` attribute (and a console warning), and `bun run shoot <game> --device mobile` (or `both`) exits non-zero naming the escaping panels. A game is not mobile-done while shoot reports HUD OVERFLOW.
144
149
 
150
+ **Authoring placement lives in the editor.** HUD panel placement is authored from the scene editor's **HUD** mode (F2+E → **HUD** toolbar button), not a separate devtools chord — the button mounts the game's real `HudCanvas` over the frozen world with drag/resize editing on. Dragging a panel writes an undoable `setUiPanel` patch into the scene document's `ui.panels` section (the same document the `canvas_move_panel` / `canvas_resize_panel` RPC verbs still target), so authored layout ships in `editor.scene.json`. The author's canvas **Done** button (or Esc / F2+C / F2+E) leaves HUD mode. Per-panel resize axes come from the panel `type`'s `registerHudPanelType` spec.
151
+
145
152
  ### Phase-gated HUD visibility (`showDuring`)
146
153
 
147
- `HudCanvas` and `HudPanel` take an opt-in `showDuring?: GamePhase[]` — the element renders only while `gamePhase` is one of the listed phases (`"menu" | "playing" | "paused" | "ended"`), so a non-cartridge game hides its HUD under menu/end overlays without hand-rolling a phase check. Omit it for the default always-visible behavior. Gate the whole HUD with `<HudCanvas showDuring={["playing"]}>`, or keep a single results panel up with a per-`HudPanel` value. The read degrades to `"playing"` when the component renders outside a `GameProvider` (component showcases, previews), so it never throws there.
154
+ `HudCanvas` and `HudPanel` take an opt-in `showDuring?: GamePhase[]` — the element renders only while `gamePhase` is one of the listed phases (`"menu" | "playing" | "paused" | "ended"`), so a game hides its HUD under menu/end overlays without hand-rolling a phase check. Omit it for the default always-visible behavior. Gate the whole HUD with `<HudCanvas showDuring={["playing"]}>`, or keep a single results panel up with a per-`HudPanel` value. The read degrades to `"playing"` when the component renders outside a `GameProvider` (component showcases, previews), so it never throws there.
148
155
 
149
156
  ### Shared viewport allocation (`GameViewportProvider`, region collision)
150
157
 
@@ -220,12 +227,27 @@ Prefer small headless or lightly styled primitives over a giant universal design
220
227
 
221
228
  A primitive should expose game-oriented choices such as shape, material, urgency, placement, hierarchy, entry motion, icon treatment, compactness, and diegetic-versus-overlay presentation.
222
229
 
223
- **Minimap** is already shipped, not hand-rolled: `Minimap` / `WorldMap` / `Compass` from `@jgengine/react/map` render a framed SVG minimap over a `MarkerSet` (`createMarkerSet`, `@jgengine/core/world/markers`) and optional `FogField`, projecting via `@jgengine/core/world/minimap` (`projectToMinimap`, `headingToBearing`, edge-clamp). A game feeds flat props (`markers`, `center: [x,z]`, `worldRadius`, `size`, `facingYaw`, `rotate`) and repopulates the marker set each HUD tick from live entities — no custom canvas painter. `MinimapPanel` (same module) composes zone-label + clock header slots and a `Compass` strip around a `Minimap` — pass `zoneLabel`/`clock` nodes (e.g. from `useGameClock().calendar`) and `showCompass`/`compassProps`; omit either header slot and it collapses, `showCompass={false}` drops the strip. Rolling a **custom** minimap around your own course/route content (not the full `Minimap` panel)? `MinimapChrome` is the headless chrome layer other games were re-drawing by hand: nest it inside your own `<svg>` for the ring `frame`, and pass `markers: MinimapChromeMarker[]` (`{ id, position, heading?, color?, radius?, glyph? }`) for edge-clamped dots that draw as a rotated arrow whenever a marker carries a `heading` (the compass-arrow / player-blip layer) — never hand-roll `clampToMinimapEdge` + a `<polygon>` again. **Nameplates**: `WorldNameplates` (`@jgengine/shell/world/WorldHud`) billboards a name + optional 78×6px HP bar over every nearby non-local entity — turn it on declaratively via `defineGame({ nameplates: { roles, maxDistance } })` (mirrors `worldHealthBars`) rather than mounting it by hand; skin every part via `className`/`data-*` slots (`nameplateClassName`, `nameClassName`, `barClassName`, `fillClassName`) or swap the whole markup with `renderNameplate`. **Selection rings**: `WorldObjectHighlights` (`@jgengine/shell/world/WorldHud`) draws a ground ring over every `ctx.scene.object.selection`-ed placed object — the object-layer counterpart to `WorldEntityBars`/`WorldNameplates`; mount it once in the game's scene (`color`/`radius`/`y` props, `radius` otherwise derived from the object's catalog `halfExtents`) instead of hand-rolling a selection highlight through `WorldOverlay` against external state. **Playing cards** are already shipped, not hand-rolled: `CardFace` / `StackedPile` / `CardStack` (with `Suit`, `Rank`, `PlayingCard`) from `@jgengine/react/cards` render a French-deck face (rank + suit pips, patterned back when `faceDown`) and an overlapping fan. `StackedPile` composes the existing drag system (`useDragLayer`/`DraggableCard`/`DropZone`) — pass a `layer` for drag and `dropId` for a drop target; `CardStack` binds a fan to a headless `CardPileState` zone (`@jgengine/core/cards/cardPile`). Never draw cards on a canvas or reinvent pointer handling. **Swing timer**: `swingTimerState(player, target, prevPeriod, prevTimer)` (`@jgengine/core/ui/swingTimer`) is the pure, parameter-in/next-state-out core for a melee swing bar (recovers period on the reset edge; hidden unless auto-attacking a live non-object target) — thread the returned `nextPeriod`/`nextTimer` back via refs, render a thin `Meter`.
230
+ **Portable minimap adoption.** Existing React games pass `MarkerView[]`, a
231
+ `MarkerSource`, or their existing `MarkerSet` to `Minimap`/`Compass`/`WorldMap`.
232
+ Use `createMarkerSource` (`@jgengine/core/world/markers`) to cache a projection
233
+ from an external ECS/store snapshot; it needs no `GameProvider`, Three.js, or
234
+ React Three Fiber. Follow [the portable minimap recipe](recipes/portable-minimap.md)
235
+ instead of mirroring external entities into `createMarkerSet`.
236
+
237
+ **Minimap** is already shipped, not hand-rolled: `Minimap` / `WorldMap` / `Compass` from `@jgengine/react/map` render a framed SVG minimap over a `MarkerSet` (`createMarkerSet`, `@jgengine/core/world/markers`) and optional `FogField`, projecting via `@jgengine/core/world/minimap` (`projectToMinimap`, `headingToBearing`, edge-clamp). A game feeds flat props (`markers`, `center: [x,z]`, `worldRadius`, `size`, `facingYaw`, `rotate`) and repopulates the marker set each HUD tick from live entities — no custom canvas painter. `MinimapPanel` (same module) composes zone-label + clock header slots and a `Compass` strip around a `Minimap` — pass `zoneLabel`/`clock` nodes (e.g. from `useGameClock().calendar`) and `showCompass`/`compassProps`; omit either header slot and it collapses, `showCompass={false}` drops the strip. **Baked terrain background**: to draw the authored terrain under the markers, pass `background={document.minimap?.background}` and `mapBounds={document.minimap?.bounds}` to `Minimap`/`WorldMap` (or `MinimapPanel`, which forwards both through) — the editor's `bake_minimap` action stores a top-down terrain PNG + world bounds on `EditorDocument.minimap` (see `jgengine-editor`), and runtime reads that stored PNG with no re-rasterization. Both are undefined until baked, so an unbaked game renders the plain map unchanged. Rolling a **custom** minimap around your own course/route content (not the full `Minimap` panel)? `MinimapChrome` is the headless chrome layer other games were re-drawing by hand: nest it inside your own `<svg>` for the ring `frame`, and pass `markers: MinimapChromeMarker[]` (`{ id, position, heading?, color?, radius?, glyph? }`) for edge-clamped dots that draw as a rotated arrow whenever a marker carries a `heading` (the compass-arrow / player-blip layer) — never hand-roll `clampToMinimapEdge` + a `<polygon>` again. **Nameplates**: `WorldNameplates` (`@jgengine/shell/world/WorldHud`) billboards a name + optional 78×6px HP bar over every nearby non-local entity — turn it on declaratively via `defineGame({ nameplates: { roles, maxDistance } })` (mirrors `worldHealthBars`) rather than mounting it by hand; skin every part via `className`/`data-*` slots (`nameplateClassName`, `nameClassName`, `barClassName`, `fillClassName`) or swap the whole markup with `renderNameplate`. **Selection rings**: `WorldObjectHighlights` (`@jgengine/shell/world/WorldHud`) draws a ground ring over every `ctx.scene.object.selection`-ed placed object — the object-layer counterpart to `WorldEntityBars`/`WorldNameplates`; mount it once in the game's scene (`color`/`radius`/`y` props, `radius` otherwise derived from the object's catalog `halfExtents`) instead of hand-rolling a selection highlight through `WorldOverlay` against external state. **Playing cards** are already shipped, not hand-rolled: `CardFace` / `StackedPile` / `CardStack` (with `Suit`, `Rank`, `PlayingCard`) from `@jgengine/react/cards` render a French-deck face (rank + suit pips, patterned back when `faceDown`) and an overlapping fan. `StackedPile` composes the existing drag system (`useDragLayer`/`DraggableCard`/`DropZone`) — pass a `layer` for drag and `dropId` for a drop target; `CardStack` binds a fan to a headless `CardPileState` zone (`@jgengine/core/cards/cardPile`). Never draw cards on a canvas or reinvent pointer handling. **Swing timer**: `swingTimerState(player, target, prevPeriod, prevTimer)` (`@jgengine/core/ui/swingTimer`) is the pure, parameter-in/next-state-out core for a melee swing bar (recovers period on the reset edge; hidden unless auto-attacking a live non-object target) — thread the returned `nextPeriod`/`nextTimer` back via refs, render a thin `Meter`.
238
+
239
+ **HUD widgets split into three reusable layers** — data, renderer, chrome — so a second presentation drops in without a forked widget. **Chrome**: `HudFrame` (`@jgengine/react/hudFrame`, `hud-frame` capability) is the single framed-panel primitive — `<HudFrame variation="glass"|"plate"|"retro" shape="rounded"|"circle"|"square" title aside padding width style>` renders a `data-hud-frame` div with an optional small-caps `title`/`aside` header; `glass` is byte-identical to the built-in widgets' dark-glass panel, `plate` a heavier opaque plate, `retro` a hard black-outlined frame. Need only the CSS on your own element? spread `hudFrameStyle(variation, shape)`. Reach for it instead of hand-rolling a bordered panel `<div>` per widget. **Renderer**: `MinimapTrack` (`@jgengine/react/map`, `minimap-track-hud` capability) is a structurally-different second minimap — a horizontal linear progress rail (vs the radial `Minimap`) with colored zone `spans` and gate/exit/player `pips` placed by 0..1 fraction, driven by core `trackFraction([x,z], { from, to })` (`@jgengine/core/world/minimapTrack`, `minimap-track` capability — the 1D analogue of `projectToMinimap`). It reads no store; the caller supplies fractions. Use it for corridor/route progress; use the radial `Minimap` for open worlds — both share `HudFrame` chrome. **Data**: `useLiveMarkers(rebuild, { intervalMs?, deps? })` (`@jgengine/react/hooks`, `live-markers` capability) returns a self-ticking `MarkerSet` that each HUD tick clears and re-runs your `rebuild(markers, ctx)` against the live scene — replaces the hand-rolled `useState` ticker + `useEffect` + `createMarkerSet` + `markers.clear()` + rescan boilerplate; feed the returned set straight into `Minimap`/`MinimapPanel`.
224
240
 
225
241
  **Heat/streak gauges** are already shipped, not hand-rolled `clamp(value/max)`: build the gauge with `createEventMeter` (`@jgengine/core/stats/eventMeter`, `event-meter` capability — ult/adrenaline `"hold"` mode or streak/combo `"reset"` mode over tagged event gains) and bind it to a component with `useEventMeter(meter)` (`@jgengine/react`, `event-meter-hud` capability) — it self-ticks a heartbeat and returns `{ value, fraction, tier, ready }` for a `Meter`/`BossBar`-style fill, no manual `setInterval` polling of `meter.value()`.
226
242
 
227
243
  **Action cards / command bars / ability bars** are a headless data → renderer → chrome split, not a hand-rolled cooldown grid. The model is caller-owned data: build `ActionDef[]` (`@jgengine/core/ui/actionModel` — `id`, optional `label`/`icon`/`hotkey`/`group`/`active`/`disabled`, plus `cooldown` via `actionCooldown`/`actionCooldownFromFraction` and `costs` via `actionCost`); `resolveAction`/`resolveActionCollection` compute `enabled` + ordered blocking `reasons` (cooldown, then unmet cost, then caller reasons). React side (`@jgengine/react`): `useActionBar(defs, { columns, wrap, hotkeys, onActivate })` is the DATA/HOOK layer (`action-bar-model` — focus, hover, `moveGridFocus` keyboard/controller grid nav, `actionByHotkey` routing); `ActionBarChrome` is the composable RENDERER (`action-bar-chrome` — `role="toolbar"`, roving tabindex, `layout: "grid" | "list"`, swap per-item chrome with `renderItem`); `ActionButton` is the default item chrome (`action-button` — radial cooldown wipe, cost line, `Keycap` hotkey, full rest/hover/focus/pressed/active/disabled states, `aria-disabled`/`aria-pressed`/`aria-keyshortcuts`); `ActionBar` is the batteries-included skinned default. A radial UI ignores the chrome and lays out `ActionButton`s from the model. Bind a combat `AbilityKit` with `actionFromAbilitySlot(slot, { label, icon, hotkey, resourceId })` over `useAbilitySlots`. Tooltips use the shared seam (no more DOM `title=` hand-rolling): `actionTooltip(resolved)` → `TooltipContent`, rendered by `ActionTooltip` inside a `Popover` (`popover` — pure `placePopover` viewport flip/clamp math from `@jgengine/core/ui/tooltipModel`, SSR-safe). **Selection panels / party frames / squad summaries** are the same split for entities: feed caller-owned `EntitySummaryDef[]` (`@jgengine/core/ui/selectionModel` — `id`, `name`, `kind`, `icon`, `vitals`, `tags`) to `summarizeSelection` (primary focus, `kind` buckets, `grouped` past a threshold, `selectionWindow` virtualization) or the `useSelectionView` hook (`selection-model` — focus stepping + keyboard nav). Renderers (`@jgengine/react`): `EntityPortrait` (`entity-portrait` selectable tile + health strip), `EntitySummary` (`entity-summary` primary detail with `VitalBar` rows), `SelectionGroupChip` (`selection-group-chip` icon+count), `SelectionCollectionChrome` (`selection-collection` portrait strip that flips to group chips when large, roving tabindex), and `SelectionPanel` (`selection-panel` batteries-included primary-summary-beside-strip default). Keep layout, dimensions, hotkeys, catalog-id mapping, renderer, and chrome caller-owned — the defaults are examples, not fixed RTS rules; never fork the model to reskin.
228
244
 
245
+ **Inventory grid** is already shipped, not hand-rolled: `<InventoryGrid inventoryId columns>` (`@jgengine/react`, backed by the `useInventoryGrid` hook) is a drop-in NxN grid bound to a live inventory — it renders the bound inventory's slots and wires drag-to-move / swap / stack / split through the built-in `inventory.move` / `inventory.split` commands, so a bag is one element plus a reskin, never a re-derived slot matrix over raw divs. It is distinct from the atomic `EquipmentSlots` / `WeaponSlots`, which are single-slot targets for paperdoll and equip layouts — reach for `InventoryGrid` for the bag/backpack surface and the atomic slots for the worn/equipped layout. Reskin via `className`/`data-*` slots and HudTheme tokens; the mechanics stay caller-agnostic.
246
+
247
+ **Toggleable game windows** are already shipped, not hand-rolled open/close state: `usePanels(defs)` + `<PanelHost>` + `<Window>` (`@jgengine/react`) over the headless `@jgengine/core/ui/panelModel` are THE way to build openable windows — bag (`B`), character (`C`), spellbook, quest log — with keybind toggles, drag-to-move, close buttons, ESC-to-close, and z-stack ordering all handled. Declare each window in `defs` (id, keybind, title), mount one `<PanelHost>`, and render each panel's body inside a `<Window>`; the game owns the body content and skin, the model owns focus/stacking/keybind wiring. Never re-derive window toggling from local `useState` booleans and hand-rolled keydown listeners.
248
+
249
+ **Character sheet / paperdoll** is already shipped, not hand-rolled: `<CharacterSheet>` / `<Paperdoll>` / `<StatList>` (`@jgengine/react`) compose the paperdoll equip layout (worn `EquipmentSlots` arranged around a portrait) and the derived-stat readout into one window body. `Paperdoll` is the equip-slot arrangement, `StatList` the derived-stat rows, `CharacterSheet` the batteries-included default that pairs them — the game composes and reskins them (slot layout, stat labels/terminology, skin) rather than re-deriving a paperdoll from raw divs and manual stat math.
250
+
229
251
  **Toast/announcer stacks**: two supported sources, pick the one that matches where the message comes from. An engine event already bound onto `ctx.game.feed` (kill feed, loot log, quest update) renders with `@jgengine/react`'s `ToastStack({ action })` directly. A game-raised one-off message with its own lifetime (boss-intro banner, objective callout) belongs in a `createToastQueue` (`@jgengine/core/game/toasts`, `toast-feed` capability — see `jgengine-gameplay`) rendered from `queue.list()`; don't reimplement the append-cap-plus-TTL-prune loop as a local `Toast`/`pushToast` pair.
230
252
 
231
253
  Do not create a primitive whose only value is wrapping a `div` with border radius.
@@ -283,8 +305,28 @@ Three composable-chrome seams for FPS/TPS polish — a placement hook plus a goo
283
305
  - **Render cues for any custom rig.** `useEntityRenderCues(instanceId, tuning?)` (`@jgengine/shell/render/useEntityRenderCues`) returns the same `cuesRef` the viewmodel gets, for a custom `renderEntity` component: velocity-driven `bobPhase`, `firing`/`recoil` from `ctx.game.playEntityAnimation(instanceId, "fire")`, `reloading` from `"reload"`/`"reloadEnd"`, `hit` from `combat.hitReaction`, `dead` from `entity.died` — no diffing the parent group's position, no game-side module map for attack timing. `RenderCueTuning` (`@jgengine/core/combat/renderCues`) overrides the bob rate / recoil decay / pulse durations; see `jgengine-combat`'s `combat/renderCues` for the underlying pure math.
284
306
  `@jgengine/shell/terrain`'s `ProceduralGround`/`CarvedTerrain`/`GrassField` are the game-facing terrain surface; the height-field math underneath (`arenaField`, `flatField`, `fractalNoise`, `resolveGroundStep`, `valueNoise`, `withNormal`, re-exported from `@jgengine/core/world/terrain`) and `GamePlayerShell`'s physics helpers (`applyMotionImpulses`, `nearbyObstacles`, `resolvePhysicsTuning`, `hasEnvironmentTerrain`, sourced from `core/runtime/motionIntents` / `core/movement`) are internal building blocks the shell composes for you — reach for `defineGame({ movement, world })` and the ground primitives above instead of calling these directly.
285
307
 
308
+ ## Main menu
309
+
310
+ **The main menu is the game's front-end, not a marketing splash.** The hosting website/runner loads straight into the game's own menu (see rejection criteria) — there is no engine-supplied main menu, and the page must not render a title card, tagline, or "enter game" button of its own. What the player first sees is a screen the game designs and skins in its own art direction: the same shape/material/type language as its HUD, not a landing page, hero banner, feature list, or store page. Homage/credit lines are fine as small print; the screen's job is to start play, not to sell it.
311
+
312
+ Compose it from the game's own markup over the headless `StartScreen` (`@jgengine/react`, `start-screen` capability) — a full-bleed `data-jg-menu` overlay that centers your content and offers an opt-in settings corner; it imposes no look. Menu rows are the copy-in `MenuList` / `MenuButton` / `TitleScreen` registry scaffolds (keyboard/controller focus + `KeybindBadge` that self-hides on touch already wired) restyled into the game's chrome — never shipped as raw scaffold.
313
+
314
+ **The front-end entries, when the game has them.** A menu is more than one Start button. Offer the real front-end verbs a player expects, gated to what the game actually supports:
315
+
316
+ - **New Game** — begin a fresh run. When a save exists, keep it distinct from Continue rather than silently overwriting.
317
+ - **Continue / Load** — resume the most recent save, and (when the game keeps more than one) a **Load** entry that lists save slots. Save slots are `createSaveStore` instances keyed by slot id; read them reactively with `useSaveStore` (`@jgengine/react`, offline `localSaveBackend` or cloud `remoteSaveBackend` — same hook, different backend). Disable Continue/Load with a real disabled state when there is nothing to resume; do not hide the verb entirely.
318
+ - **Create Character / Choose Class** — when the game has characters, classes, loadouts, or profiles, the create/select flow lives here as an authored screen, not a stray dropdown. Keep the character/roster model serializable and game-owned (persist it through the game's save store); the menu is its presentation. Do not reach for a genre "class picker" preset — compose the choices this game actually offers.
319
+ - **Load Character / Roster** — choose an existing profile or party when the game persists more than one.
320
+ - **Settings** — reachable from the main menu, always (see Settings menu below). This is one of the two canonical places settings live; pause is the other.
321
+ - **Credits** — a real, reachable screen. Player-facing games owe HUD and website credit (see governance) and record borrowed work in `CREDITS.md`; the menu's Credits entry surfaces that in-game. Read it from the game's own credits data — do not bury attribution only in a build artifact.
322
+ - **Quit / Back to site** — where it makes sense for the host.
323
+
324
+ Only show entries the game supports, and disable (don't delete) verbs that are temporarily unavailable so the menu shape stays legible. Character create/select and Credits are their own authored sub-screens reached from the menu, composed to the same art direction — not modal form dumps.
325
+
286
326
  ## Settings menu
287
327
 
328
+ **Settings is game-owned and reachable from a place that makes sense.** Every game ships settings — but as *its* settings, skinned in its own art direction and placed inline with its own UI, never the engine's stock face dropped into a corner. The two canonical homes are the **main menu** and the **pause** screen; put the entry where the player would look, not floating over live play. The engine gives you the menu's contents and behavior for free — you own its skin, layout choice, and placement. If the default four layouts do not match the game, drive `useSettings()` and render the rows entirely inside your own chrome.
329
+
288
330
  **Settings menu (themed, four layouts, no forced chrome).** The engine builds the whole menu for free — Sound (master + per-bus volume), Graphics (quality/dpr + shadows), Gameplay (FOV slider, default 40–120), Controls (per-action key rebinding, inline click-to-rebind, persisted) — from the game's `audio.buses` and `input` map. What it does **not** do is bolt a fixed gear onto every game: **there is no auto trigger.** You place the entry yourself so it lives *inline with your game's own UI*, never a stray corner overlay. Drop `<SettingsTrigger className=…>` (from `@jgengine/react`) anywhere in your HUD or menu — headless button, `className` for skin/placement, optional `children` to replace the default gear glyph, renders nothing when there's nothing to show. Or call `useSettings().open()` from your own control. Tune the menu via `defineGame({ settings })` (`GameSettingsConfig` from `@jgengine/core/settings/settingsModel`):
289
331
 
290
332
  - `variant: "panel" | "sheet" | "sidebar" | "fullscreen"` — the layout + skin (default `panel`; `sheet` is the mobile bottom-sheet). All four are fixed-size (no shrink-to-content jitter) and read the game's `--jg-*` theme tokens, falling back to a neutral dark skin.
@@ -459,7 +501,7 @@ Require revision when any of these are true:
459
501
  - controls overlap each other
460
502
  - keyboard instructions appear on touch devices
461
503
  - large instruction panels remain visible during gameplay
462
- - more than three ordinary card-like panels are persistently visible
504
+ - more than three undifferentiated, equally-weighted card-like panels are persistently visible (this is about generic card soup — using the shipped windows/bars is fine; the fault is unstyled boxes with no hierarchy)
463
505
  - the main action looks like a standard website button
464
506
  - pause resembles a generic website modal
465
507
  - victory/failure is only text plus restart
@@ -474,14 +516,19 @@ Require revision when any of these are true:
474
516
  - ordinary document flow determines the HUD layout
475
517
  - generic default styling is used because no art direction was written
476
518
  - the hosting website page renders its own title, tagline, or "enter game" button instead of loading straight into the game's own menu
519
+ - the game's own main menu reads as a marketing/landing page (hero banner, feature list, store framing) rather than a game front-end
520
+ - the game supports saves, characters, or classes but the main menu offers no Continue/Load or character create/select entry
521
+ - Continue/Load is hidden rather than shown disabled when there is nothing to resume
522
+ - settings ship as the engine's unskinned stock menu instead of the game's own skin and placement, or sit floating over live play with no sensible home
523
+ - credits are absent from the game, or attribution lives only in a build artifact instead of a reachable in-game screen
477
524
 
478
525
  ## 15. Compact implementation API appendix
479
526
 
480
- Use the main `jgengine` skill for the authoritative engine API routing. The React package exposes `GameProvider`, hooks, and headless primitives from `@jgengine/react` and its documented subpaths. Common hooks include player/game state, entities, stats, inventory, quests, prompts, clocks, markers, fog, and engine stores. The shell provides `GamePlayerShell`, input integration, devtools, and `GameUiPreview`; `@jgengine/shell/GamePlayerShell` also re-exports the movement primitives it wires up internally (`applyMotionImpulses`, `nearbyObstacles`, `resolvePhysicsTuning`, `hasEnvironmentTerrain`) for a host that needs to replicate its physics loop, and `@jgengine/shell/terrain` re-exports the `jgengine-world` field builders (`arenaField`, `flatField`, `fractalNoise`, `resolveGroundStep`, `valueNoise`, `withNormal`) alongside its own `CarvedTerrain`/`GrassField` components so a shell consumer never needs a second import from core.
527
+ Use the main `jgengine` skill for the authoritative engine API routing. The React package exposes `GameProvider`, hooks, and headless primitives from `@jgengine/react` and its documented subpaths. Common hooks include player/game state, entities, stats, inventory, quests, prompts, clocks, markers, fog, and engine stores. The shell provides `GameHost` (the game mount), input integration, devtools, and `GameUiPreview`; `@jgengine/shell/GamePlayerShell` (the internal player `GameHost` composes) also re-exports the movement primitives it wires up internally (`applyMotionImpulses`, `nearbyObstacles`, `resolvePhysicsTuning`, `hasEnvironmentTerrain`) for a host that needs to replicate its physics loop, and `@jgengine/shell/terrain` re-exports the `jgengine-world` field builders (`arenaField`, `flatField`, `fractalNoise`, `resolveGroundStep`, `valueNoise`, `withNormal`) alongside its own `CarvedTerrain`/`GrassField` components so a shell consumer never needs a second import from core.
481
528
 
482
529
  Use these APIs to bind state; do not let API wiring dictate visual composition. Keybind labels should derive from the game’s binding table, and UI actions should dispatch through game commands rather than existing only as click handlers.
483
530
 
484
- Headless components are not finished design. They are behavior and accessibility seams that the game must art-direct.
531
+ The shipped building blocks — inventory grids, toggleable windows, character sheets, bars — are legitimate to compose and are the right starting point; reach for them instead of re-deriving inventory, windowing, or a paperdoll. But neither they nor the headless seams are finished design on their own: the game must still arrange, place, name, and art-direct them into its own look. Compose the building blocks, then skin and lay them out — don't ship them unarranged.
485
532
 
486
533
  ## Definition of done
487
534
 
@@ -494,6 +541,9 @@ UI work is complete only when:
494
541
  - mobile controls do not cover critical content
495
542
  - touch controls match the genre and game identity
496
543
  - title, pause, and results screens feel authored
544
+ - the main menu is a game-owned front-end (New Game / Continue / Load / character create+select / Settings / Credits as the game supports them), not a marketing splash
545
+ - settings are game-skinned and reachable from the main menu and pause, not a stock corner overlay
546
+ - credits are reachable in-game and record borrowed work
497
547
  - interaction states and transitions are present
498
548
  - screenshots have been reviewed and revised
499
549
  - the implementation remains accessible and performant
@@ -27,7 +27,7 @@ Score all ten from one `--mode play` shot plus deterministic world/document evid
27
27
  6. **Materials/textures** — terrain and props carry texture and color variation (`environment({ sculpt, materialRegions })`, catalog GLBs via `jgengine-assets`), not flat untextured solids.
28
28
  7. **Lighting/render** — a tuned daylight/sun+hemisphere rig and post stack (per `look: "cinematic"`, #773), not the bare untouched three.js default light.
29
29
  8. **VFX/motion** — particle/motion feedback tied to actual gameplay events (hits, pickups, weather), not absent, and not one looping default effect standing in everywhere.
30
- 9. **UI/HUD** — a game-specific `HudCanvas`/`HudPanel` composition; fails automatically if it could pass for a generic admin dashboard.
30
+ 9. **UI/HUD** — a game-specific `HudCanvas`/`HudPanel` composition with unique art direction; fails automatically if it could pass for a generic admin dashboard **or** for another game shipping stock engine widgets/theme presets unskinned.
31
31
  10. **Performance evidence** — the look holds at the scale the game actually runs (`InstancedScatter` density, many entities), not only a hand-posed hero shot with three objects in frame.
32
32
 
33
33
  ## Measured-evidence row
@@ -11,8 +11,16 @@ This skill owns runtime spatial behavior: authored-scene consumption, environmen
11
11
 
12
12
  Search [capabilities.md](capabilities.md) by intent before designing a primitive. Use [api.md](api.md) for exact exports and [reference.md](reference.md) for environment, camera, navigation, physics, audio, and authored-scene recipes.
13
13
 
14
+ For a *connected* walkthrough that wires several primitives into a running loop, read the goal-oriented recipes under [recipes/](recipes/) — organized by composition seam (what connects to what), never by genre. Start there before reading a game's source to learn how pieces fit; a hybrid game is just a different composition of the same primitives.
15
+
16
+ - [recipes/commanding-entities.md](recipes/commanding-entities.md) — select → order → tick → resolve: direct command over a group of entities (the command loop behind squad control, base-defense, herding, autobattle, or a mutating game's real-time phase).
17
+
14
18
  ## Canonical workflows
15
19
 
20
+ ### Declare the place
21
+
22
+ A world is the place you play in: substrate + laws, via `world()` from `@jgengine/core/world/place` — `{ id, ground: { mode: flat|round|voxel|board, size, surface?, generator? }, physics? }`. Thin is complete: flat with `Infinity` axes for 3D, `board` for 2D, or omit `world` for pure UI/rules games. Never put seed, sky, or vegetation in a world definition — seeds derive from world id + save/run (`seedForPlace`); sky look and foliage scatter are editor-authored scene content, with the engine default sky when the document has none. `ground.surface` carries matter/feel laws (metal vs slime vs felt) that the same rule systems read — swap the surface, not the rules. See [reference.md](reference.md) for the full shape; `environment()` is the legacy consumption path for editor/preset-written scene data, never the starter.
23
+
16
24
  ### Authored world
17
25
 
18
26
  1. Load the scene document through the shared authored-scene feature.
@@ -28,8 +36,13 @@ Choose input intent, controller/motor, collision/navigation, and camera as separ
28
36
 
29
37
  Compose perception, selection, planning/behavior, movement, and lifecycle independently. Inject randomness and scheduling. Use spatial indexes, interest tiers, or bounded candidate sets for scale.
30
38
 
39
+ ### Fallback-seam diagnostics
40
+
41
+ Render seams fall back to placeholders when content is unauthored: default green ground (no environment), primitive capsule/box actors (no `entityModels`/`objectModels` mapping), stylized proxy foliage (no scatter `resolveItem`). These placeholders are sometimes intended, so runtime never changes — but a dev-only, opt-in probe (`@jgengine/core/devtools/fallbackSeams`) tells "unauthored/misconfigured" apart from "intended". The shell arms it via `armFallbackSeams(devtoolsEnabled)`; each seam reports at its choice site (`reportFallbackSeam`), and counts surface through the devtools `fallbacks` probe. Disarmed (production) it is a pure, allocation-free no-op.
42
+
31
43
  ## Traps
32
44
 
45
+ - Do not put seed, sky presets, or vegetation/scatter fields in a world definition — `world()` rejects them; dressing belongs to the editor's scene document.
33
46
  - Do not put UI layout, touch chrome, combat resolution, or asset acquisition here.
34
47
  - Do not duplicate editor coordinates in runtime state.
35
48
  - Public APIs accept semantic policies and caller data, not renderer mechanics or internal tuning rolls.