@jgengine/react 0.13.0 → 0.15.0-next.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 (101) hide show
  1. package/CHANGELOG.md +529 -1
  2. package/dist/accessibility.d.ts +39 -0
  3. package/dist/accessibility.js +61 -0
  4. package/dist/achievements.d.ts +50 -0
  5. package/dist/achievements.js +94 -0
  6. package/dist/cameraShake.d.ts +52 -0
  7. package/dist/cameraShake.js +53 -0
  8. package/dist/coachMarks.d.ts +72 -0
  9. package/dist/coachMarks.js +136 -0
  10. package/dist/codex.d.ts +25 -0
  11. package/dist/codex.js +62 -0
  12. package/dist/comboMeter.d.ts +63 -0
  13. package/dist/comboMeter.js +93 -0
  14. package/dist/components.js +4 -3
  15. package/dist/creditsScreen.d.ts +19 -0
  16. package/dist/creditsScreen.js +31 -0
  17. package/dist/cutscene.d.ts +66 -0
  18. package/dist/cutscene.js +132 -0
  19. package/dist/damageDirection.d.ts +51 -0
  20. package/dist/damageDirection.js +103 -0
  21. package/dist/dialogueView.d.ts +69 -0
  22. package/dist/dialogueView.js +111 -0
  23. package/dist/entityFrames.d.ts +114 -0
  24. package/dist/entityFrames.js +55 -0
  25. package/dist/fastTravel.d.ts +36 -0
  26. package/dist/fastTravel.js +103 -0
  27. package/dist/floatingText.d.ts +84 -0
  28. package/dist/floatingText.js +61 -0
  29. package/dist/gameViewport.js +8 -12
  30. package/dist/hooks.d.ts +42 -0
  31. package/dist/hooks.js +85 -0
  32. package/dist/hudLayout.js +138 -9
  33. package/dist/hudTheme.d.ts +104 -7
  34. package/dist/hudTheme.js +300 -2
  35. package/dist/i18n.d.ts +29 -0
  36. package/dist/i18n.js +44 -0
  37. package/dist/index.d.ts +34 -0
  38. package/dist/index.js +34 -0
  39. package/dist/interactionPrompt.d.ts +80 -0
  40. package/dist/interactionPrompt.js +112 -0
  41. package/dist/keybindingMenu.d.ts +59 -0
  42. package/dist/keybindingMenu.js +146 -0
  43. package/dist/killFeed.d.ts +63 -0
  44. package/dist/killFeed.js +88 -0
  45. package/dist/map.d.ts +123 -2
  46. package/dist/map.js +239 -36
  47. package/dist/menuRouter.d.ts +51 -0
  48. package/dist/menuRouter.js +49 -0
  49. package/dist/modals.d.ts +131 -0
  50. package/dist/modals.js +168 -0
  51. package/dist/notifications.d.ts +39 -0
  52. package/dist/notifications.js +111 -0
  53. package/dist/objectiveBanner.d.ts +62 -0
  54. package/dist/objectiveBanner.js +129 -0
  55. package/dist/panels.d.ts +10 -2
  56. package/dist/panels.js +9 -3
  57. package/dist/photoMode.d.ts +21 -0
  58. package/dist/photoMode.js +39 -0
  59. package/dist/previewFixtures.d.ts +30 -0
  60. package/dist/previewFixtures.js +46 -0
  61. package/dist/provider.d.ts +15 -0
  62. package/dist/provider.js +17 -1
  63. package/dist/questTracker.d.ts +24 -0
  64. package/dist/questTracker.js +31 -0
  65. package/dist/quickMenu.d.ts +39 -0
  66. package/dist/quickMenu.js +79 -0
  67. package/dist/radialMenu.d.ts +50 -0
  68. package/dist/radialMenu.js +69 -0
  69. package/dist/saveSlots.d.ts +81 -0
  70. package/dist/saveSlots.js +138 -0
  71. package/dist/scoreboard.d.ts +67 -0
  72. package/dist/scoreboard.js +71 -0
  73. package/dist/settings.d.ts +9 -3
  74. package/dist/settings.js +10 -4
  75. package/dist/shopGrid.d.ts +70 -0
  76. package/dist/shopGrid.js +129 -0
  77. package/dist/statusEffectBar.d.ts +52 -0
  78. package/dist/statusEffectBar.js +57 -0
  79. package/dist/streetGeometryPreview.d.ts +7 -0
  80. package/dist/streetGeometryPreview.js +119 -0
  81. package/dist/talentTree.d.ts +60 -0
  82. package/dist/talentTree.js +217 -0
  83. package/dist/timerReadout.d.ts +90 -0
  84. package/dist/timerReadout.js +111 -0
  85. package/dist/useDebouncedCommit.d.ts +54 -0
  86. package/dist/useDebouncedCommit.js +81 -0
  87. package/dist/waveHud.d.ts +49 -0
  88. package/dist/waveHud.js +53 -0
  89. package/dist/waypointMarkers.d.ts +80 -0
  90. package/dist/waypointMarkers.js +116 -0
  91. package/package.json +3 -3
  92. package/skills/ce-handoff/SKILL.md +149 -0
  93. package/skills/jgengine/SKILL.md +7 -3
  94. package/skills/jgengine/api.md +17 -7
  95. package/skills/jgengine/capabilities.md +12 -0
  96. package/skills/jgengine/recipes/minimal-game.md +7 -4
  97. package/skills/jgengine-ui/SKILL.md +8 -3
  98. package/skills/jgengine-ui/api.md +732 -23
  99. package/skills/jgengine-ui/capabilities.md +371 -2
  100. package/skills/jgengine-ui/recipes/overhead-entity-frames.md +136 -0
  101. package/skills/jgengine-ui/reference.md +7 -4
@@ -6,7 +6,7 @@
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.13.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
9
+ - `VERSION` (const): const VERSION: "0.15.0-next.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
10
10
 
11
11
  ## @jgengine/core/authoring
12
12
 
@@ -14,7 +14,7 @@
14
14
  - `AuthoredProvenance` (interface): interface AuthoredProvenance — A first-class entry in the scene document — the editor fully owns and persists it.
15
15
  - `Drop` (interface): interface Drop — A resolved loot outcome — one item or currency grant with its rolled count.
16
16
  - `GameDefinition` (interface): interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> — Fully-resolved game description produced by {@link defineGameDefinition} — assets, scene, and opted-in subsystems.
17
- - `GameDefinitionConfig` (type): type GameDefinitionConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> = Omit<GameDefinition<TAssetRef, TMultiplayer>, "scene" | "assets"> & { assets?: AssetCatalog<TAssetRef>; } — Input to {@link defineGameDefinition} — a `GameDefinition` with `scene` derived and `assets` optional.
17
+ - `GameDefinitionConfig` (type): type GameDefinitionConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> = Omit<GameDefinition<TAssetRef, TMultiplayer>, "scene" | "assets" | "lifecycle"> & { assets?: AssetCatalog<TAssetRef>; /** * The game's run-phase story — a {@link LifecycleConfig} or the `"always-live… — Input to {@link defineGameDefinition} — a `GameDefinition` with `scene` derived and `assets` optional.
18
18
  - `GameLoop` (interface): interface GameLoop<TContext = unknown> — Lifecycle hooks a game implements to drive init, per-tick simulation, and player join/leave.
19
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
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.
@@ -125,7 +125,7 @@
125
125
 
126
126
  ## @jgengine/core/devtools/fallbackSeams
127
127
 
128
- - `FallbackCause` (type): type FallbackCause = "omittedMapping" | "unpulledPack" | "noScene" — Why a seam fell back: `omittedMapping` (no mapping supplied — often intended), `unpulledPack` (a mapping was supplied but its asset pack is not pulled/indexed), `noScene` (nothing authored — e.g. no environment component).
128
+ - `FallbackCause` (type): type FallbackCause = "omittedMapping" | "unpulledPack" | "noScene" | "renderError" — Why a seam fell back: `omittedMapping` (no mapping supplied — often intended), `unpulledPack` (a mapping was supplied but its asset pack is not pulled/indexed), `noScene` (nothing authored — e.g. no environment component), `renderError` (authored content was found but threw while rendering — always a defect).
129
129
  - `FallbackSeam` (type): type FallbackSeam = "ground" | "entity" | "object" | "scatter" — Render seams that can resolve to a placeholder FALLBACK instead of authored content: `ground` (default green terrain), `entity`/`object` (primitive capsule/box actors), `scatter` (stylized proxy foliage).
130
130
  - `FallbackSeamCounts` (type): type FallbackSeamCounts = Record<FallbackSeam, Record<FallbackCause, number>> — A per-seam, per-cause count table. Serializable, allocation-stable (keys never change).
131
131
  - `FallbackSeamsReport` (type): type FallbackSeamsReport = Partial<Record<FallbackSeam, Partial<Record<FallbackCause, number>>>> — Non-zero-only view of {@link FallbackSeamCounts} returned by {@link fallbackSeamsSnapshot}.
@@ -163,6 +163,14 @@
163
163
  - `SaveEndpointRequest` (type): type SaveEndpointRequest = | { kind: "editor-document"; gameId: string; json: string } | { kind: "tunables"; gameId: string; deltas: readonly { table: string; key: string; value: unknown }[]; } — One write request the dev save endpoint accepts: a scene document or tunable deltas.
164
164
  - `SaveEndpointResponse` (interface): interface SaveEndpointResponse — Result envelope the dev save endpoint returns for every write request.
165
165
 
166
+ ## @jgengine/core/devtools/textureErrors
167
+
168
+ - `TextureLoadError` (interface): interface TextureLoadError — One texture (or other GLB sub-resource) that failed to load, plus how many times a load for that URL errored. Serializable so it flows through the `debug_snapshot` RPC exactly like {@link import("./fallbackSeams").FallbackSeamsReport}.
169
+ - `armTextureErrors` (function): function armTextureErrors(on: boolean): void — Arm or disarm the texture-load-error diagnostic. Off by default; the shell arms it only when devtools is enabled (dev builds), so production is a pure no-op. Toggling clears the tally so a fresh observation starts empty and the opt-out path reports nothing.
170
+ - `reportTextureLoadError` (function): function reportTextureLoadError(url: string): void — Record that a sub-resource load ERRORED for `url` (called from the shared GLB loading manager's `onError`, which fires for GLTF textures/images whose fetch 404s or otherwise fails — the failure the whole-model fallback probe never sees because the model itself still resolves). A pure, allocation-free no-op when disarmed. On the armed path it also emits ONE deduped devtools warn line per newly-seen failure set so a texture-404'd scene is loud, not silent.
171
+ - `resetTextureErrors` (function): function resetTextureErrors(): void — Clear the texture-error tally without changing the armed state. Used to bracket a fresh observation (e.g. between scenes or in tests). No-op when disarmed.
172
+ - `textureErrorsSnapshot` (function): function textureErrorsSnapshot(): TextureLoadError[] — Snapshot the current texture-load-error tally as a compact, serializable list (empty when nothing failed). Flows into the devtools `textureErrors` probe and the `debug_snapshot` RPC so `jgengine-verify` can treat a non-empty set exactly like a model fallback: content that resolved but is visibly broken.
173
+
166
174
  ## @jgengine/core/devtools/transformTunables
167
175
 
168
176
  - `TunableTransformResult` (interface): interface TunableTransformResult — ⚠ undocumented
@@ -220,7 +228,7 @@
220
228
 
221
229
  - `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
222
230
  - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
223
- - `VERSION` (const): const VERSION: "0.13.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
231
+ - `VERSION` (const): const VERSION: "0.15.0-next.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
224
232
 
225
233
  ## @jgengine/core/runtime/adapter
226
234
 
@@ -248,7 +256,7 @@
248
256
  ## @jgengine/core/runtime/cameraDirector
249
257
 
250
258
  - `CameraDirector` (interface): interface CameraDirector — ⚠ undocumented
251
- - `ChaseCameraTuning` (type): type ChaseCameraTuning = Partial< Pick<ChaseCameraConfig, "distance" | "height" | "lookHeight" | "springDamping" | "fov" | "lead" | "bank"> > — Runtime patch over the static `camera.chase` config — distance/height/fov retuning from gameplay events (#286.11).
259
+ - `ChaseCameraTuning` (type): type ChaseCameraTuning = Partial< Pick<ChaseCameraConfig, "distance" | "height" | "lookHeight" | "springDamping" | "fov" | "lead" | "bank" | "shakePerSpeed" | "velocityYaw" | "yawResponse"> > — Runtime patch over the static `camera.chase` config — distance/height/fov retuning from gameplay events (#286.11), or a whole driving-feel overlay (speed→FOV, lead, bank, speed shake, drift-lag) applied only while a vehicle is piloted (#1299).
252
260
 
253
261
  ## @jgengine/core/runtime/commandRunner
254
262
 
@@ -414,9 +422,9 @@
414
422
 
415
423
  ## @jgengine/core/runtime/runtimeSave
416
424
 
417
- - `RuntimeSave` (interface): interface RuntimeSave — Whole-world save/load bound to a live world and a pluggable backend. `save()` captures `target.snapshot()` and writes it; `load()` reads it back and `target.hydrate()`s the whole world. In `autosave` mode it also writes on a debounce after any change. Named slots, versioned migration, and offline↔cloud (backend swap) all come for free from the underlying save store.
425
+ - `RuntimeSave` (interface): interface RuntimeSave — Whole-world save/load bound to a live world and a pluggable backend. `save()` captures `target.snapshot()` and writes it; `load()` reads it back and `target.hydrate()`s the whole world. In `autosave` mode it also writes on a trailing timer while the world keeps changing. Named slots, versioned migration, and offline↔cloud (backend swap) all come for free from the underlying save store.
418
426
  - `RuntimeSaveConfig` (interface): interface RuntimeSaveConfig — How {@link createRuntimeSave} is wired — the live world `target`, the `backend` it persists through, and optional mode/slot/versioning/cadence knobs.
419
- - `RuntimeSaveMode` (type): type RuntimeSaveMode = "autosave" | "manual" — `"autosave"` writes a fresh capture on a debounce after any world change; `"manual"` writes only on an explicit `save()`/`checkpoint()` — the save-point / quest-trigger model.
427
+ - `RuntimeSaveMode` (type): type RuntimeSaveMode = "autosave" | "manual" — `"autosave"` writes a fresh capture on a trailing timer while the world changes (at most once per `autosaveMs`, so a never-idle world still persists); `"manual"` writes only on an explicit `save()`/`checkpoint()` — the save-point / quest-trigger model.
420
428
  - `RuntimeSaveOptions` (type): type RuntimeSaveOptions = Omit<RuntimeSaveConfig, "target"> — {@link RuntimeSaveConfig} without `target` — what a host (`createGameContext`) accepts to build `ctx.game.save` and bind it to the context itself.
421
429
  - `RuntimeSaveTarget` (interface): interface RuntimeSaveTarget — The narrow slice of a live `GameContext` a runtime save reads and writes — a `GameContext` satisfies it directly (`ctx.snapshot`/`ctx.hydrate`/`ctx.subscribe`). Depending on this instead of the full context keeps the save controller a deep, decoupled module: it captures and restores the *whole* opted-in world without knowing any subsystem.
422
430
  - `createRuntimeSave` (function): function createRuntimeSave(config: RuntimeSaveConfig): RuntimeSave — Bridge a live world to a pluggable save backend: whole-state capture/restore with autosave, save points, named slots, and versioned migration — the same call for offline (localStorage) and cloud (Convex), only the `backend` differs. The game keeps control of *when* it saves (the `mode` plus explicit `checkpoint()` calls from quests/areas) and *when* it restores (`load()` on boot), so any save mechanic — continuous autosave, manual save points, checkpoint triggers — is expressible without new engine code.
@@ -506,6 +514,7 @@
506
514
 
507
515
  - `Clock` (function): function Clock({ format, showDay, controls, style, className, }: { format?: "24h" | "12h"; showDay?: boolean; controls?: boolean; style?: CSSProperties; className?: string; }): JSX.Element — A time-of-day clock reading the sim calendar — `Day N · HH:MM`, 24h or 12h. `controls` adds pause + the game's speed multipliers as clickable pills (the "fast-forward" bar), off by default so a game opts into letting the player scrub time.
508
516
  - `Coins` (function): function Coins({ currencyId, icon, style, className, }: { currencyId: string; icon?: ReactNode; style?: CSSProperties; className?: string; }): JSX.Element — A currency counter — an icon (emoji/char, default a coin) plus the live amount for `currencyId`. *
517
+ - `DEFAULT_WALK_CODES` (const): const DEFAULT_WALK_CODES: { readonly moveForward: readonly ["KeyW"]; readonly moveBack: readonly ["KeyS"]; readonly moveLeft: readonly ["KeyA"]; readonly moveRight: readonly ["KeyD"]; readonly jump: readonly ["Space"]; } — Default keyboard codes for the shell walk controller (WASD + Space). Use as the base of `defineGame({ input: { ...DEFAULT_WALK_CODES, interact: ["KeyE"] } })` instead of re-typing the same five bindings in every game.
509
518
  - `EditorCatalogDefinition` (interface): interface EditorCatalogDefinition — Game-exported catalog definition: a `ParamSchema` plus default entries. Schemas stay in code; entry values merge into `document.catalogs` and are what the editor/RPC edits and saves.
510
519
  - `EditorDocument` (interface): interface EditorDocument — The full authored scene: every marker, volume, path, note, and sculpted terrain for a game.
511
520
  - `EditorSummonModule` (interface): interface EditorSummonModule — Structural shape of the module `GameHost`'s `editor` loader resolves — `import("@jgengine/editor")` satisfies it.
@@ -530,6 +539,7 @@
530
539
  - `defineSystem` (function): function defineSystem(definition: SystemDefinition): SystemDefinition — Declare a composable game system. Pure data + hooks — the engine compiles the schedule and installs lifecycle when the game boots.
531
540
  - `environment` (function): function environment(config?: EnvironmentWorldConfig): EnvironmentWorldFeature — Composes an `environment()` feature from terrain, sky, weather, vegetation, water, structures, roads, and pads.
532
541
  - `environmentContentFromDocument` (function): function environmentContentFromDocument(doc: EditorDocument, options?: EnvironmentContentOptions): EnvironmentContent — Derives the coordinate/placement content of an `environment()` world from its scene document: terrain footprint (via {@link terrainBoundsFromDocument}), ground clearings under authored spawns/POIs (via `clearanceZonesFrom`), and the document's sculpt snapshot. This is the seam that lets a game author its world footprint and flatten regions in the editor instead of hardcoding them — the `world.ts` that consumes it carries only engine tuning.
542
+ - `grounded` (function): function grounded(ctx: Pick<GameContext, "world">, x: number, z: number): readonly [number, number, number] — Ground an XZ point onto the live world surface as `[x, groundY, z]`.
533
543
  - `offline` (function): function offline(): MultiplayerAdapterConfig — Explicit single-player adapter. Solo games never need this — omitting `multiplayer` in the shell `defineGame` already defaults to offline; pass it only where an adapter value is structurally required.
534
544
  - `seededRng` (function): function seededRng(seed: string | number): () => number — Deterministic pseudo-random generator seeded from a string or number — same seed, same sequence.
535
545
  - `useHudLayout` (function): function useHudLayout(options?: { storageKey?: string; snap?: number; locked?: boolean; /** * Scene-document `ui` section — source of truth for panel placement/size. * When provided, hydrates the layout store (and wins over legacy localStorage). */ documentUi?: EditorUiDocument; /** * When true (def… — Layout state for `HudCanvas` — panel placements, edit-mode drag/resize, and per-game persistence.
@@ -16,6 +16,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
16
16
 
17
17
  - `GameContextModels` (interface) · `import { GameContextModels } from "@jgengine/core/runtime/gameContext"`
18
18
 
19
+ ## default-walk-codes — stock WASD + jump key codes for the shell walk controller
20
+
21
+ - `DEFAULT_WALK_CODES` (const) · `import { DEFAULT_WALK_CODES } from "@jgengine/shell/gameKit"`
22
+
19
23
  ## define-game — single public game-authoring path — compose systems, world, presentation, and authored scene in one definition
20
24
 
21
25
  - `defineGame` (function) · `import { defineGame } from "@jgengine/shell/gameKit"`
@@ -32,6 +36,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
32
36
 
33
37
  - `SystemDefinition` (interface) · `import { SystemDefinition } from "@jgengine/shell/gameKit"`
34
38
 
39
+ ## grounded — ground an XZ point onto the world surface as a pose triple
40
+
41
+ - `grounded` (function) · `import { grounded } from "@jgengine/shell/gameKit"`
42
+
35
43
  ## headless-runner — play a real game loop with no renderer — tick, feed input, read the world snapshot
36
44
 
37
45
  - `HeadlessRunner` (interface) · `import { HeadlessRunner } from "@jgengine/core/runtime/headlessRunner"`
@@ -95,8 +103,12 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
95
103
  ## render-fallback-diagnostics — Enable/disable the dev-only placeholder-fallback probe.
96
104
 
97
105
  - `armFallbackSeams` (function) · `import { armFallbackSeams } from "@jgengine/core/devtools/fallbackSeams"`
106
+ - `armTextureErrors` (function) · `import { armTextureErrors } from "@jgengine/core/devtools/textureErrors"`
98
107
  - `fallbackSeamsSnapshot` (function) · `import { fallbackSeamsSnapshot } from "@jgengine/core/devtools/fallbackSeams"`
99
108
  - `reportFallbackSeam` (function) · `import { reportFallbackSeam } from "@jgengine/core/devtools/fallbackSeams"`
109
+ - `reportTextureLoadError` (function) · `import { reportTextureLoadError } from "@jgengine/core/devtools/textureErrors"`
110
+ - `resetTextureErrors` (function) · `import { resetTextureErrors } from "@jgengine/core/devtools/textureErrors"`
111
+ - `textureErrorsSnapshot` (function) · `import { textureErrorsSnapshot } from "@jgengine/core/devtools/textureErrors"`
100
112
 
101
113
  ## runtime-save — save/load the whole game world through a pluggable backend, autosave or save points
102
114
 
@@ -72,10 +72,13 @@ export function onNewPlayer(ctx: GameContext): void {
72
72
  ```
73
73
 
74
74
  `GameUI` starts as an empty `HudCanvas` (`useHudLayout` + `HudCanvas` from the kit). That is
75
- intentional: **every game owns its UI**. Write a short UI art direction, then build custom
76
- panels for this pitch — do not ship stock drop-in widgets (`StatBar`, `Hotbar`, `Coins`, glass
77
- frames) as the product face. Reach for engine pieces only as headless data, layout, or
78
- interaction models under game-owned chrome (see `jgengine-ui`).
75
+ intentional: **every game owns its UI composition** — layout, terminology, placement, and art
76
+ direction. Compose the shipped building blocks the moment you need them (`StatBar`, `Hotbar`,
77
+ `Coins`, `InventoryGrid`, `CharacterSheet`, window shells from `jgengine-ui`), then reskin via
78
+ `HudTheme` tokens. Reaching for those is using the engine correctly, not incomplete work. What
79
+ the engine does not ship is a whole finished game *face* or genre theme preset — do not leave an
80
+ unarranged, unskinned generic dashboard as the product. Hand-roll markup only where no building
81
+ block fits (see `jgengine-ui`).
79
82
 
80
83
  ## 3. Author, play, win
81
84
 
@@ -14,11 +14,11 @@ Search [capabilities.md](capabilities.md), use [api.md](api.md) for signatures,
14
14
  ## Canonical workflow
15
15
 
16
16
  1. Write the game's UI art direction first (see [reference.md](reference.md) §1) — fantasy, shape/material language, hierarchy, forbidden patterns. Stock glass widgets are not a stand-in for this step.
17
- 2. Define the real UI states: attract, main menu (the game's front-end), character create/select where relevant, live play, pause, settings, credits, results, empty/error, and relevant overlays.
17
+ 2. Define the real UI states: attract, main menu (the game's front-end), character create/select where relevant, live play, pause, settings, credits, results, empty/error, and relevant overlays. **If the game can kill or down the player, a death/downed state is one of these required screens** — see the death-flow product rule below.
18
18
  3. Read state through selectors/hooks; keep simulation mutation behind commands.
19
- 4. Compose this game's UI, reaching first for the shipped building blocks — `InventoryGrid`, `usePanels`/`PanelHost`/`Window`, `CharacterSheet`/`Paperdoll`, and the action/selection bars — and composing + reskinning them; only hand-roll custom markup where no building block fits. Toggleable windows (bag `B`, character `C`, etc.) come from `usePanels`, not hand-rolled open/close state. Prefer headless data + game-owned chrome. When a second presentation is plausible, split data / renderer / chrome (minimap stack example: `useLiveMarkers` → track/renderer → game frame; see reference §6).
19
+ 4. Compose this game's UI, reaching first for the shipped building blocks — `InventoryGrid`, `usePanels`/`PanelHost`/`Window`, `CharacterSheet`/`Paperdoll`, and the action/selection bars — and composing + reskinning them; only hand-roll custom markup where no building block fits. Toggleable windows (bag `B`, character `C`, etc.) come from `usePanels`, not hand-rolled open/close state — and `PanelHost` already stacks windows above the HUD (`zIndexBase`) and wires hotkeys/ESC, so never hand-roll a `z-*` or keydown listener for them. A live 3D character-screen portrait is `EntityPreview` (`@jgengine/shell/render/EntityPreview`): it owns the nested `<Canvas>`, the `GameContextBridge` across the R3F boundary, and the `StudioStage` lighting — pass the game's own `renderEntity` as children (or a native `model`+`instanceId`) and it mirrors the live entity; don't re-derive the context bridge by hand. Prefer headless data + game-owned chrome. When a second presentation is plausible, split data / renderer / chrome (minimap stack example: `useLiveMarkers` → track/renderer → game frame; see reference §6).
20
20
  5. Make keyboard, pointer, touch, controller, focus, and screen-reader behavior explicit.
21
- 6. Add preview fixtures using the real components for fast deterministic capture.
21
+ 6. Add preview fixtures using the real components for fast deterministic capture. To preview the shipped engine HUD primitives themselves (bars, `HudTheme` presets, painted icons), the exported fixtures are registered in `@jgengine/react`'s `PREVIEW_FIXTURES` — capture one directly with `bun run shoot --fixture <name>` (`--list` shows the set) instead of hand-rolling a `--url` mount; register any new deterministic preview component there to make it capturable.
22
22
  7. Verify desktop and mobile layouts through `jgengine-verify`. A HUD that could pass for another game's default chrome fails visual review.
23
23
 
24
24
  Existing React games keep their entity store and use the focused
@@ -30,8 +30,12 @@ Existing React games keep their entity store and use the focused
30
30
  - **Every game owns its UI end-to-end.** Custom composition, skin, placement, terminology, motion, and one main menu. The website/runner is a bare loader.
31
31
  - **The main menu is a game front-end, not marketing.** The runner loads straight into the game's own menu; it offers the real front-end verbs the game supports — New Game, Continue/Load (save slots), Create/Choose character or class, Settings, Credits — each an authored screen in the game's art direction, not a landing page or raw scaffold. See [reference.md](reference.md) §"Main menu".
32
32
  - **Settings is game-owned and reachable from a sensible place.** Every game ships settings as *its* settings — skinned and placed inline with its UI (main menu and pause), never the engine's stock face floated into a corner. Credits are a reachable in-game screen, not attribution buried in a build artifact.
33
+ - **Route the front-end with `useMenuRouter`, and generate the credits.** `useMenuRouter(root)` is the screen stack behind title → save select → settings → credits: `open`/`back`/`replace`/`reset`, `path` for depth, and Escape-to-back bound by default (`escapeToBack: false` when the game owns Escape). `menuStackReducer` is the same transition as a pure function for a game driving it from its own store. For the credits screen itself, `creditsForSourceIds(ids)` (`@jgengine/assets/credits`) turns the pack ids a game already declares to `buildCatalog` into licensed, attributed sections, `mergeCredits` folds them into the game's hand-authored lines, and `CreditsScreen` renders the result HudTheme-skinned — so attribution is derived from what shipped instead of hand-maintained and drifting. Wreckway is the first adopter.
33
34
  - Engine packages ship both headless seams *and* good drop-in building blocks. The seams are layout (`HudCanvas`/`HudPanel`), data hooks, interaction models, and tokens; the building blocks are the parts almost every game needs — inventory grids (`InventoryGrid`), toggleable windows (`usePanels`/`PanelHost`/`Window`), character sheets (`CharacterSheet`/`Paperdoll`), stat/vitals bars, and action/selection bars. Composing and reskinning these is using the engine correctly, not out of policy. What stays out of scope is a whole finished game *face* or a genre theme preset shipped as the product (see [AGENTS.md](../../../AGENTS.md)).
35
+ - **Skin the shared chrome with one `HudTheme` token object.** `hudThemeVars(theme)` emits the whole `--jg-*` vocabulary — bar fills and troughs, `HudFrame variation="themed"`, painted slots, the minimap ring, plus surfaces, edges, text, status colors, fonts, and rarity — so one theme spread on any HUD ancestor reskins the `@jgengine/react` primitives and the `registry/jgengine` blocks together. Author your own with `deriveHudTheme({ accent, surface })` (a seed pair fills every group coherently) or `hudTheme({ ... })` for exact values; `HUD_THEME_PRESETS` / `resolveHudTheme("<preset>")` are demo/scaffold starting points, never a game's identity. A component that needs a token no theme emits fails `check-hud-tokens` — add it to `HudTheme` rather than inventing a second vocabulary. Caller `style`/`renderItem` overrides still win over the theme default.
36
+ - **Paint slot icons with `IconTreatment`, never render raw `itemId` strings.** It draws a `GameIcon` glyph (or any node) over a school/element-keyed radial gradient with a gloss + vignette and optional count/keycap badges, and reads the `--jg-slot-*` / `--jg-accent` `HudTheme` tokens so a theme change reskins every treated icon. `schoolForItem`/`schoolForAction` infer the gradient from an id; `treatedItemIcon(itemId)` / `treatedActionIcon(action)` are ready adapters. Use it in hotbars and ability bars so slots read as painted icons, not debug text.
34
37
  - Never reach for a genre HUD kit, theme preset, or "default RPG/FPS chrome" as the game's *identity*. Composing the shared building blocks (bag, character, spellbook, action-bar windows) is fine and expected — it's a prefab genre *look/identity* dropped in unchanged that's discouraged. Build the look this pitch needs on top of them.
38
+ - **Player death gets a visible screen and respawn feedback — never a silent teleport.** When the game can kill or down the player, build the moment `game-design` requires: an authored death/downed overlay that reads the lethal event, communicates the consequence, and surfaces the way back (respawn/revive/restart affordance) with legible feedback. Drive it from the existing state primitive — `createDownedState` (`@jgengine/core/combat`) already models a downed/bleed-out-then-revive lifecycle — so the screen reflects real state instead of a hand-rolled flag; mount the overlay inside `HudCanvas` like any other window. A lethal hit that just resets position with no on-screen acknowledgement is a presentation gap, not a finished flow.
35
39
  - Layout and skin remain caller-controlled; shared primitives own reusable behavior, not product look.
36
40
  - SSR-visible output is hydration-stable; round computed SVG values at the boundary.
37
41
 
@@ -41,3 +45,4 @@ Existing React games keep their entity store and use the focused
41
45
  - Do not duplicate inventory, combat, quest, or selection state inside components.
42
46
  - A static facsimile preview does not test the real UI; compose production components with fixtures.
43
47
  - Visual quality is judged from rendered evidence, not component counts or prose.
48
+ - Shown HUD UI must fit the player viewport. `HudCanvas` runs a live boundary check on every registered `HudPanel`, any element tagged `data-hud-window`, and its own direct children: if a *visible* surface crosses a viewport edge it draws a red dev overlay on the offender, `console.warn`s, and sets `data-hud-overflow` (which fails the `bun run shoot` gate). A closed/parked/hidden surface (off-screen drawer, unopened menu, `visibility:hidden`, `aria-hidden`) never trips it — only surfaces partly on screen and partly past an edge. Deliberate bleed opts out with `data-hud-allow-overflow`. Mount custom windows inside `HudCanvas` (tag deeper ones `data-hud-window`) so the check can see them.