@jgengine/react 0.22.0 → 0.23.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 (70) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/dist/accessibility.d.ts +5 -0
  3. package/dist/accessibility.js +9 -0
  4. package/dist/achievements.js +4 -3
  5. package/dist/actionHud.d.ts +6 -1
  6. package/dist/actionHud.js +37 -17
  7. package/dist/bars.js +5 -2
  8. package/dist/barsPreview.js +2 -1
  9. package/dist/characterSheet.js +5 -4
  10. package/dist/coachMarks.js +3 -2
  11. package/dist/codex.js +4 -3
  12. package/dist/components.js +9 -2
  13. package/dist/creditsScreen.js +2 -1
  14. package/dist/engineStyles.d.ts +18 -0
  15. package/dist/engineStyles.js +119 -0
  16. package/dist/fastTravel.js +4 -3
  17. package/dist/hud.js +7 -6
  18. package/dist/hudButton.d.ts +29 -0
  19. package/dist/hudButton.js +31 -0
  20. package/dist/hudChromePreview.d.ts +8 -0
  21. package/dist/hudChromePreview.js +27 -0
  22. package/dist/hudFrame.js +15 -11
  23. package/dist/hudInteract.d.ts +26 -0
  24. package/dist/hudInteract.js +28 -0
  25. package/dist/hudLayout.js +4 -3
  26. package/dist/hudPanel.d.ts +31 -0
  27. package/dist/hudPanel.js +32 -0
  28. package/dist/hudTheme.d.ts +88 -1
  29. package/dist/hudTheme.js +123 -8
  30. package/dist/hudThemePreview.js +2 -1
  31. package/dist/hudThemeRoot.d.ts +15 -0
  32. package/dist/hudThemeRoot.js +20 -0
  33. package/dist/hudTokens.d.ts +39 -0
  34. package/dist/hudTokens.js +56 -0
  35. package/dist/iconTreatment.js +3 -2
  36. package/dist/iconsPreview.js +2 -1
  37. package/dist/keybindingMenu.js +3 -2
  38. package/dist/map.js +9 -8
  39. package/dist/modals.d.ts +6 -3
  40. package/dist/modals.js +42 -52
  41. package/dist/notices.d.ts +27 -0
  42. package/dist/notices.js +145 -0
  43. package/dist/notifications.js +4 -3
  44. package/dist/panels.d.ts +9 -2
  45. package/dist/panels.js +78 -35
  46. package/dist/photoMode.js +2 -1
  47. package/dist/presence.d.ts +9 -0
  48. package/dist/presence.js +25 -0
  49. package/dist/previewFixtures.js +6 -0
  50. package/dist/questTracker.js +3 -2
  51. package/dist/quickMenu.js +6 -5
  52. package/dist/saveSlots.js +4 -3
  53. package/dist/selectionHud.js +4 -3
  54. package/dist/shopGrid.js +2 -1
  55. package/dist/statusEffectBar.js +2 -1
  56. package/dist/streetGeometryPreview.js +3 -2
  57. package/dist/styles.css +72 -0
  58. package/dist/talentTree.js +3 -2
  59. package/dist/tweenNumber.d.ts +37 -0
  60. package/dist/tweenNumber.js +66 -0
  61. package/dist/uiCues.d.ts +20 -0
  62. package/dist/uiCues.js +37 -0
  63. package/dist/waveHud.js +2 -1
  64. package/dist/waypointMarkers.js +2 -1
  65. package/package.json +10 -6
  66. package/skills/jgengine/api.md +18 -18
  67. package/skills/jgengine-ui/SKILL.md +4 -0
  68. package/skills/jgengine-ui/api.md +233 -37
  69. package/skills/jgengine-ui/capabilities.md +52 -0
  70. package/skills/jgengine-ui/reference-primitives.md +4 -4
@@ -37,6 +37,12 @@
37
37
 
38
38
  - `EnvironmentSource` (type): type EnvironmentSource = | { kind: "gradient"; sky: string; ground: string; sun?: string; intensity?: number } /** Equirectangular HDR or EXR environment map loaded from `url`. */ | { kind: "hdri"; url: string; rotation?: number; intensity?: number } /** Six faces of a cube environment map, in three… — A declarative source for image-based scene lighting.
39
39
 
40
+ ## @jgengine/core/render/lightingBalance
41
+
42
+ - `DEFAULT_IBL_AMBIENT_SCALE` (const): const DEFAULT_IBL_AMBIENT_SCALE: 0.5 — Ambient multiplier applied under image-based light when `iblAmbientScale` is unset.
43
+ - `FALLBACK_KEY_LIGHT` (const): const FALLBACK_KEY_LIGHT: Readonly<DirectionalLightingConfig> — Soft key added when authored lighting has no directional light: high, off to one side, shadowless.
44
+ - `balanceLightingForIbl` (function): function balanceLightingForIbl(lighting: LightingConfig | undefined): LightingConfig | undefined — Rebalance authored lighting for a scene that also has image-based light: trim the flat ambient term by `iblAmbientScale` (default 0.5) and, when no directional light is authored, add {@link FALLBACK_KEY_LIGHT} tinted by the ambient colour so forms keep a lit and a shaded side. Returns the input unchanged when there is nothing to adjust.
45
+
40
46
  ## @jgengine/core/render/lookPreset
41
47
 
42
48
  - `CINEMATIC_POST_PROCESSING` (const): const CINEMATIC_POST_PROCESSING: PostProcessingConfig — Tuned tone-map + bloom + gentle SSAO + vignette/grade stack — the shipped-game post look.
@@ -45,17 +51,19 @@
45
51
  - `GameLookInput` (interface): interface GameLookInput — Explicit graphics knobs a game may set; the preset only fills the ones left undefined.
46
52
  - `LOOK_PRESETS` (const): const LOOK_PRESETS: readonly LookPreset[] — Every look preset name, for pickers and validating a preset read from a URL or file.
47
53
  - `LookPreset` (type): type LookPreset = "neutral" | "photoreal" | "toon" | "comic" | "retro" | "cinematic" | "flat" — Named look preset composing the existing lighting/sky/fog/post knobs into one field. `"neutral"` (the default when unset) keeps presentation unstyled; `"cinematic"` draws a scene lit like a shipped game — a real day sky with a view-following shadow-casting sun + hemisphere fill, a network-free image-based-lighting environment so PBR surfaces catch soft reflections, and a tuned tone-map/bloom/AO/vignette post stack. `"comic"` adds ink outlines and cel bands over a saturated grade (Borderlands-style); `"retro"` pixelates and posterizes the frame. `"flat"` opts out of the sky/IBL/post rig to the bare ambient+directional default (pre-#773). The upgraded default primitive materials — tuned roughness/metalness plus subtle procedural surface detail so un-modeled boxes/capsules stop reading as flat plastic — apply under both presets.
48
- - `NEUTRAL_POST_PROCESSING` (const): const NEUTRAL_POST_PROCESSING: PostProcessingConfig — Unstyled baseline: a stable linear presentation with no genre-specific grading.
54
+ - `NEUTRAL_POST_PROCESSING` (const): const NEUTRAL_POST_PROCESSING: PostProcessingConfig — Unstyled baseline: no bloom or grade; contact AO stays at the graphics-tier default.
49
55
  - `PHOTOREAL_POST_PROCESSING` (const): const PHOTOREAL_POST_PROCESSING: PostProcessingConfig — High-contrast physically based presentation for authored HDR environments.
50
56
  - `RETRO_POST_PROCESSING` (const): const RETRO_POST_PROCESSING: PostProcessingConfig — Chunky pixels and posterized colour with no smoothing — a low-resolution retro look.
51
57
  - `ResolvedGameLook` (interface): interface ResolvedGameLook — Concrete lighting/backdrop/post the shell renders after the preset has filled unset knobs.
52
58
  - `TOON_POST_PROCESSING` (const): const TOON_POST_PROCESSING: PostProcessingConfig — Clean graphic presentation; toon materials remain game-owned while this sets the grade.
53
- - `resolveGameLook` (function): function resolveGameLook(input: GameLookInput): ResolvedGameLook — Expand a game's `look` into concrete lighting/backdrop/post. The default is `"neutral"`; `"flat"` passes the explicit knobs through untouched. Anything the game authored wins — the preset only fills unset knobs, and it never adds a sky when the world already owns one (so the sky's tuned sun/hemisphere serve as the lighting rig).
59
+ - `mergePostProcessing` (function): function mergePostProcessing(preset: PostProcessingConfig, authored: PostProcessingConfig | undefined): PostProcessingConfig — Deep-merge a game's partial post chain over a preset's. Every field the game set wins, including `false` to drop a stage; fields it left unset come from the preset, and stage objects merge field by field (tuples such as `grade.lift` replace whole).
60
+ - `resolveGameLook` (function): function resolveGameLook(input: GameLookInput): ResolvedGameLook — Expand a game's `look` into concrete lighting/backdrop/post. The default is `"neutral"`; `"flat"` passes the explicit knobs through untouched. Anything the game authored wins — the preset only fills unset knobs, and it never adds a sky when the world already owns one (so the sky's tuned sun/hemisphere serve as the lighting rig). A styled preset's post stages are deep-merged under a partial game `postProcessing` ({@link mergePostProcessing}); under `"neutral"` a game's own chain is used as authored.
54
61
 
55
62
  ## @jgengine/core/render/postProcessing
56
63
 
57
64
  - `AoConfig` (interface): interface AoConfig — Ground-truth ambient occlusion stage — darkens contact creases and cavities for depth.
58
65
  - `BloomConfig` (interface): interface BloomConfig — UnrealBloom stage — soft HDR glow around bright pixels (sun, glints, emissive).
66
+ - `DEFAULT_AO` (const): const DEFAULT_AO: Readonly<AoConfig> — Contact shading used when a post chain leaves `ao` unset: soft creases, no dark halos.
59
67
  - `DofConfig` (interface): interface DofConfig — Depth-of-field (bokeh) stage — throws the fore/background out of focus around a focus distance.
60
68
  - `GradeConfig` (interface): interface GradeConfig — Final colour-grade stage: lift/gain/gamma, saturation, vignette, film grain — applied in display space after tone mapping.
61
69
  - `OutlineConfig` (interface): interface OutlineConfig — Ink outlines traced from depth and surface-normal edges — silhouettes plus interior creases.
@@ -83,6 +91,7 @@
83
91
 
84
92
  - `DEFAULT_GRAPHICS_PROFILES` (const): const DEFAULT_GRAPHICS_PROFILES: Record<GraphicsQuality, GraphicsProfile> — Conservative defaults that preserve the existing high-quality shell behavior.
85
93
  - `GraphicsProfile` (interface): interface GraphicsProfile — Serializable renderer budget selected by the player's graphics tier.
94
+ - `SurfaceDetailLevel` (type): type SurfaceDetailLevel = "off" | "lite" | "full" — Procedural surface detail on loaded models: `"full"` adds mottling, wear, roughness break-up, a micro-normal and cavity shading; `"lite"` drops the micro-normal; `"off"` compiles the plain material.
86
95
  - `resolveGraphicsProfile` (function): function resolveGraphicsProfile(quality: GraphicsQuality, overrides?: Partial<GraphicsProfile>): GraphicsProfile — Resolve one tier and apply game-authored field and post-stage overrides.
87
96
 
88
97
  ## @jgengine/core/settings/graphicsSettings
@@ -98,8 +107,10 @@
98
107
  - `RENDER_SCALE_STEP` (const): const RENDER_SCALE_STEP: 0.05 — Render-scale slider increment.
99
108
  - `applyGraphicsQuality` (function): function applyGraphicsQuality(store: Pick<SettingsStore, "set">, quality: GraphicsQuality, overrides?: GraphicsProfileOverrides): GraphicsProfile — Select a quality tier and re-apply its render scale, texture filtering and stage defaults, so picking a preset resets the individual overrides instead of leaving stale toggles behind.
100
109
  - `graphicsPostStageSettingId` (function): function graphicsPostStageSettingId(stage: GraphicsPostStage): string — Setting id that stores one stage toggle (`graphics.post.<stage>`).
110
+ - `parseGraphicsQuality` (function): function parseGraphicsQuality(raw: string | null | undefined): GraphicsQuality | null — Parse a `?quality=` value into a graphics tier, or null when absent or unknown.
101
111
  - `readGraphicsQuality` (function): function readGraphicsQuality(store: Pick<SettingsStore, "get">): GraphicsQuality — The stored quality tier, falling back to the default when the value is missing or unknown.
102
112
  - `readGraphicsSettings` (function): function readGraphicsSettings(store: Pick<SettingsStore, "get">, overrides?: GraphicsProfileOverrides): GraphicsSettingsState — Resolve the player's graphics state from a settings store. The tier profile (with the game's overrides) supplies every default; a stored render scale or stage toggle replaces its field.
113
+ - `withGraphicsQualityOverride` (function): function withGraphicsQualityOverride(store: SettingsStore, quality: GraphicsQuality, overrides?: GraphicsProfileOverrides): SettingsStore — Wrap a settings store so the graphics tier reads as `quality` for this session only: every graphics field resolves from that tier until the player changes it, and nothing is persisted. Capture hosts use it (`?quality=low`) to render cheap iteration shots without leaving the draft tier in the browser profile for the next full-quality shot.
103
114
 
104
115
  ## @jgengine/core/settings/settingsModel
105
116
 
@@ -302,7 +313,7 @@
302
313
  - `resizePanelSize` (function): function resizePanelSize(current: HudSize, delta: { dw: number; dh: number }, axes: HudResizeAxes, limits?: { minWidth?: number; maxWidth?: number; minHeight?: number; maxHeight?: number; }): HudSize — Semantic resize: apply pixel deltas only on growable axes, clamped to min/max. Never scales content — callers reflow layout size (track length, list rows).
303
314
  - `resolveAction` (function): function resolveAction(def: ActionDef): ResolvedAction — Resolve one {@link ActionDef} into a {@link ResolvedAction}: normalize label, compute `enabled`, and order the blocking `reasons`. Pure — the same input always yields the same view model.
304
315
  - `resolveActionCollection` (function): function resolveActionCollection(defs: readonly ActionDef[]): ResolvedAction[] — Resolve an ordered list of actions, preserving input order.
305
- - `resolveGameLook` (function): function resolveGameLook(input: GameLookInput): ResolvedGameLook — Expand a game's `look` into concrete lighting/backdrop/post. The default is `"neutral"`; `"flat"` passes the explicit knobs through untouched. Anything the game authored wins — the preset only fills unset knobs, and it never adds a sky when the world already owns one (so the sky's tuned sun/hemisphere serve as the lighting rig).
316
+ - `resolveGameLook` (function): function resolveGameLook(input: GameLookInput): ResolvedGameLook — Expand a game's `look` into concrete lighting/backdrop/post. The default is `"neutral"`; `"flat"` passes the explicit knobs through untouched. Anything the game authored wins — the preset only fills unset knobs, and it never adds a sky when the world already owns one (so the sky's tuned sun/hemisphere serve as the lighting rig). A styled preset's post stages are deep-merged under a partial game `postProcessing` ({@link mergePostProcessing}); under `"neutral"` a game's own chain is used as authored.
306
317
  - `resolveHudFit` (function): function resolveHudFit(config: HudViewportConfig | undefined, mobile: boolean): Required<HudFitConfig> — ⚠ undocumented
307
318
  - `resolveHudPanelLayout` (function): function resolveHudPanelLayout(docPanel: EditorUiPanelLayout | undefined, fallback: HudPanelFallback): ResolvedHudPanelLayout — Resolve a panel's layout: document entry wins field-by-field over TSX fallback. TSX props are fallback-only — the scene document is the source of truth once authored.
308
319
  - `resolveOrientationRequirement` (function): function resolveOrientationRequirement(orientation: GameOrientation | undefined, platform: "mobile" | "desktop"): OrientationRequirement — Resolve the game's orientation declaration into a concrete requirement for a platform. Desktop is always unconstrained.
@@ -542,7 +553,7 @@
542
553
  - `ActionBarChrome` (function): function ActionBarChrome({ model, layout = "grid", itemSize = 52, gap = 6, ariaLabel = "Actions", renderItem, className, style, }: { model: ActionBarModel; layout?: "grid" | "list"; itemSize?: number; gap?: number; ariaLabel?: string; renderItem?: (action: ResolvedAction, ctx: ActionItemContext) => … — The composable RENDERER: lay the model's actions out as a `grid` or `list` toolbar with roving tabindex, arrow-key navigation, and a moved DOM focus. `renderItem` swaps per-item chrome; omit it for {@link ActionButton}. A radial or bespoke UI can skip this entirely and read the model. Purely a wiring/behavior layer — no panel skin — so a game art-directs freely.
543
554
  - `ActionBarKeyResult` (interface): interface ActionBarKeyResult — The intent a key press resolves to over an action bar: move focus, activate, or ignore.
544
555
  - `ActionBarModel` (interface): interface ActionBarModel — The live action-bar model returned by {@link useActionBar}.
545
- - `ActionButton` (function): function ActionButton({ action, focused = false, hovered = false, size = 52, showHotkey = true, tabIndex, buttonRef, onActivate, onFocus, onHover, renderIcon, className, style, }: { action: ResolvedAction; focused?: boolean; hovered?: boolean; size?: number; showHotkey?: boolean; tabIndex?: number; … — The default per-item chrome — a cooldown-aware action button with hotkey cap, cost line, radial cooldown wipe, and full rest/hover/focus/pressed/active/disabled states. Accessible: it is a real `<button>` with `aria-disabled`, `aria-pressed` for toggles, `aria-keyshortcuts`, and a native `title` describing why it is blocked. Swap it via `ActionBarChrome`'s `renderItem`, or drop it into a radial layout yourself.
556
+ - `ActionButton` (function): function ActionButton({ action, focused = false, hovered = false, size = 52, showHotkey = true, tabIndex, buttonRef, onActivate, onFocus, onHover, renderIcon, cue, onInteract, className, style, }: { action: ResolvedAction; /** Feedback cue name (default the action id), rendered as `data-jg-cue`. */ … — The default per-item chrome — a cooldown-aware action button with hotkey cap, cost line, radial cooldown wipe, and full rest/hover/focus/pressed/active/disabled states. Accessible: it is a real `<button>` with `aria-disabled`, `aria-pressed` for toggles, `aria-keyshortcuts`, and a native `title` describing why it is blocked. Swap it via `ActionBarChrome`'s `renderItem`, or drop it into a radial layout yourself.
546
557
  - `ActionItemContext` (interface): interface ActionItemContext — Render context passed to a custom {@link ActionBarChrome} item renderer.
547
558
  - `ActionKeyEvent` (interface): interface ActionKeyEvent — A minimal keyboard-event shape — a real `KeyboardEvent`/React synthetic satisfies it.
548
559
  - `ActionTooltip` (function): function ActionTooltip({ content, className, style, }: { content: TooltipContent; className?: string; style?: CSSProperties; }): React.JSX.Element — Render {@link TooltipContent} (from `actionTooltip`) as a styled block: title, subtitle, body, hotkey, cost lines, cooldown, and a warning footer of blocking notes. Drop it inside a {@link Popover} for an action's hover/focus description.
@@ -590,7 +601,7 @@
590
601
  - `ComboMeterTheme` (interface): interface ComboMeterTheme — Reskin tokens for {@link ComboMeterHud}.
591
602
  - `Compass` (function): function Compass({ facingYaw, center, markers, width = 340, fov = (Math.PI * 2) / 3, kindStyles = DEFAULT_MARKER_KINDS, className, }: CompassProps): ReactNode — Horizontal compass strip centered on the player's facing direction, with the eight cardinals and optional marker pips from static views, an external source, or a native `MarkerSet`.
592
603
  - `CompassProps` (interface): interface CompassProps { facingYaw: number; center?: WorldXZ; markers?: MarkerCollection; width?: number; fov?: number; kindStyles?: Record<string, MarkerKindStyle>; className?: string } — ⚠ undocumented · used by `Compass`: Horizontal compass strip centered on the player's facing direction, with the eight cardinals and optional marker pips from static views, an …
593
- - `ConfirmDialog` (function): function ConfirmDialog({ title, body, confirmLabel = "Confirm", cancelLabel = "Cancel", onConfirm, onCancel, danger = false, width = 360, className, style, }: ConfirmDialogProps): ReactNode — A generic two-button confirmation dialog — title, optional body, and Cancel / Confirm buttons over a `HudTheme`-token-driven {@link HudFrame}. Presentation-only: wire `onConfirm`/`onCancel` to a modal stack's `resolve` (typically inside a {@link ModalHost} render callback) and reskin via `HudTheme` tokens or `danger` for a destructive action. It interprets nothing — the game supplies the copy.
604
+ - `ConfirmDialog` (function): function ConfirmDialog({ title, body, confirmLabel = "Confirm", cancelLabel = "Cancel", onConfirm, onCancel, danger = false, width = 360, className, style, }: ConfirmDialogProps): ReactNode — A generic two-button confirmation dialog — title, optional body, and Cancel / Confirm buttons over a `HudTheme`-token-driven {@link HudPanel}. Presentation-only: wire `onConfirm`/`onCancel` to a modal stack's `resolve` (typically inside a {@link ModalHost} render callback) and reskin via `HudTheme` tokens or `danger` for a destructive action. It interprets nothing — the game supplies the copy.
594
605
  - `ConfirmDialogProps` (interface): interface ConfirmDialogProps — Props for {@link ConfirmDialog}.
595
606
  - `ControlHint` (interface): interface ControlHint — One row of a control legend. Name the game action(s) whose bound key(s) to show (`action`) so the glyphs come straight from the keybind map — never re-typed — or give literal `keys` for controls that live outside the map (`"Mouse"`, `"LMB"`). `label` says what the control does.
596
607
  - `ControlsList` (function): function ControlsList({ bindings, controls, hideOnCoarsePointer = true, separator = " / ", className, rowClassName, keysClassName, keyClassName, separatorClassName, labelClassName, renderKey, renderRow, }: { bindings?: ActionCodesMap; controls: readonly ControlHint[]; hideOnCoarsePointer?: boolean; … — Renders a controls legend whose key glyphs come from the game's keybind map, so bindings live in one place and never drift from a hand-typed table. Hides itself on coarse-pointer devices (a touchscreen has no keyboard) via the same `data-jg-kbd-hint` marker as `KeyHint`. Headless: every part carries a `className` slot and `data-*` hook; pass `renderKey`/`renderRow` to fully own the markup.
@@ -677,8 +688,10 @@
677
688
  - `HudSkinPreview` (function): function HudSkinPreview({ className }: { className?: string }): React.JSX.Element — Procedural two-skin fixture for visual regression captures.
678
689
  - `HudTheme` (interface): interface HudTheme — A full HUD theme. Author one with {@link hudTheme} or {@link deriveHudTheme}.
679
690
  - `HudThemeBar` (interface): interface HudThemeBar — Framed-trough tokens shared by every atomic bar.
691
+ - `HudThemeElevation` (interface): interface HudThemeElevation — Layered `box-shadow` stacks by height, plus the inner top highlight a lit panel edge gets.
680
692
  - `HudThemeFont` (interface): interface HudThemeFont — Typography roles. Any CSS `font-family` list.
681
693
  - `HudThemeFrame` (interface): interface HudThemeFrame — Frame material + shape tokens (panels, `HudFrame variation="themed"`).
694
+ - `HudThemeMotion` (interface): interface HudThemeMotion — Motion durations and easings. Blocks multiply durations by `--jg-motion-scale`, which the engine stylesheet sets to 0 under `prefers-reduced-motion` or `[data-reduced-motion="true"]`.
682
695
  - `HudThemeOverrides` (interface): interface HudThemeOverrides — Per-group partial overrides accepted by {@link hudTheme}.
683
696
  - `HudThemePalette` (interface): interface HudThemePalette — `HudTheme` (#1034, #1573): one token object that restyles every shared token-driven building block at once — the atomic bars (#1033), frames, slots, the minimap ring, and the chrome the registry blocks read (surfaces, edges, text, status colors, fonts, rarity). `hudThemeVars(theme)` emits the whole `--jg-*` vocabulary, so setting a **game-authored** theme on any HUD ancestor re-skins `packages/react` primitives and `registry/jgengine` components together. Purely CSS-token driven (no image assets). Tokens are not a finished UI: every game still owns composition, skin, and art direction (see AGENTS.md). Built-in presets are demo/scaffold starting points only.
684
697
  - `HudThemePreset` (type): type HudThemePreset = keyof typeof HUD_THEME_PRESETS — Built-in demo/scaffold theme preset name — not a game identity.
@@ -686,8 +699,11 @@
686
699
  - `HudThemeRarity` (interface): interface HudThemeRarity — Item-rarity coloring shared by loot cards, tooltips, and inventory slots.
687
700
  - `HudThemeSeed` (interface): interface HudThemeSeed — Seed colors for {@link deriveHudTheme}. Only `accent` and `surface` are required.
688
701
  - `HudThemeSlot` (interface): interface HudThemeSlot — Action/inventory slot tokens.
702
+ - `HudThemeSpace` (interface): interface HudThemeSpace — Spacing steps (any CSS length).
703
+ - `HudThemeState` (interface): interface HudThemeState — Interactive-state tokens shared by every engine button, slot, and window.
689
704
  - `HudThemeStatus` (interface): interface HudThemeStatus — Semantic status colors — outcome, threat, and relationship coloring.
690
705
  - `HudThemeSurface` (interface): interface HudThemeSurface — Chrome surfaces, edges, and text — what panels, overlays, and menus are built out of.
706
+ - `HudThemeType` (interface): interface HudThemeType — Type scale (any CSS length). `min` is the readability floor every engine block clamps to; `xs` is the smallest step a block uses, so it never drops below `min`.
691
707
  - `HudViewportContextValue` (interface): interface HudViewportContextValue { fitEnabled: boolean; config: HudViewportConfig | undefined; userScale: number } — ⚠ undocumented
692
708
  - `HudViewportProvider` (function): function HudViewportProvider({ platforms, config, userScale, children, }: { platforms: readonly HudPlatform[] | undefined; config: HudViewportConfig | undefined; userScale?: number; children?: ReactNode; }): React.JSX.Element — Mounted by the shell around `GameUI` so every `HudCanvas` inside the game picks up the game's `platforms`/`hudFit` declaration and the player's UI scale setting without any game-side wiring.
693
709
  - `I18nProvider` (function): function I18nProvider({ i18n, children }: { i18n: I18n; children: ReactNode }): ReactNode — Provide an {@link I18n} instance to the tree so `useT`/`useLocale` can read it.
@@ -737,7 +753,7 @@
737
753
  - `MinimapTrackProps` (interface): interface MinimapTrackProps — Props for {@link MinimapTrack}.
738
754
  - `MinimapTrackSpan` (interface): interface MinimapTrackSpan — A colored span across the {@link MinimapTrack} rail, given by 0..1 `start`/`end` fractions (e.g. from core `trackFraction`).
739
755
  - `ModalControls` (interface): interface ModalControls — Stack-bound controls handed to a {@link ModalHost} render callback.
740
- - `ModalHost` (function): function ModalHost({ stack, children, showBackdrop = true, backdrop, closeOnBackdrop = true, closeOnEsc = true, zIndexBase = 80, className, style, }: ModalHostProps): ReactNode — Full-screen modal host: watches a stack, renders only the topmost modal centered over a dimmed backdrop, and manages accessibility — the panel is a `role="dialog" aria-modal`, focus moves into it on open and is trapped with Tab/Shift+Tab cycling, Escape and (optionally) a backdrop click cancel the top modal, and focus returns to the previously focused element on close. It renders nothing when the stack is empty. Skin the panel and backdrop via `HudTheme` tokens.
756
+ - `ModalHost` (function): function ModalHost({ stack, children, showBackdrop = true, backdrop, closeOnBackdrop = true, closeOnEsc = true, zIndexBase = 80, onInteract, className, style, }: ModalHostProps): ReactNode — Full-screen modal host: watches a stack, renders only the topmost modal centered over a dimmed backdrop, and manages accessibility — the panel is a `role="dialog" aria-modal`, focus moves into it on open and is trapped with Tab/Shift+Tab cycling, Escape and (optionally) a backdrop click cancel the top modal, and focus returns to the previously focused element on close. It renders nothing when the stack is empty. Skin the panel and backdrop via `HudTheme` tokens.
741
757
  - `ModalHostProps` (interface): interface ModalHostProps — Props for {@link ModalHost}.
742
758
  - `ModalStackView` (interface): interface ModalStackView — React chrome over the headless modal stack (`@jgengine/core/ui/modalStack`) — the blocking-overlay layer a game raises over its scene: a pause menu, a "quit to menu?" confirm, a reward popup. {@link useModalStack} is the DATA/HOOK layer (subscribe + the active record), {@link ModalHost} renders the top modal with a backdrop, focus trap, and Esc-to-cancel, and {@link ConfirmDialog} / {@link PauseMenu} are ready-to-wire, HudTheme-token-driven building blocks. All unskinned and game-owned: the model never interprets a modal's `kind`, so a game switches on it to pick which of these (or its own) content to show, and reskins everything via `HudTheme` tokens and the frame.
743
759
  - `NotificationBell` (function): function NotificationBell({ store, onClick, icon = "🔔", className, style }: NotificationBellProps): ReactNode — Notification bell — an icon button with an unread-count badge, bound to a notification store. Toggle a {@link NotificationCenter} panel from its click.
@@ -748,7 +764,7 @@
748
764
  - `ObjectiveBannerHostProps` (interface): interface ObjectiveBannerHostProps — Props for {@link ObjectiveBannerHost}.
749
765
  - `ObjectiveBannerTheme` (interface): interface ObjectiveBannerTheme — Reskin tokens for {@link ObjectiveBannerHost}. Per-`kind` overrides layer on top.
750
766
  - `PREVIEW_FIXTURES` (const): const PREVIEW_FIXTURES: Record<string, PreviewFixture> — Registry of deterministic, engine-level preview fixtures — the *real* exported `@jgengine/react` components (`HudThemePreview`, `BarsPreview`, `IconsPreview`, …) that render identically every time from static values, so they can be screenshotted as regression evidence without booting a game.
751
- - `PanelHost` (function): function PanelHost({ manager, render, panels, variation, shape, width, zIndexBase = 40, className, windowClassName, windowStyle, bodyStyle, }: { manager: PanelsManager; /** Content for a panel by id. Takes precedence over `panels`. */ render?: (id: string) => ReactNode; /** Static content map by pan… — Renders the open panels of a {@link usePanels} manager as absolutely-positioned, draggable, closable windows in z-order — the WoW window layer. Each window uses {@link HudFrame} chrome, is dragged by its title bar, raises focus on pointer-down, and gets its content from the `render` prop (or a `panels` map). Accessible: every window is a `role="dialog"` with an `aria-label` and a focusable close button; ESC-close is wired by the manager. Unskinned — art-direct via `HudTheme` tokens and the `variation`.
767
+ - `PanelHost` (function): function PanelHost({ manager, render, panels, variation, shape, width, zIndexBase = 40, className, windowClassName, windowStyle, bodyStyle, onInteract, }: { manager: PanelsManager; /** Called as windows open and close (`cue` is `panel:<id>`) — the seam a UI sound system hooks. */ onInteract?: HudInt… — Renders the open panels of a {@link usePanels} manager as absolutely-positioned, draggable, closable windows in z-order — the WoW window layer. Each window uses {@link HudFrame} chrome, is dragged by its title bar, raises focus on pointer-down, and gets its content from the `render` prop (or a `panels` map). Accessible: every window is a `role="dialog"` with an `aria-label` and a focusable close button; ESC-close is wired by the manager. Unskinned — art-direct via `HudTheme` tokens and the `variation`.
752
768
  - `PanelKeyResult` (interface): interface PanelKeyResult — React chrome over the headless panel/window model (`@jgengine/core/ui/panelModel`) — the toggleable, draggable, z-stacked windows a WoW-style UI is made of (B for bag, C for character, ESC to close). `usePanels` is the DATA/HOOK layer (open/close/toggle/focus + a keybind listener), `PanelHost` renders the open windows in z-order, and `Window` is a standalone one-off window. All unskinned and token-driven: games reskin via `HudTheme` tokens and by swapping the `HudFrame` variation.
753
769
  - `PanelsManager` (interface): interface PanelsManager — The live panel manager returned by {@link usePanels} and consumed by {@link PanelHost}.
754
770
  - `Paperdoll` (function): function Paperdoll({ slots, portrait, slotSize = 44, onSlotActivate, className, style }: PaperdollProps): ReactNode — Arranges caller-supplied equip slots as a paperdoll — worn gear in two flanking columns around a center `portrait` (or a silhouette placeholder), with any `bottom` slots in a row beneath it. Each slot reuses the atomic {@link EquipmentSlots} tile (filled icon or themed empty placeholder), carries a label, and is a focusable `role="group"` with an `aria-label`; `onSlotActivate` fires on click/Enter/Space so games can wire drag or an equip picker. Composition and skin stay the game's.
@@ -756,7 +772,7 @@
756
772
  - `PartyFrame` (function): function PartyFrame({ className, rowClassName, dotClassName, emptyState, renderMember, }: { className?: string; rowClassName?: string; dotClassName?: string; emptyState?: ReactNode; renderMember?: (member: PartyMemberEntry) => ReactNode; }): React.JSX.Element — ⚠ undocumented
757
773
  - `PartyInviteToast` (function): function PartyInviteToast({ className, acceptClassName, declineClassName, renderInvite, }: { className?: string; acceptClassName?: string; declineClassName?: string; renderInvite?: (invite: PartyInviteEntry) => ReactNode; }): React.JSX.Element | null — ⚠ undocumented
758
774
  - `PartyMemberRow` (function): function PartyMemberRow({ member, className, dotClassName, children, }: { member: PartyMemberEntry; className?: string; dotClassName?: string; children?: ReactNode; }): React.JSX.Element — ⚠ undocumented
759
- - `PauseMenu` (function): function PauseMenu({ title = "Paused", onResume, resumeLabel = "Resume", items = [], width = 300, className, style, }: PauseMenuProps): ReactNode — A reskinnable pause-menu building block: a primary Resume button plus a game-filled list of slot items (Settings, Save, Quit, …), stacked in a `HudTheme`-token-driven {@link HudFrame}. It ships the common shape every game's pause screen needs and nothing genre-specific — the game supplies the title, item labels/order, and destructive styling, and reskins with `HudTheme` tokens. Drop it into a {@link ModalHost} keyed on a `"pause"` modal, or render it standalone.
775
+ - `PauseMenu` (function): function PauseMenu({ title = "Paused", onResume, resumeLabel = "Resume", items = [], width = 300, className, style, }: PauseMenuProps): ReactNode — A reskinnable pause-menu building block: a primary Resume button plus a game-filled list of slot items (Settings, Save, Quit, …), stacked in a `HudTheme`-token-driven {@link HudPanel}. It ships the common shape every game's pause screen needs and nothing genre-specific — the game supplies the title, item labels/order, and destructive styling, and reskins with `HudTheme` tokens. Drop it into a {@link ModalHost} keyed on a `"pause"` modal, or render it standalone.
760
776
  - `PauseMenuItem` (interface): interface PauseMenuItem — One selectable row in a {@link PauseMenu}'s slot list (Settings, Save, Quit, …).
761
777
  - `PauseMenuProps` (interface): interface PauseMenuProps — Props for {@link PauseMenu}.
762
778
  - `PhotoModeControls` (function): function PhotoModeControls({ store, onCapture, className, style }: PhotoModeControlsProps): ReactNode — Photo-mode toolbar — a hide/show-HUD toggle, a capture button, and an exit button, bound to a `createPhotoModeStore`. Presentation-only: the game wires `onCapture` to shell `captureCanvas`/`downloadImage` and reads `hideHud` to drop its gameplay HUD.
@@ -870,7 +886,7 @@
870
886
  - `WaypointMarkers` (function): function WaypointMarkers({ entries, project, viewport, distanceOf, distanceFormat, theme, kindColors, margin = 28, showPinLabels = true, className, style, }: WaypointMarkersProps): ReactNode — Off-screen objective / waypoint markers: an absolutely-positioned overlay that turns a set of world waypoints into on-screen pins and off-screen directional arrows. For each waypoint it calls the caller's `project` to get a screen point, then {@link layoutScreenMarker} to decide: an in-view target renders a pin (and optional label) at its projected spot; an off-screen or behind-camera target renders an arrow clamped to the viewport edge, rotated to point the way, with an optional distance label from `distanceOf`. Color comes from `theme` plus a per-`kind` `kindColors` map the game owns — the model never interprets `kind`. Presentation only: tracking, projection, and distance are all caller-supplied.
871
887
  - `WaypointMarkersProps` (interface): interface WaypointMarkersProps — Props for {@link WaypointMarkers}.
872
888
  - `WeaponSlots` (function): function WeaponSlots(props: SlotGridProps): React.JSX.Element — Weapon loadout slots.
873
- - `Window` (function): function Window({ title, ariaLabel, closable = true, onClose, x, y, onMove, z, width, variation, shape, className, style, bodyStyle, children, }: { title: ReactNode; ariaLabel?: string; closable?: boolean; onClose?: () => void; /** Initial (uncontrolled) or current (controlled, with `onMove`) x posi… — A single standalone window — title bar, close button, and title-bar drag over {@link HudFrame} chrome — usable without the {@link usePanels} manager for a one-off dialog. Position is uncontrolled (seeded by `x`/`y`) unless both `x`/`y` and `onMove` are supplied, in which case the caller owns placement. Accessible: `role="dialog"` with an `aria-label` and a focusable close button.
889
+ - `Window` (function): function Window({ title, ariaLabel, closable = true, onClose, x, y, onMove, z, width, variation, shape, className, style, bodyStyle, cue, onInteract, children, }: { title: ReactNode; ariaLabel?: string; closable?: boolean; /** Feedback cue name, rendered as `data-jg-cue` and passed to `onInteract`. … — A single standalone window — title bar, close button, and title-bar drag over {@link HudFrame} chrome — usable without the {@link usePanels} manager for a one-off dialog. Position is uncontrolled (seeded by `x`/`y`) unless both `x`/`y` and `onMove` are supplied, in which case the caller owns placement. Accessible: `role="dialog"` with an `aria-label` and a focusable close button.
874
890
  - `WorldBrowser` (function): function WorldBrowser({ listings, onJoin, className, rowClassName, joinClassName, emptyState, renderListing, }: { listings: readonly SessionListing[]; onJoin: (listing: SessionListing) => void; className?: string; rowClassName?: string; joinClassName?: string; emptyState?: ReactNode; renderListing?:… — ⚠ undocumented
875
891
  - `WorldBrowserState` (interface): interface WorldBrowserState { listings: SessionListing[]; loading: boolean; error: string | null; refresh(): void } — ⚠ undocumented · used by `useWorldBrowser`: Polls a host-supplied session fetcher (e.g.
876
892
  - `WorldInviteToast` (function): function WorldInviteToast({ className, acceptClassName, declineClassName, onAccepted, renderInvite, }: { className?: string; acceptClassName?: string; declineClassName?: string; onAccepted: (target: WorldInviteTarget) => void; renderInvite?: (invite: WorldInvite) => ReactNode; }): React.JSX.Element … — ⚠ undocumented
@@ -887,7 +903,7 @@
887
903
  - `clerkIdentity` (function): function clerkIdentity(state: ClerkUserState, options?: { signOut?: () => void }): IdentitySource — ⚠ undocumented
888
904
  - `createHeldKeyTracker` (function): function createHeldKeyTracker(target: HeldKeyEventTarget): { isDown: (code: string) => boolean; dispose: () => void; } — ⚠ undocumented
889
905
  - `defaultEquipLayout` (function): function defaultEquipLayout(items: Partial<Record<DefaultEquipSlotId, SlotItem | null>> = {}): EquipSlot[] — A sensible default paperdoll arrangement — worn armor down the left flank, held gear and jewelry down the right — that the game fills by passing worn items keyed by slot id. Every id is optional; unfilled slots render empty. Games that need a different slot set pass their own {@link EquipSlotLayout}.
890
- - `defaultHudTheme` (const): const defaultHudTheme: HudTheme — Default theme tokens for previews/scaffold — not a finished game look; author a custom theme.
906
+ - `defaultHudTheme` (const): const defaultHudTheme: HudTheme — The engine's default HUD look, applied by the shell at the HUD root of every game that does not opt out: a strong system UI font stack, layered translucent panels with a lit top rim and crisp hairline borders, a 12px type floor, and motion that honours reduced-motion. Games override any part with their own theme on a HUD ancestor.
891
907
  - `deriveHudTheme` (function): function deriveHudTheme(seed: HudThemeSeed): HudTheme — Derives a coherent full theme from an accent/surface pair — shades, edges, dim text, frames, bar troughs, and slots all fall out of the seed, so a game gets one consistent look across the `packages/react` primitives and the `registry/jgengine` blocks without naming 50 tokens. Hex seeds only (`#rgb` or `#rrggbb`).
892
908
  - `eventMeterNeedsHeartbeat` (function): function eventMeterNeedsHeartbeat(meter: EventMeter, previous: EventMeterView | null): boolean — True when `meter`'s value/fraction/tier/ready diverge from `previous` — the re-render check `useEventMeter` polls on its heartbeat.
893
909
  - `formatTimerMs` (function): function formatTimerMs(ms: number, format: TimerFormat = "mm:ss"): string — Format a millisecond duration as digital timer text — `mm:ss` (default), `m:ss.d` (with tenths), or `ss.d` (seconds + tenths). Negative inputs clamp to zero. No genre meaning: the same helper serves a round clock, a respawn countdown, or an ability charge readout.
@@ -988,6 +1004,7 @@
988
1004
  - `useQuestJournal` (function): function useQuestJournal(): QuestInstance[] — ⚠ undocumented
989
1005
  - `useRafLoop` (function): function useRafLoop(onFrame: (deltaSeconds: number) => void, active = true): void — Run a requestAnimationFrame loop while `active`, calling `onFrame(deltaSeconds)` each frame. The callback is kept in a ref so re-renders never restart the loop; cleanup cancels the pending frame. For scene-graph work inside a canvas prefer R3F's useFrame — this is for DOM-side animation outside the renderer.
990
1006
  - `useRebindSession` (function): function useRebindSession(session: RebindSession): RebindSession — Subscribe to a rebind session and re-render on every change (capture, reset, arm/disarm, restore). Returns the session unchanged for convenient inline use.
1007
+ - `useReducedMotion` (function): function useReducedMotion(): boolean — True when motion should be skipped: the OS `prefers-reduced-motion` preference, or the nearest {@link AccessibilityProvider}'s `reducedMotion` setting. Works without a provider.
991
1008
  - `useRegisterLayoutRegion` (function): function useRegisterLayoutRegion(spec: LayoutRegionSpec, ref: RefObject<HTMLElement | null>, enabled = true): void — Register the element behind `ref` as a layout region and keep its measured rectangle live (ResizeObserver + viewport changes). No-op outside a `GameViewportProvider`, so a component using it still works in isolation.
992
1009
  - `useReservedControlZones` (function): function useReservedControlZones(): readonly LayoutRect[] — Rectangles reserved by touch controls and system UI — HUD placement should avoid these.
993
1010
  - `useRoster` (function): function useRoster(userId?: string): readonly RosterEntry[] — ⚠ undocumented
@@ -1023,6 +1040,7 @@
1023
1040
  - `ColorblindFilters` (function): function ColorblindFilters(): ReactNode — Hidden SVG `<defs>` holding the `feColorMatrix` colorblind filters that `AccessibilityProvider` references by `filter: url(#jg-cb-<mode>)`. Rendered automatically inside the provider; export standalone for custom roots.
1024
1041
  - `useAccessibility` (function): function useAccessibility(): AccessibilityContextValue — Read the live accessibility state + its store; throws without an {@link AccessibilityProvider} above.
1025
1042
  - `usePrefersReducedMotion` (function): function usePrefersReducedMotion(): boolean — OS "reduce motion" preference (`prefers-reduced-motion: reduce`) as a live boolean — SSR-safe (defaults false). Use it to seed an accessibility store's `reducedMotion` default.
1043
+ - `useReducedMotion` (function): function useReducedMotion(): boolean — True when motion should be skipped: the OS `prefers-reduced-motion` preference, or the nearest {@link AccessibilityProvider}'s `reducedMotion` setting. Works without a provider.
1026
1044
 
1027
1045
  ## @jgengine/react/achievements
1028
1046
 
@@ -1038,7 +1056,7 @@
1038
1056
  - `ActionBarChrome` (function): function ActionBarChrome({ model, layout = "grid", itemSize = 52, gap = 6, ariaLabel = "Actions", renderItem, className, style, }: { model: ActionBarModel; layout?: "grid" | "list"; itemSize?: number; gap?: number; ariaLabel?: string; renderItem?: (action: ResolvedAction, ctx: ActionItemContext) => … — The composable RENDERER: lay the model's actions out as a `grid` or `list` toolbar with roving tabindex, arrow-key navigation, and a moved DOM focus. `renderItem` swaps per-item chrome; omit it for {@link ActionButton}. A radial or bespoke UI can skip this entirely and read the model. Purely a wiring/behavior layer — no panel skin — so a game art-directs freely.
1039
1057
  - `ActionBarKeyResult` (interface): interface ActionBarKeyResult — The intent a key press resolves to over an action bar: move focus, activate, or ignore.
1040
1058
  - `ActionBarModel` (interface): interface ActionBarModel — The live action-bar model returned by {@link useActionBar}.
1041
- - `ActionButton` (function): function ActionButton({ action, focused = false, hovered = false, size = 52, showHotkey = true, tabIndex, buttonRef, onActivate, onFocus, onHover, renderIcon, className, style, }: { action: ResolvedAction; focused?: boolean; hovered?: boolean; size?: number; showHotkey?: boolean; tabIndex?: number; … — The default per-item chrome — a cooldown-aware action button with hotkey cap, cost line, radial cooldown wipe, and full rest/hover/focus/pressed/active/disabled states. Accessible: it is a real `<button>` with `aria-disabled`, `aria-pressed` for toggles, `aria-keyshortcuts`, and a native `title` describing why it is blocked. Swap it via `ActionBarChrome`'s `renderItem`, or drop it into a radial layout yourself.
1059
+ - `ActionButton` (function): function ActionButton({ action, focused = false, hovered = false, size = 52, showHotkey = true, tabIndex, buttonRef, onActivate, onFocus, onHover, renderIcon, cue, onInteract, className, style, }: { action: ResolvedAction; /** Feedback cue name (default the action id), rendered as `data-jg-cue`. */ … — The default per-item chrome — a cooldown-aware action button with hotkey cap, cost line, radial cooldown wipe, and full rest/hover/focus/pressed/active/disabled states. Accessible: it is a real `<button>` with `aria-disabled`, `aria-pressed` for toggles, `aria-keyshortcuts`, and a native `title` describing why it is blocked. Swap it via `ActionBarChrome`'s `renderItem`, or drop it into a radial layout yourself.
1042
1060
  - `ActionItemContext` (interface): interface ActionItemContext — Render context passed to a custom {@link ActionBarChrome} item renderer.
1043
1061
  - `ActionKeyEvent` (interface): interface ActionKeyEvent — A minimal keyboard-event shape — a real `KeyboardEvent`/React synthetic satisfies it.
1044
1062
  - `ActionTooltip` (function): function ActionTooltip({ content, className, style, }: { content: TooltipContent; className?: string; style?: CSSProperties; }): React.JSX.Element — Render {@link TooltipContent} (from `actionTooltip`) as a styled block: title, subtitle, body, hotkey, cost lines, cooldown, and a warning footer of blocking notes. Drop it inside a {@link Popover} for an action's hover/focus description.
@@ -1221,6 +1239,12 @@
1221
1239
  - `EventfulEngineStore` (interface): interface EventfulEngineStore<TEventMap extends object> { on<K extends keyof TEventMap>(eventName: K, listener: (payload: TEventMap[K]) => void): () => void } — ⚠ undocumented
1222
1240
  - `ReadableEngineStore` (interface): interface ReadableEngineStore<TState> { getState(): TState; subscribe(listener: (state: TState) => void): () => void } — ⚠ undocumented
1223
1241
 
1242
+ ## @jgengine/react/engineStyles
1243
+
1244
+ - `ENGINE_STYLESHEET` (const): const ENGINE_STYLESHEET: string — The engine stylesheet: every `jg-*` keyframe the shared blocks and registry components animate with, the `[data-jg-*]` interaction-state rules behind {@link HudButton} / {@link HudPanel}, the quiet hover/press/focus defaults for plain buttons inside the HUD root, and reduced-motion handling (`prefers-reduced-motion` or `[data-reduced-motion="true"]` zero `--jg-motion-scale`). Generic HUD rules sit in `:where()` so any game selector wins. The shell injects it; games that mount outside the shell import `@jgengine/react/styles.css`, which holds the same text.
1245
+ - `ensureEngineStyles` (function): function ensureEngineStyles(doc: Document | undefined = typeof document === "undefined" ? undefined : document): void — Injects {@link ENGINE_STYLESHEET} into `doc` once. Safe to call repeatedly and during SSR (no-op).
1246
+ - `useEngineStyles` (function): function useEngineStyles(): void — Ensures the engine stylesheet is in the document before the calling component paints. Engine primitives call it, so they animate and show interaction states even outside the shell.
1247
+
1224
1248
  ## @jgengine/react/entityFrames
1225
1249
 
1226
1250
  - `EntityFrameEntry` (interface): interface EntityFrameEntry — The minimum an entry must carry; games extend it with their own display data.
@@ -1340,6 +1364,17 @@
1340
1364
  - `StatTone` (type): type StatTone = keyof typeof TONES — A stat tone — picks the bar's color ramp.
1341
1365
  - `WaveBanner` (function): function WaveBanner({ wave, label = "Wave", subtitle, style, className, }: { wave: number | string; label?: string; subtitle?: ReactNode; style?: CSSProperties; className?: string; }): React.JSX.Element — A wave / round banner — a bold centered pill for "WAVE 3" style callouts, with an optional subtitle (enemies remaining, timer). Pure display: pass the current `wave` and whatever subtitle you track.
1342
1366
 
1367
+ ## @jgengine/react/hudButton
1368
+
1369
+ - `HudButton` (const): const HudButton: React.ForwardRefExoticComponent<HudButtonProps & React.RefAttributes<HTMLButtonElement>> — Themed HUD button: reads the `HudTheme` slot, accent, elevation, motion, and state tokens, and gets hover lift, press scale, a focus-visible ring, and a disabled state from the engine stylesheet. `className` and `style` restyle it per game. Interactions report to `onInteract` and the nearest `HudInteractProvider`.
1370
+ - `HudButtonProps` (interface): interface HudButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type"> — Props for {@link HudButton}: native button attributes plus theme-driven variant, size, and feedback hooks.
1371
+ - `HudButtonSize` (type): type HudButtonSize = "sm" | "md" | "lg" | "icon" — Size of a {@link HudButton}; `icon` is a 28px square.
1372
+ - `HudButtonVariant` (type): type HudButtonVariant = "secondary" | "primary" | "danger" | "ghost" — Visual weight of a {@link HudButton}.
1373
+
1374
+ ## @jgengine/react/hudChromePreview
1375
+
1376
+ - `HudChromePreview` (function): function HudChromePreview({ className }: { className?: string }): React.JSX.Element — The shared HUD chrome under the shell's default root theme, from static values: a glass frame with vitals and a counter, an action bar, a window, a confirm dialog, a pause menu, and the button variants. Screenshots identically; the before/after evidence for theme and primitive changes.
1377
+
1343
1378
  ## @jgengine/react/hudFrame
1344
1379
 
1345
1380
  - `HudFrame` (function): function HudFrame({ variation = "glass", shape = "rounded", title, aside, padding, width, interactive, className, style, children, }: HudFrameProps): ReactNode — Optional framed panel primitive — a single `<div data-hud-frame>` with a `glass`, `plate`, `retro`, or `themed` skin, optional `title`/`aside` header, and caller `style` merged last. Shared building block for demos and token-themed widgets; games own product chrome and art direction (see AGENTS.md).
@@ -1347,6 +1382,14 @@
1347
1382
  - `HudFrameShape` (type): type HudFrameShape = "rounded" | "circle" | "square" — Frame corner shape — `rounded` (the variation's default radius), `circle` (fully round), or `square` (no radius).
1348
1383
  - `HudFrameVariation` (type): type HudFrameVariation = "glass" | "plate" | "retro" | "themed" — Frame skin — `glass` (the shared dark-glass panel), `plate` (a heavier opaque plate), `retro` (a hard black-outlined frame), or `themed` (driven by the `--jg-frame-*` `HudTheme` tokens).
1349
1384
 
1385
+ ## @jgengine/react/hudInteract
1386
+
1387
+ - `HudInteractEvent` (interface): interface HudInteractEvent — One interaction reported by an engine HUD primitive to an `onInteract` listener.
1388
+ - `HudInteractKind` (type): type HudInteractKind = "hover" | "press" | "activate" | "focus" | "open" | "close" — What happened to an engine HUD primitive.
1389
+ - `HudInteractListener` (type): type HudInteractListener = (event: HudInteractEvent) => void — Listener for {@link HudInteractEvent}s.
1390
+ - `HudInteractProvider` (function): function HudInteractProvider({ onInteract, children }: { onInteract: HudInteractListener; children?: ReactNode }): ReactNode — Routes interactions from every engine HUD primitive below it (buttons, panels, windows, action slots) to one listener — the seam a UI sound or haptics system hooks without per-button wiring. Primitives also expose `data-jg-cue` and a per-instance `onInteract`.
1391
+ - `useHudInteract` (function): function useHudInteract(source: string, cue?: string, onInteract?: HudInteractListener): (kind: HudInteractKind) => void — Returns a reporter that calls the instance listener, then the nearest {@link HudInteractProvider}.
1392
+
1350
1393
  ## @jgengine/react/hudLayout
1351
1394
 
1352
1395
  - `HudCanvas` (function): function HudCanvas({ layout, editChord, compactScale, showDuring, className, style, children, }: { layout: HudLayoutStore; editChord?: HudEditChord | false; /** Zoom applied to the whole HUD on compact displays. Default 0.85. */ compactScale?: number; /** Opt-in play-phase gate: render the HUD only … — Full-viewport HUD surface. Panels declared with `HudPanel` flow into nine anchor regions and stack automatically with a gap — no per-panel pixel offsets, no manual clearance for sibling panels, the touch-control dock (`--jg-hud-dock-clearance`), or device safe areas. On compact displays the whole surface scales down and each panel applies its `compact` behavior.
@@ -1362,6 +1405,12 @@
1362
1405
  - `HudLayoutPersistProvider` (function): function HudLayoutPersistProvider({ onPanelCommit, children, }: { onPanelCommit: HudLayoutPersist; children?: ReactNode; }): React.JSX.Element — Provides {@link HudLayoutPersist} / `onPanelCommit` to descendant `useHudLayout` calls. Mount around game UI when a host can accept document UI patches (editor). Absent provider → canvas moves/resizes are no-ops for document writes.
1363
1406
  - `useHudLayoutPersist` (function): function useHudLayoutPersist(): HudLayoutPersist | null — Injected panel-commit port, or `null` when no host has provided one.
1364
1407
 
1408
+ ## @jgengine/react/hudPanel
1409
+
1410
+ - `HudPanel` (const): const HudPanel: React.ForwardRefExoticComponent<HudPanelProps & React.RefAttributes<HTMLDivElement>> — Themed panel/window frame over the `HudTheme` frame, elevation, and motion tokens. It enters with a short rise-and-fade and stays mounted through its exit when `open` turns false (`data-state="open" | "closed"`); reduced motion skips both. `className` and `style` restyle it.
1411
+ - `HudPanelElevation` (type): type HudPanelElevation = "raised" | "overlay" | "modal" — Shadow height of a {@link HudPanel}.
1412
+ - `HudPanelProps` (interface): interface HudPanelProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> — Props for {@link HudPanel}.
1413
+
1365
1414
  ## @jgengine/react/hudSkin
1366
1415
 
1367
1416
  - `HudSkin` (interface): interface HudSkin — A game-authored visual skin layered over a {@link HudTheme}.
@@ -1379,17 +1428,22 @@
1379
1428
  - `HudSkin` (interface): interface HudSkin — A game-authored visual skin layered over a {@link HudTheme}.
1380
1429
  - `HudTheme` (interface): interface HudTheme — A full HUD theme. Author one with {@link hudTheme} or {@link deriveHudTheme}.
1381
1430
  - `HudThemeBar` (interface): interface HudThemeBar — Framed-trough tokens shared by every atomic bar.
1431
+ - `HudThemeElevation` (interface): interface HudThemeElevation — Layered `box-shadow` stacks by height, plus the inner top highlight a lit panel edge gets.
1382
1432
  - `HudThemeFont` (interface): interface HudThemeFont — Typography roles. Any CSS `font-family` list.
1383
1433
  - `HudThemeFrame` (interface): interface HudThemeFrame — Frame material + shape tokens (panels, `HudFrame variation="themed"`).
1434
+ - `HudThemeMotion` (interface): interface HudThemeMotion — Motion durations and easings. Blocks multiply durations by `--jg-motion-scale`, which the engine stylesheet sets to 0 under `prefers-reduced-motion` or `[data-reduced-motion="true"]`.
1384
1435
  - `HudThemeOverrides` (interface): interface HudThemeOverrides — Per-group partial overrides accepted by {@link hudTheme}.
1385
1436
  - `HudThemePalette` (interface): interface HudThemePalette — `HudTheme` (#1034, #1573): one token object that restyles every shared token-driven building block at once — the atomic bars (#1033), frames, slots, the minimap ring, and the chrome the registry blocks read (surfaces, edges, text, status colors, fonts, rarity). `hudThemeVars(theme)` emits the whole `--jg-*` vocabulary, so setting a **game-authored** theme on any HUD ancestor re-skins `packages/react` primitives and `registry/jgengine` components together. Purely CSS-token driven (no image assets). Tokens are not a finished UI: every game still owns composition, skin, and art direction (see AGENTS.md). Built-in presets are demo/scaffold starting points only.
1386
1437
  - `HudThemePreset` (type): type HudThemePreset = keyof typeof HUD_THEME_PRESETS — Built-in demo/scaffold theme preset name — not a game identity.
1387
1438
  - `HudThemeRarity` (interface): interface HudThemeRarity — Item-rarity coloring shared by loot cards, tooltips, and inventory slots.
1388
1439
  - `HudThemeSeed` (interface): interface HudThemeSeed — Seed colors for {@link deriveHudTheme}. Only `accent` and `surface` are required.
1389
1440
  - `HudThemeSlot` (interface): interface HudThemeSlot — Action/inventory slot tokens.
1441
+ - `HudThemeSpace` (interface): interface HudThemeSpace — Spacing steps (any CSS length).
1442
+ - `HudThemeState` (interface): interface HudThemeState — Interactive-state tokens shared by every engine button, slot, and window.
1390
1443
  - `HudThemeStatus` (interface): interface HudThemeStatus — Semantic status colors — outcome, threat, and relationship coloring.
1391
1444
  - `HudThemeSurface` (interface): interface HudThemeSurface — Chrome surfaces, edges, and text — what panels, overlays, and menus are built out of.
1392
- - `defaultHudTheme` (const): const defaultHudTheme: HudTheme — Default theme tokens for previews/scaffold — not a finished game look; author a custom theme.
1445
+ - `HudThemeType` (interface): interface HudThemeType — Type scale (any CSS length). `min` is the readability floor every engine block clamps to; `xs` is the smallest step a block uses, so it never drops below `min`.
1446
+ - `defaultHudTheme` (const): const defaultHudTheme: HudTheme — The engine's default HUD look, applied by the shell at the HUD root of every game that does not opt out: a strong system UI font stack, layered translucent panels with a lit top rim and crisp hairline borders, a 12px type floor, and motion that honours reduced-motion. Games override any part with their own theme on a HUD ancestor.
1393
1447
  - `deriveHudTheme` (function): function deriveHudTheme(seed: HudThemeSeed): HudTheme — Derives a coherent full theme from an accent/surface pair — shades, edges, dim text, frames, bar troughs, and slots all fall out of the seed, so a game gets one consistent look across the `packages/react` primitives and the `registry/jgengine` blocks without naming 50 tokens. Hex seeds only (`#rgb` or `#rrggbb`).
1394
1448
  - `hudSkinVars` (function): function hudSkinVars(skin?: HudSkin): CSSProperties — Converts a skin into the CSS variables consumed by shared HUD components.
1395
1449
  - `hudTheme` (function): function hudTheme(overrides: HudThemeOverrides): HudTheme — Builds a full {@link HudTheme} from per-group partials, filling the rest from {@link defaultHudTheme}. Reach for this when a game wants to name exact values; use {@link deriveHudTheme} when a seed color pair is enough.
@@ -1400,6 +1454,27 @@
1400
1454
 
1401
1455
  - `HudThemePreview` (function): function HudThemePreview({ className }: { className?: string }): React.JSX.Element — Renders the default theme plus every genre preset as a deterministic matrix.
1402
1456
 
1457
+ ## @jgengine/react/hudThemeRoot
1458
+
1459
+ - `HudThemeRoot` (function): function HudThemeRoot({ theme, children }: { theme?: HudTheme | false; children?: ReactNode }): ReactNode — The HUD root the shell mounts around every `GameUI`: a layout-free (`display: contents`) `[data-jg-hud]` element carrying the theme's `--jg-*` variables, plus the engine stylesheet. Unset `theme` applies {@link defaultHudTheme}; a game's own theme on any descendant still wins by inheritance. `false` opts out of the default theme and the generic HUD control defaults; the keyframes stay so engine blocks still animate.
1460
+
1461
+ ## @jgengine/react/hudTokens
1462
+
1463
+ - `HUD_FOCUS_RING` (const): const HUD_FOCUS_RING: "var(--jg-focus-ring, 0 0 0 2px #0a0c10, 0 0 0 4px var(--jg-accent, #38bdf8))" — Focus ring `box-shadow` reference.
1464
+ - `HUD_FONT_STACK` (const): const HUD_FONT_STACK: "\"Inter Variable\", Inter, \"SF Pro Text\", -apple-system, BlinkMacSystemFont, \"Segoe UI Variable Text\", \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif" — UI sans stack used when no theme supplies `--jg-font-body`.
1465
+ - `HudEase` (type): type HudEase = "ease" | "easeOut" | "spring" — Motion easing.
1466
+ - `HudElevation` (type): type HudElevation = "raised" | "overlay" | "modal" — Shadow stack height.
1467
+ - `HudMotionSpeed` (type): type HudMotionSpeed = "fast" | "base" | "slow" — Motion duration step.
1468
+ - `HudSpaceStep` (type): type HudSpaceStep = "xs" | "sm" | "md" | "lg" | "xl" — Spacing step.
1469
+ - `HudTypeStep` (type): type HudTypeStep = "xs" | "sm" | "md" | "lg" | "xl" | "xxl" | "display" — Inline-style accessors for the `HudTheme` scale tokens (#1034): each returns a `var(--jg-*)` reference with the default theme's value as fallback, so a block reads the active theme when one is applied and still renders the default look outside the shell.
1470
+ - `hudDuration` (function): function hudDuration(speed: HudMotionSpeed = "fast"): string — Duration reference scaled by `--jg-motion-scale` (0 under reduced motion).
1471
+ - `hudEase` (function): function hudEase(ease: HudEase = "ease"): string — Easing reference.
1472
+ - `hudElevation` (function): function hudElevation(level: HudElevation = "raised"): string — `box-shadow` reference for a height, with the lit top rim layered in.
1473
+ - `hudFont` (const): const hudFont: { readonly body: "var(--jg-font-body, \"Inter Variable\", Inter, \"SF Pro Text\", -apple-system, BlinkMacSystemFont, \"Segoe UI Variable Text\", \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif)"; readonly display: "var(--jg-font-display, \"Inter Variable\", Inter, \"SF Pro… — Font-family references for each theme role.
1474
+ - `hudSpace` (function): function hudSpace(step: HudSpaceStep): string — Spacing reference for a step, e.g. `padding: hudSpace("sm")`.
1475
+ - `hudTransition` (function): function hudTransition(properties: string | readonly string[], speed: HudMotionSpeed = "fast", ease: HudEase = "ease"): string — `transition` value for the given properties at one speed and easing, honouring reduced motion.
1476
+ - `hudType` (function): function hudType(step: HudTypeStep): string — Font-size reference for a type-scale step, e.g. `fontSize: hudType("xs")`.
1477
+
1403
1478
  ## @jgengine/react/hudViewport
1404
1479
 
1405
1480
  - `HudViewportContextValue` (interface): interface HudViewportContextValue { fitEnabled: boolean; config: HudViewportConfig | undefined; userScale: number } — ⚠ undocumented
@@ -1525,17 +1600,25 @@
1525
1600
 
1526
1601
  ## @jgengine/react/modals
1527
1602
 
1528
- - `ConfirmDialog` (function): function ConfirmDialog({ title, body, confirmLabel = "Confirm", cancelLabel = "Cancel", onConfirm, onCancel, danger = false, width = 360, className, style, }: ConfirmDialogProps): ReactNode — A generic two-button confirmation dialog — title, optional body, and Cancel / Confirm buttons over a `HudTheme`-token-driven {@link HudFrame}. Presentation-only: wire `onConfirm`/`onCancel` to a modal stack's `resolve` (typically inside a {@link ModalHost} render callback) and reskin via `HudTheme` tokens or `danger` for a destructive action. It interprets nothing — the game supplies the copy.
1603
+ - `ConfirmDialog` (function): function ConfirmDialog({ title, body, confirmLabel = "Confirm", cancelLabel = "Cancel", onConfirm, onCancel, danger = false, width = 360, className, style, }: ConfirmDialogProps): ReactNode — A generic two-button confirmation dialog — title, optional body, and Cancel / Confirm buttons over a `HudTheme`-token-driven {@link HudPanel}. Presentation-only: wire `onConfirm`/`onCancel` to a modal stack's `resolve` (typically inside a {@link ModalHost} render callback) and reskin via `HudTheme` tokens or `danger` for a destructive action. It interprets nothing — the game supplies the copy.
1529
1604
  - `ConfirmDialogProps` (interface): interface ConfirmDialogProps — Props for {@link ConfirmDialog}.
1530
1605
  - `ModalControls` (interface): interface ModalControls — Stack-bound controls handed to a {@link ModalHost} render callback.
1531
- - `ModalHost` (function): function ModalHost({ stack, children, showBackdrop = true, backdrop, closeOnBackdrop = true, closeOnEsc = true, zIndexBase = 80, className, style, }: ModalHostProps): ReactNode — Full-screen modal host: watches a stack, renders only the topmost modal centered over a dimmed backdrop, and manages accessibility — the panel is a `role="dialog" aria-modal`, focus moves into it on open and is trapped with Tab/Shift+Tab cycling, Escape and (optionally) a backdrop click cancel the top modal, and focus returns to the previously focused element on close. It renders nothing when the stack is empty. Skin the panel and backdrop via `HudTheme` tokens.
1606
+ - `ModalHost` (function): function ModalHost({ stack, children, showBackdrop = true, backdrop, closeOnBackdrop = true, closeOnEsc = true, zIndexBase = 80, onInteract, className, style, }: ModalHostProps): ReactNode — Full-screen modal host: watches a stack, renders only the topmost modal centered over a dimmed backdrop, and manages accessibility — the panel is a `role="dialog" aria-modal`, focus moves into it on open and is trapped with Tab/Shift+Tab cycling, Escape and (optionally) a backdrop click cancel the top modal, and focus returns to the previously focused element on close. It renders nothing when the stack is empty. Skin the panel and backdrop via `HudTheme` tokens.
1532
1607
  - `ModalHostProps` (interface): interface ModalHostProps — Props for {@link ModalHost}.
1533
1608
  - `ModalStackView` (interface): interface ModalStackView — React chrome over the headless modal stack (`@jgengine/core/ui/modalStack`) — the blocking-overlay layer a game raises over its scene: a pause menu, a "quit to menu?" confirm, a reward popup. {@link useModalStack} is the DATA/HOOK layer (subscribe + the active record), {@link ModalHost} renders the top modal with a backdrop, focus trap, and Esc-to-cancel, and {@link ConfirmDialog} / {@link PauseMenu} are ready-to-wire, HudTheme-token-driven building blocks. All unskinned and game-owned: the model never interprets a modal's `kind`, so a game switches on it to pick which of these (or its own) content to show, and reskins everything via `HudTheme` tokens and the frame.
1534
- - `PauseMenu` (function): function PauseMenu({ title = "Paused", onResume, resumeLabel = "Resume", items = [], width = 300, className, style, }: PauseMenuProps): ReactNode — A reskinnable pause-menu building block: a primary Resume button plus a game-filled list of slot items (Settings, Save, Quit, …), stacked in a `HudTheme`-token-driven {@link HudFrame}. It ships the common shape every game's pause screen needs and nothing genre-specific — the game supplies the title, item labels/order, and destructive styling, and reskins with `HudTheme` tokens. Drop it into a {@link ModalHost} keyed on a `"pause"` modal, or render it standalone.
1609
+ - `PauseMenu` (function): function PauseMenu({ title = "Paused", onResume, resumeLabel = "Resume", items = [], width = 300, className, style, }: PauseMenuProps): ReactNode — A reskinnable pause-menu building block: a primary Resume button plus a game-filled list of slot items (Settings, Save, Quit, …), stacked in a `HudTheme`-token-driven {@link HudPanel}. It ships the common shape every game's pause screen needs and nothing genre-specific — the game supplies the title, item labels/order, and destructive styling, and reskins with `HudTheme` tokens. Drop it into a {@link ModalHost} keyed on a `"pause"` modal, or render it standalone.
1535
1610
  - `PauseMenuItem` (interface): interface PauseMenuItem — One selectable row in a {@link PauseMenu}'s slot list (Settings, Save, Quit, …).
1536
1611
  - `PauseMenuProps` (interface): interface PauseMenuProps — Props for {@link PauseMenu}.
1537
1612
  - `useModalStack` (function): function useModalStack(stack: ModalStack): ModalStackView — Subscribe to a modal stack and re-render on every change, returning the active modal and depth.
1538
1613
 
1614
+ ## @jgengine/react/notices
1615
+
1616
+ - `Notice` (interface): interface Notice — A live notice as the HUD renders it.
1617
+ - `NoticePlacement` (type): type NoticePlacement = "top" | "center" | "bottom" — Where {@link NoticeStack} sits: below top-edge HUD bars, over the play area, or above bottom action bars.
1618
+ - `NoticeStack` (function): function NoticeStack({ feed, placement = "top", scale = 1, renderNotice, }: { /** Defaults to the surrounding game's `ctx.game.notices`. */ feed?: NoticeFeed; placement?: NoticePlacement; /** Multiplies text and spacing (the shell passes the player's UI scale). */ scale?: number; renderNotice?: (not… — Animated, toned notice stack for a {@link NoticeFeed} — the default renderer the shell mounts for `ctx.game.notices`. Uses `--jg-*` theme vars with fallbacks, 14px text, and never takes pointer input.
1619
+ - `NoticeTone` (type): type NoticeTone = "info" | "success" | "warn" | "deny" — How a notice reads: neutral info, a win, a caution, or a refused action.
1620
+ - `useNoticeList` (function): function useNoticeList(feed: NoticeFeed | null): readonly Notice[] — Live notices of `feed`, re-rendering on change and pruning each one when it expires.
1621
+
1539
1622
  ## @jgengine/react/notifications
1540
1623
 
1541
1624
  - `NotificationBell` (function): function NotificationBell({ store, onClick, icon = "🔔", className, style }: NotificationBellProps): ReactNode — Notification bell — an icon button with an unread-count badge, bound to a notification store. Toggle a {@link NotificationCenter} panel from its click.
@@ -1553,11 +1636,11 @@
1553
1636
 
1554
1637
  ## @jgengine/react/panels
1555
1638
 
1556
- - `PanelHost` (function): function PanelHost({ manager, render, panels, variation, shape, width, zIndexBase = 40, className, windowClassName, windowStyle, bodyStyle, }: { manager: PanelsManager; /** Content for a panel by id. Takes precedence over `panels`. */ render?: (id: string) => ReactNode; /** Static content map by pan… — Renders the open panels of a {@link usePanels} manager as absolutely-positioned, draggable, closable windows in z-order — the WoW window layer. Each window uses {@link HudFrame} chrome, is dragged by its title bar, raises focus on pointer-down, and gets its content from the `render` prop (or a `panels` map). Accessible: every window is a `role="dialog"` with an `aria-label` and a focusable close button; ESC-close is wired by the manager. Unskinned — art-direct via `HudTheme` tokens and the `variation`.
1639
+ - `PanelHost` (function): function PanelHost({ manager, render, panels, variation, shape, width, zIndexBase = 40, className, windowClassName, windowStyle, bodyStyle, onInteract, }: { manager: PanelsManager; /** Called as windows open and close (`cue` is `panel:<id>`) — the seam a UI sound system hooks. */ onInteract?: HudInt… — Renders the open panels of a {@link usePanels} manager as absolutely-positioned, draggable, closable windows in z-order — the WoW window layer. Each window uses {@link HudFrame} chrome, is dragged by its title bar, raises focus on pointer-down, and gets its content from the `render` prop (or a `panels` map). Accessible: every window is a `role="dialog"` with an `aria-label` and a focusable close button; ESC-close is wired by the manager. Unskinned — art-direct via `HudTheme` tokens and the `variation`.
1557
1640
  - `PanelKeyResult` (interface): interface PanelKeyResult — React chrome over the headless panel/window model (`@jgengine/core/ui/panelModel`) — the toggleable, draggable, z-stacked windows a WoW-style UI is made of (B for bag, C for character, ESC to close). `usePanels` is the DATA/HOOK layer (open/close/toggle/focus + a keybind listener), `PanelHost` renders the open windows in z-order, and `Window` is a standalone one-off window. All unskinned and token-driven: games reskin via `HudTheme` tokens and by swapping the `HudFrame` variation.
1558
1641
  - `PanelsManager` (interface): interface PanelsManager — The live panel manager returned by {@link usePanels} and consumed by {@link PanelHost}.
1559
1642
  - `UsePanelsOptions` (interface): interface UsePanelsOptions — Options for {@link usePanels}.
1560
- - `Window` (function): function Window({ title, ariaLabel, closable = true, onClose, x, y, onMove, z, width, variation, shape, className, style, bodyStyle, children, }: { title: ReactNode; ariaLabel?: string; closable?: boolean; onClose?: () => void; /** Initial (uncontrolled) or current (controlled, with `onMove`) x posi… — A single standalone window — title bar, close button, and title-bar drag over {@link HudFrame} chrome — usable without the {@link usePanels} manager for a one-off dialog. Position is uncontrolled (seeded by `x`/`y`) unless both `x`/`y` and `onMove` are supplied, in which case the caller owns placement. Accessible: `role="dialog"` with an `aria-label` and a focusable close button.
1643
+ - `Window` (function): function Window({ title, ariaLabel, closable = true, onClose, x, y, onMove, z, width, variation, shape, className, style, bodyStyle, cue, onInteract, children, }: { title: ReactNode; ariaLabel?: string; closable?: boolean; /** Feedback cue name, rendered as `data-jg-cue` and passed to `onInteract`. … — A single standalone window — title bar, close button, and title-bar drag over {@link HudFrame} chrome — usable without the {@link usePanels} manager for a one-off dialog. Position is uncontrolled (seeded by `x`/`y`) unless both `x`/`y` and `onMove` are supplied, in which case the caller owns placement. Accessible: `role="dialog"` with an `aria-label` and a focusable close button.
1561
1644
  - `panelKeyAction` (function): function panelKeyAction(defs: readonly PanelDef[], state: PanelState, event: { code?: string; key?: string }): PanelKeyResult — Pure keybind resolver for a panel set: ESC resolves to `closeTop` when a closable window is open, and any other key routes through {@link panelByHotkey} (trying `code` then `key`) to a `toggle`. Returns the intent without touching the DOM, so it unit-tests headless and `usePanels` is a thin shell over it.
1562
1645
  - `usePanels` (function): function usePanels(defs: readonly PanelDef[], options?: UsePanelsOptions): PanelsManager — The DATA/HOOK layer: wrap the core panel model in state and install a window keybind listener — per-panel hotkeys toggle their window and ESC closes the topmost closable one. Hotkeys are ignored while the player is typing in an input/textarea/contenteditable. The listener is cleaned up on unmount and SSR-safe (armed in an effect). Feed the returned manager to {@link PanelHost}, or drive a bespoke window layout from it directly.
1563
1646
 
@@ -1567,6 +1650,11 @@
1567
1650
  - `PhotoModeControlsProps` (interface): interface PhotoModeControlsProps — Props for {@link PhotoModeControls}.
1568
1651
  - `usePhotoMode` (function): function usePhotoMode(store: PhotoModeStore): PhotoModeState — Subscribe to a photo-mode store's live state.
1569
1652
 
1653
+ ## @jgengine/react/presence
1654
+
1655
+ - `PresenceState` (type): type PresenceState = "open" | "closed" | "unmounted" — Mount state for an element with an exit transition: `closed` keeps it mounted until the exit ends.
1656
+ - `usePresence` (function): function usePresence(open: boolean, exitMs: number): PresenceState — Keeps an element mounted for `exitMs` after `open` turns false, so a `data-state="closed"` exit animation can play. Returns `"unmounted"` once the exit has finished (or at once when `exitMs` is 0).
1657
+
1570
1658
  ## @jgengine/react/preview
1571
1659
 
1572
1660
  - `GamePreviewComponent` (type): type GamePreviewComponent = ComponentType<GamePreviewProps> — ⚠ undocumented
@@ -1749,6 +1837,23 @@
1749
1837
  - `formatTimerMs` (function): function formatTimerMs(ms: number, format: TimerFormat = "mm:ss"): string — Format a millisecond duration as digital timer text — `mm:ss` (default), `m:ss.d` (with tenths), or `ss.d` (seconds + tenths). Negative inputs clamp to zero. No genre meaning: the same helper serves a round clock, a respawn countdown, or an ability charge readout.
1750
1838
  - `useTimerRead` (function): function useTimerRead(timer: TimerSet, id: string, active = true): TimerRead | null — Subscribe to a single timer and re-read it every animation frame while mounted, so a HUD readout stays live without the game hand-rolling interval math. Reuses one read object (allocation-aware). Returns `null` for an unknown id. Pass `active={false}` to freeze the per-frame tick (e.g. an off-screen HUD).
1751
1839
 
1840
+ ## @jgengine/react/tweenNumber
1841
+
1842
+ - `TweenDelta` (type): type TweenDelta = "up" | "down" — Direction of the last change, for the delta flash.
1843
+ - `TweenNumber` (function): function TweenNumber({ value, format = defaultFormat, flash = true, durationMs, ease, className, style }: TweenNumberProps): ReactNode — A counter that rolls to each new value and flashes the `--jg-success` / `--jg-danger` color with a small scale pop on the change (`data-jg-delta="up" | "down"`). Tabular digits keep its width stable.
1844
+ - `TweenNumberProps` (interface): interface TweenNumberProps extends TweenOptions — Props for {@link TweenNumber}.
1845
+ - `TweenOptions` (interface): interface TweenOptions — Options for {@link useTweenedValue}.
1846
+ - `easeOutCubic` (function): function easeOutCubic(t: number): number — Ease-out cubic, the default counter curve.
1847
+ - `tweenAt` (function): function tweenAt(from: number, to: number, t: number, ease: (t: number) => number = easeOutCubic): number — Value at progress `t` (0–1) between `from` and `to` under `ease`.
1848
+ - `useTweenedValue` (function): function useTweenedValue(target: number, options: TweenOptions = {}): number — Animates toward `target` whenever it changes, starting from the currently displayed value, so a counter rolls instead of jumping. Snaps immediately under reduced motion or a non-finite target.
1849
+
1850
+ ## @jgengine/react/uiCues
1851
+
1852
+ - `UiCue` (type): type UiCue = "click" | "hover" | "open" | "close" | "confirm" | "deny" | "notify" — Interface sound cues every game gets: press, hover, panel open/close, confirm, refusal, notification.
1853
+ - `playUiCue` (function): function playUiCue(ctx: Pick<GameContext, "game"> | null, cue: UiCue): void — Play UI cue `cue` through the game's audio seam (`ui.<cue>` in the shell catalog). No-op outside a game.
1854
+ - `uiCueForInteraction` (function): function uiCueForInteraction(event: HudInteractEvent): UiCue | null — The UI cue an engine HUD primitive interaction plays, or `null` for none: activating plays the primitive's `cue` when it names a {@link UiCue} (e.g. `cue="confirm"`), else `click`; hovering a button or action slot plays `hover`; panels and windows play `open`/`close`. Press and focus are silent.
1855
+ - `useUiCue` (function): function useUiCue(): (cue: UiCue) => void — Returns a stable `cue(name)` that plays a UI sound: click, hover, open, close, confirm, deny, notify. The shell ships soft procedural defaults on the "ui" bus (volume in Settings, muted with master); a game replaces one by declaring `audio.sounds["ui.click"]` or `audio.uiCues`. Safe outside a GameProvider.
1856
+
1752
1857
  ## @jgengine/react/useDebouncedCommit
1753
1858
 
1754
1859
  - `DebounceTimer` (interface): interface DebounceTimer — Injectable timer seam so the controller is testable with a manual clock.
@@ -1812,7 +1917,7 @@
1812
1917
 
1813
1918
  ## @jgengine/shell/audio/AudioComponents
1814
1919
 
1815
- - `AudioListener` (function): function AudioListener({ engine }: { engine: AudioEngine }): null — ⚠ undocumented
1920
+ - `AudioListener` (function): function AudioListener({ engine, anchor = "camera" }: { engine: AudioEngine; anchor?: ListenerAnchor }): null — ⚠ undocumented
1816
1921
  - `EntityAudioEmitters` (function): function EntityAudioEmitters({ engine, entitySounds, }: { engine: AudioEngine; entitySounds: Record<string, string> | undefined; }): null — ⚠ undocumented
1817
1922
  - `ObjectAudioEmitters` (function): function ObjectAudioEmitters({ engine, objectSounds, }: { engine: AudioEngine; objectSounds: Record<string, string> | undefined; }): null — ⚠ undocumented
1818
1923
 
@@ -2092,6 +2197,10 @@
2092
2197
 
2093
2198
  - `readStoredOverrides` (function): function readStoredOverrides(gameName: string): DevtoolsOverrides | null — ⚠ undocumented
2094
2199
 
2200
+ ## @jgengine/shell/devtools/devtoolsReports
2201
+
2202
+ - `DebugSummary` (interface): interface DebugSummary — Compact `debug_summary` payload: the numbers an agent needs to judge a run, about 1 KB.
2203
+
2095
2204
  ## @jgengine/shell/devtools/panelAtoms
2096
2205
 
2097
2206
  - `SectionLabel` (function): function SectionLabel({ children }: { children: string }): React.JSX.Element — ⚠ undocumented
@@ -2182,7 +2291,7 @@
2182
2291
 
2183
2292
  - `AuthoredRoadNetwork` (interface): interface AuthoredRoadNetwork — Trimmed ribbons for every road plus the welded surfaces that fill the crossings between them.
2184
2293
  - `JunctionSurface` (interface): interface JunctionSurface — One welded asphalt junction surface plus the color it should render in.
2185
- - `RoadRibbons` (function): function RoadRibbons({ road, field, paths, }: { road: RoadEnvironmentDescriptor; field: TerrainField; paths: readonly (readonly RoadPoint[])[]; }): React.JSX.Element | null — Renders one `road()` descriptor from its trimmed centerline sub-paths (cut back at every junction by {@link buildAuthoredRoadNetwork}): an asphalt ribbon, a curb/edge strip along each border, an optional dashed centerline, and optional sidewalks. Because the sub-paths already stop at the junction boundary, every layer naturally ends there — no ribbon ploughs through the crossing and the dashed line no longer needs exclusion circles to skip intersections.
2294
+ - `RoadRibbons` (function): function RoadRibbons({ road, field, paths, }: { road: RoadEnvironmentDescriptor; field: TerrainField; paths: readonly (readonly RoadPoint[])[]; }): React.JSX.Element | null — Renders one `road()` descriptor from its trimmed centerline sub-paths (cut back at every junction by {@link buildAuthoredRoadNetwork}): an asphalt ribbon, a curb/edge strip along each border, an optional dashed centerline, and optional sidewalks. Because the sub-paths already stop at the junction boundary, every layer naturally ends there — no ribbon ploughs through the crossing and the dashed line no longer needs exclusion circles to skip intersections. The carriageway, curbs and sidewalks shade with the terrain's procedural surfaces (`road.surface`, default asphalt with tyre wear); `surface: false` keeps them flat.
2186
2295
 
2187
2296
  ## @jgengine/shell/environment/VolumetricClouds
2188
2297
 
@@ -2334,7 +2443,7 @@
2334
2443
  ## @jgengine/shell/registry
2335
2444
 
2336
2445
  - `GameRegistry` (type): type GameRegistry = Record<string, () => Promise<PlayableGame>> — ⚠ undocumented
2337
- - `PlayableGame` (type): type PlayableGame = EnginePlayableGame< ComponentType, ComponentType, RenderEntity, RenderObject, ComponentType<ViewmodelProps>, ComponentType<WorldOverlayProps> > — The concrete React-bound playable a shell `defineGame` returns and `GameHost` mounts — the one PlayableGame games handle.
2446
+ - `PlayableGame` (type): type PlayableGame = EnginePlayableGame< ComponentType, ComponentType, RenderEntity, RenderObject, ComponentType<ViewmodelProps>, ComponentType<WorldOverlayProps> > & { /** HUD theme the shell applies at the `GameUI` root. Unset uses `defaultHudTheme`; `false` opts out of the default theme and the en… — The concrete React-bound playable a shell `defineGame` returns and `GameHost` mounts — the one PlayableGame games handle.
2338
2447
  - `RenderEntity` (type): type RenderEntity = (entity: SceneEntity) => ReactNode — ⚠ undocumented
2339
2448
  - `RenderObject` (type): type RenderObject = (object: SceneObject) => ReactNode — ⚠ undocumented
2340
2449
  - `resolveGameLoader` (function): function resolveGameLoader(registry: GameRegistry, gameId: string, fallbackGameId?: string): (() => Promise<PlayableGame>) | undefined — ⚠ undocumented
@@ -2443,6 +2552,7 @@
2443
2552
  - `heavyModels` (function): function heavyModels(): readonly HeavyModelRecord[] — Every model the shared loader resolved whose triangle count exceeds the budget, in load order. A heavy prop is invisible in a screenshot and only shows up as a slow frame, so the loader measures each GLB as it lands and this exposes the verdict for `debug_snapshot` probes and smoke tests to assert against.
2444
2553
  - `modelLoadFallbacks` (function): function modelLoadFallbacks(): readonly { url: string; message: string }[] — Every model the shared loader diagnosed as broken and replaced with a magenta placeholder, in load order.
2445
2554
  - `modelLoadIdleMs` (function): function modelLoadIdleMs(): number — How long the shared GLB loader has been idle, in ms — `0` while any model is still in flight. A capture host reads this to wait for streaming to finish instead of guessing a settle delay: models that pop in after the shot are why an establishing capture used to come back half-empty until someone hand-tuned `--settle`.
2555
+ - `modelLoadStarts` (function): function modelLoadStarts(): number — How many times the shared GLB loader has gone from idle to busy since boot. A capture host compares two readings around a rendered frame to tell whether that frame started new model streaming, which an idle reading alone cannot show.
2446
2556
  - `setModelTriangleBudget` (function): function setModelTriangleBudget(triangles: number): void — Retune the per-model triangle budget (a cinematic game may raise it; a mobile target lowers it). Non-finite or non-positive values restore the default.
2447
2557
 
2448
2558
  ## @jgengine/shell/render/modelRender
@@ -2451,7 +2561,7 @@
2451
2561
  - `ModelShadowMode` (type): type ModelShadowMode = "cast" | "receive" | "both" | "none" — Shadow participation applied to every mesh of a cloned model; mirrors `ModelConfig.shadows`.
2452
2562
  - `PAINT_TEXTURE_SIZE` (const): const PAINT_TEXTURE_SIZE: 512 — ⚠ undocumented
2453
2563
  - `PaintCanvas` (interface): interface PaintCanvas { canvas: HTMLCanvasElement; context: CanvasRenderingContext2D; texture: THREE.CanvasTexture } — ⚠ undocumented
2454
- - `cloneModelScene` (function): function cloneModelScene(source: THREE.Object3D, options?: { cloneMaterials?: boolean; shadows?: ModelShadowMode }): THREE.Object3D — Clone a model with independent pose and materials, retaining shared geometry and textures.
2564
+ - `cloneModelScene` (function): function cloneModelScene(source: THREE.Object3D, options?: { cloneMaterials?: boolean; shadows?: ModelShadowMode; surfaceDetail?: boolean }): THREE.Object3D — Clone a model with independent pose and materials, retaining shared geometry and textures.
2455
2565
  - `disposeModelScene` (function): function disposeModelScene(root: THREE.Object3D): void — Release materials and bone textures owned by `cloneModelScene`; shared assets and attached models remain owned by their callers.
2456
2566
 
2457
2567
  ## @jgengine/shell/render/projectedOverdraw
@@ -2518,7 +2628,7 @@
2518
2628
  ## @jgengine/shell/render/useModelInstance
2519
2629
 
2520
2630
  - `ModelInstance` (interface): interface ModelInstance — Render `scene` with `position` and uniform `scale`; animate or deform `content` inside that placement frame.
2521
- - `ModelInstanceConfig` (type): type ModelInstanceConfig = Pick<ModelConfig, "url" | "scale" | "targetHeight" | "y" | "anchor" | "dims" | "shadows" | "animation" | "hiddenNodes" > — Model loading, placement, shadows and animation for a custom renderer.
2631
+ - `ModelInstanceConfig` (type): type ModelInstanceConfig = Pick<ModelConfig, "url" | "scale" | "targetHeight" | "y" | "anchor" | "dims" | "shadows" | "animation" | "hiddenNodes" | "surfaceDetail" > — Model loading, placement, shadows and animation for a custom renderer.
2522
2632
  - `ModelInstanceOptions` (interface): interface ModelInstanceOptions — Caller-owned styling runs on the isolated clone, never the loader cache.
2523
2633
  - `useModelInstance` (function): function useModelInstance(model: ModelInstanceConfig, options: ModelInstanceOptions = {}): ModelInstance — Load and own an isolated model instance, preserving imported transforms and measuring placement at bind pose. Apply hiddenNodes to the configured clone before measurement; equivalent name lists retain the instance. Mount `scene` with the returned position/scale. Custom materials, deformation and surrounding geometry stay caller-owned. Must run inside an R3F canvas and Suspense boundary; a game provider is optional.
2524
2634
 
@@ -2545,9 +2655,9 @@
2545
2655
 
2546
2656
  - `AuthoredObjects` (function): function AuthoredObjects({ document, field, verticalOffset = 0, onExisting = "keep", synchronize = false, excludeKinds, }: AuthoredObjectsProps): null — Places every editor marker with a catalog id into `ctx.scene.object`, grounded on `field`. WorldScene renders them through the game's `objectModels` seam — no bespoke place loop. Pure headless twin: `resolveAuthoredObjects` / `placeAuthoredObjects`.
2547
2657
  - `AuthoredObjectsProps` (interface): interface AuthoredObjectsProps extends ResolveAuthoredObjectsOptions — Props for {@link AuthoredObjects}: document, ground field, and optional lift / onExisting.
2548
- - `AuthoredPaths` (function): function AuthoredPaths({ document, field, kinds }: AuthoredPathsProps): React.JSX.Element — Renders a document's non-scatter paths (roads, routes, corridors) as ground-draped ribbons — the editor authors the polyline, the engine drapes it over the live terrain at runtime. Width comes from `path.width`, color from `path.meta.color`/`path.color`. A game never hand-rolls path meshes.
2658
+ - `AuthoredPaths` (function): function AuthoredPaths({ document, field, kinds, surfaceDetail = true }: AuthoredPathsProps): React.JSX.Element — Renders a document's non-scatter paths (roads, routes, corridors) as ground-draped ribbons — the editor authors the polyline, the engine drapes it over the live terrain at runtime. Width comes from `path.width`, color from `path.meta.color`/`path.color`, and the procedural surface from `path.meta.surface` or the kind (see `resolvePathSurface`). A game never hand-rolls path meshes.
2549
2659
  - `AuthoredPathsProps` (interface): interface AuthoredPathsProps — Props for {@link AuthoredPaths}: the document, the ground field to drape over, and a kind filter.
2550
- - `AuthoredScene` (function): function AuthoredScene({ document, diagnostics = false, field, pathKinds, scatterModels, flockModels, assets, live = true, placeObjects, groundColorAt, }: AuthoredSceneProps): React.JSX.Element — Renders an editor document's scene content — draped paths plus GPU-instanced foliage — from one mount, grounded on the live `field`. The runtime counterpart to authoring a scene in the editor: drag paths and foliage regions, save `editor.scene.json`, and the game plays them with no bespoke render code. When a live-sync bus is installed (editor host), document patches stream in and re-render automatically — document is authoritative; runtime overrides stay ephemeral unless written back. Terrain/collision come from the world's ground field (`environment({ sculpt })`); place markers with your own entity spawns. Pass `scatterModels`+`assets` to resolve palette items to real catalog GLBs; unmapped items keep the stylized proxy.
2660
+ - `AuthoredScene` (function): function AuthoredScene({ document, diagnostics = false, field, pathKinds, pathSurfaceDetail = true, scatterModels, flockModels, assets, live = true, placeObjects, groundColorAt, }: AuthoredSceneProps): React.JSX.Element — Renders an editor document's scene content — draped paths plus GPU-instanced foliage — from one mount, grounded on the live `field`. The runtime counterpart to authoring a scene in the editor: drag paths and foliage regions, save `editor.scene.json`, and the game plays them with no bespoke render code. When a live-sync bus is installed (editor host), document patches stream in and re-render automatically — document is authoritative; runtime overrides stay ephemeral unless written back. Terrain/collision come from the world's ground field (`environment({ sculpt })`); place markers with your own entity spawns. Pass `scatterModels`+`assets` to resolve palette items to real catalog GLBs; unmapped items keep the stylized proxy.
2551
2661
  - `AuthoredSceneProps` (interface): interface AuthoredSceneProps — Props for {@link AuthoredScene}: the document to render and the ground field to drape/ground on.
2552
2662
  - `AuthoredSolids` (function): function AuthoredSolids({ document, field }: { document: EditorDocument; field: TerrainField }): null — Writes the document's studio solids (city buildings and any kind with a `solids` hook) into `ctx.world.solids`, so what the studios draw also blocks the player, NPCs and physics.
2553
2663
 
@@ -2555,9 +2665,9 @@
2555
2665
 
2556
2666
  - `AuthoredObjects` (function): function AuthoredObjects({ document, field, verticalOffset = 0, onExisting = "keep", synchronize = false, excludeKinds, }: AuthoredObjectsProps): null — Places every editor marker with a catalog id into `ctx.scene.object`, grounded on `field`. WorldScene renders them through the game's `objectModels` seam — no bespoke place loop. Pure headless twin: `resolveAuthoredObjects` / `placeAuthoredObjects`.
2557
2667
  - `AuthoredObjectsProps` (interface): interface AuthoredObjectsProps extends ResolveAuthoredObjectsOptions — Props for {@link AuthoredObjects}: document, ground field, and optional lift / onExisting.
2558
- - `AuthoredPaths` (function): function AuthoredPaths({ document, field, kinds }: AuthoredPathsProps): React.JSX.Element — Renders a document's non-scatter paths (roads, routes, corridors) as ground-draped ribbons — the editor authors the polyline, the engine drapes it over the live terrain at runtime. Width comes from `path.width`, color from `path.meta.color`/`path.color`. A game never hand-rolls path meshes.
2668
+ - `AuthoredPaths` (function): function AuthoredPaths({ document, field, kinds, surfaceDetail = true }: AuthoredPathsProps): React.JSX.Element — Renders a document's non-scatter paths (roads, routes, corridors) as ground-draped ribbons — the editor authors the polyline, the engine drapes it over the live terrain at runtime. Width comes from `path.width`, color from `path.meta.color`/`path.color`, and the procedural surface from `path.meta.surface` or the kind (see `resolvePathSurface`). A game never hand-rolls path meshes.
2559
2669
  - `AuthoredPathsProps` (interface): interface AuthoredPathsProps — Props for {@link AuthoredPaths}: the document, the ground field to drape over, and a kind filter.
2560
- - `AuthoredScene` (function): function AuthoredScene({ document, diagnostics = false, field, pathKinds, scatterModels, flockModels, assets, live = true, placeObjects, groundColorAt, }: AuthoredSceneProps): React.JSX.Element — Renders an editor document's scene content — draped paths plus GPU-instanced foliage — from one mount, grounded on the live `field`. The runtime counterpart to authoring a scene in the editor: drag paths and foliage regions, save `editor.scene.json`, and the game plays them with no bespoke render code. When a live-sync bus is installed (editor host), document patches stream in and re-render automatically — document is authoritative; runtime overrides stay ephemeral unless written back. Terrain/collision come from the world's ground field (`environment({ sculpt })`); place markers with your own entity spawns. Pass `scatterModels`+`assets` to resolve palette items to real catalog GLBs; unmapped items keep the stylized proxy.
2670
+ - `AuthoredScene` (function): function AuthoredScene({ document, diagnostics = false, field, pathKinds, pathSurfaceDetail = true, scatterModels, flockModels, assets, live = true, placeObjects, groundColorAt, }: AuthoredSceneProps): React.JSX.Element — Renders an editor document's scene content — draped paths plus GPU-instanced foliage — from one mount, grounded on the live `field`. The runtime counterpart to authoring a scene in the editor: drag paths and foliage regions, save `editor.scene.json`, and the game plays them with no bespoke render code. When a live-sync bus is installed (editor host), document patches stream in and re-render automatically — document is authoritative; runtime overrides stay ephemeral unless written back. Terrain/collision come from the world's ground field (`environment({ sculpt })`); place markers with your own entity spawns. Pass `scatterModels`+`assets` to resolve palette items to real catalog GLBs; unmapped items keep the stylized proxy.
2561
2671
  - `AuthoredSceneProps` (interface): interface AuthoredSceneProps — Props for {@link AuthoredScene}: the document to render and the ground field to drape/ground on.
2562
2672
  - `AuthoredSolids` (function): function AuthoredSolids({ document, field }: { document: EditorDocument; field: TerrainField }): null — Writes the document's studio solids (city buildings and any kind with a `solids` hook) into `ctx.world.solids`, so what the studios draw also blocks the player, NPCs and physics.
2563
2673
 
@@ -2702,6 +2812,7 @@
2702
2812
 
2703
2813
  ## @jgengine/shell/terrain
2704
2814
 
2815
+ - `ALL_TERRAIN_SURFACE_CLASSES` (const): const ALL_TERRAIN_SURFACE_CLASSES: 511 — Compile every surface response, for an editor viewport whose paint can change to any surface.
2705
2816
  - `CarvedTerrain` (function): function CarvedTerrain({ field, size, segments, center, colors, heightRange, paletteAt, paintColorAt, roughness = 0.95, metalness = 0, surfaceMaterial, receiveShadow = true, epoch = 0, ...meshProps }: CarvedTerrainProps): React.JSX.Element — Renders a `TerrainField` as a deformed ground mesh — the crater/mound view for destructible terrain. Because the geometry samples `field.sampleHeight`, a `CarvableField.carve(...)` shows as a real bowl once `epoch` changes. Pair with `InstancedBodies` to see debris resting in the crater it blasted.
2706
2817
  - `CarvedTerrainProps` (interface): interface CarvedTerrainProps extends Omit<ThreeElements["mesh"], "args" | "children" | "geometry" | "material"> { field: TerrainField; size?: FieldGroundOptions["size"]; segments?: FieldGroundOptions["segments"]; center?: FieldGroundOptions["center"]; colors?: FieldGroundOptions["colors"]; heightRange?: FieldGroundOptions["heightRange"]; paletteAt?: … — ⚠ undocumented · used by `CarvedTerrain`: Renders a `TerrainField` as a deformed ground mesh — the crater/mound view for destructible terrain.
2707
2818
  - `DEFAULT_GRASS_WIND` (const): const DEFAULT_GRASS_WIND: Required<GrassWindOptions> — ⚠ undocumented
@@ -2720,29 +2831,53 @@
2720
2831
  - `HeightfieldColorFn` (type): type HeightfieldColorFn = (x: number, z: number, height: number, out: THREE.Color) => void — Per-vertex color hook for {@link displaceHeightfieldGeometry}; write the tone into `out`.
2721
2832
  - `HeightfieldDisplaceOptions` (interface): interface HeightfieldDisplaceOptions — Options for {@link displaceHeightfieldGeometry}.
2722
2833
  - `HeightfieldRect` (interface): interface HeightfieldRect — XZ rectangle in world units — matches the shape of a terraform snapshot's `bounds`.
2834
+ - `MAX_TERRAIN_PAINT_LAYERS` (const): const MAX_TERRAIN_PAINT_LAYERS: 8 — At most this many distinct paint layers reach the GPU; extra layers fold into the last slot.
2723
2835
  - `NoiseFieldConfig` (interface): interface NoiseFieldConfig — Configuration for {@link noiseField}: seed, amplitude, and fractal noise shaping.
2724
2836
  - `ProceduralGround` (function): function ProceduralGround({ terrain, colors, roughness = 0.94, metalness = 0, receiveShadow = true, ...meshProps }: ProceduralGroundProps): React.JSX.Element — ⚠ undocumented
2725
2837
  - `ProceduralGroundProps` (interface): interface ProceduralGroundProps extends Omit<ThreeElements["mesh"], "args" | "children" | "geometry" | "material"> { terrain?: ProceduralTerrainConfig; colors?: TerrainVertexColorOptions; roughness?: number; metalness?: number } — ⚠ undocumented
2726
2838
  - `ProceduralTerrainConfig` (interface): interface ProceduralTerrainConfig { size?: TerrainArea; segments?: number | readonly [x: number, z: number]; seed?: TerrainSeed; height?: number; moundScale?: number; octaves?: number; ridged?: boolean; baseOffset?: number } — ⚠ undocumented
2839
+ - `RIBBON_FRAME_ATTRIBUTE` (const): const RIBBON_FRAME_ATTRIBUTE: "jgRibbon" — Vertex attribute {@link ribbonSurfaceFrame} fills: metres along the ribbon, signed metres across it, metres to its far end.
2727
2840
  - `ResolvedGrassBladeGeometryOptions` (interface): interface ResolvedGrassBladeGeometryOptions { count: number; area: TerrainArea; seed: TerrainSeed; segments: number; height: readonly [min: number, max: number]; width: readonly [min: number, max: number]; bend: readonly [min: number, max: number]; tuftBlades: number; tuftRadius: num… — ⚠ undocumented
2728
2841
  - `ResolvedTerrainSegments` (interface): interface ResolvedTerrainSegments { x: number; z: number } — ⚠ undocumented
2729
2842
  - `ResolvedTerrainSize` (interface): interface ResolvedTerrainSize { width: number; depth: number } — ⚠ undocumented
2843
+ - `RibbonSurfaceStyle` (interface): interface RibbonSurfaceStyle — How a ground ribbon (path, road, sidewalk) shades: procedural surface, edge blend and wear.
2844
+ - `SurfaceRibbonMaterialOptions` (interface): interface SurfaceRibbonMaterialOptions extends Partial<RibbonSurfaceStyle> — Options for {@link createSurfaceRibbonMaterial}.
2730
2845
  - `TerraformBrushCursor` (function): function TerraformBrushCursor({ center, y = 0.05, radius, mode }: TerraformBrushCursorProps): React.JSX.Element | null — ⚠ undocumented
2731
2846
  - `TerraformBrushCursorProps` (interface): interface TerraformBrushCursorProps { center: readonly [number, number] | null; y?: number; radius: number; mode: TerraformMode } — ⚠ undocumented
2732
2847
  - `TerrainArea` (type): type TerrainArea = number | readonly [width: number, depth: number] — ⚠ undocumented
2848
+ - `TerrainDetailMaterialHandle` (interface): interface TerrainDetailMaterialHandle — The built procedural detail terrain material, ready to mount on the ground mesh.
2849
+ - `TerrainDetailMaterialOptions` (interface): interface TerrainDetailMaterialOptions — Surface-pass inputs beyond the resolved detail config.
2850
+ - `TerrainDetailMaterialTextures` (interface): interface TerrainDetailMaterialTextures — Loaded PBR textures matching a resolved `ResolvedTerrainDetailMaterial.maps`' roles. The caller (a React component, via `useTexture`/`useLoader`) owns loading and disposal; `core` never touches `THREE.Texture` and this shell function never fetches a URL itself. Omitted AO/roughness skip sampling.
2733
2851
  - `TerrainField` (interface): interface TerrainField — A sampleable ground surface: height and normal at any x/z, with optional bounds and water level.
2734
2852
  - `TerrainHeightSampler` (type): type TerrainHeightSampler = (x: number, z: number) => number — ⚠ undocumented
2735
2853
  - `TerrainNormal` (type): type TerrainNormal = readonly [number, number, number] — A surface normal vector at a terrain sample point.
2736
- - `TerrainPaintColorSampler` (type): type TerrainPaintColorSampler = (x: number, z: number, out: THREE.Color) => boolean — Writes authored paint into an existing linear color; returns false for unpainted ground.
2737
- - `TerrainPaintSource` (type): type TerrainPaintSource = Pick<EditableTerrain, "layers" | "weightsAt" | "surfaceAt"> — Live authored paint channels needed to sample a terrain's material stack without its heightfield.
2854
+ - `TerrainPaintColorSampler` (type): type TerrainPaintColorSampler = (x: number, z: number, out: THREE.Color) => boolean — Blends authored paint over the base ground color already in `out`; returns false (leaving `out` untouched) where no painted cell contributes.
2855
+ - `TerrainPaintSlot` (interface): interface TerrainPaintSlot — One GPU paint slot: authored hue plus the procedural response it renders with.
2856
+ - `TerrainPaintSource` (type): type TerrainPaintSource = Pick<EditableTerrain, "layers" | "weightsAt" | "surfaceAt"> & Partial<Pick<EditableTerrain, "gridBounds" | "cols" | "rows">> — Live authored paint channels needed to sample a terrain's material stack without its heightfield. With the grid fields present, paint is filtered across neighbouring cells instead of snapping to each cell.
2857
+ - `TerrainPaintTextures` (interface): interface TerrainPaintTextures — GPU-side paint coverage shared by the terrain surface material; `update` after each paint stroke.
2858
+ - `TerrainPaintWeights` (interface): interface TerrainPaintWeights — Per-cell paint coverage packed for the shader: two RGBA8 planes holding up to eight layer weights.
2859
+ - `TerrainQualityContext` (const): const TerrainQualityContext: React.Context<GraphicsQuality> — The player's graphics tier as terrain materials see it; the shell provides it, `high` outside one.
2738
2860
  - `TerrainSeed` (type): type TerrainSeed = number | string — ⚠ undocumented
2861
+ - `TerrainSurfaceClass` (type): type TerrainSurfaceClass = "ground" | "grass" | "dirt" | "gravel" | "rock" | "sand" | "mud" | "snow" | "road" — Procedural ground response the terrain shader renders for a painted surface id.
2739
2862
  - `TerrainSurfaceColorOptions` (interface): interface TerrainSurfaceColorOptions — Slope/noise surface shading laid over the height lerp so untextured ground reads like terrain instead of a smooth two-tone gradient. All knobs are optional with sane defaults; every field is a pure function of vertex position (+ `seed`), so the coloring is stable across rebuilds and tests.
2740
2863
  - `TerrainVertexColorOptions` (interface): interface TerrainVertexColorOptions { low?: THREE.ColorRepresentation; high?: THREE.ColorRepresentation; waterline?: THREE.ColorRepresentation; waterlineHeight?: number } — ⚠ undocumented
2741
- - `createTerrainPaintColorSampler` (function): function createTerrainPaintColorSampler(terrain: TerrainPaintSource): TerrainPaintColorSampler — Shares layer tint, opacity and linear weighted-color blending between editor and runtime ground.
2864
+ - `createSurfaceRibbonMaterial` (function): function createSurfaceRibbonMaterial(options: SurfaceRibbonMaterialOptions): THREE.MeshStandardMaterial — A `MeshStandardMaterial` that shades a ground ribbon (path, road, sidewalk) with the terrain's own procedural surface functions, so a dirt track or asphalt strip matches painted ground: the authored colour varied by the surface class's grain, roughness and micro-normal, wheel ruts or tyre lanes, and (with `feather > 0`) a noise-warped edge that fades into the terrain. The geometry needs a {@link RIBBON_FRAME_ATTRIBUTE} attribute from {@link ribbonSurfaceFrame}; build it wider by {@link ribbonEdgeMargin} so the fade straddles the authored edge. The caller disposes the material.
2865
+ - `createTerrainPaintColorSampler` (function): function createTerrainPaintColorSampler(terrain: TerrainPaintSource): TerrainPaintColorSampler — Shares layer tint, opacity and linear weighted-color blending between editor and runtime ground. Paint lives on a coarse cell grid; when the source exposes that grid, each sample smoothly blends the four surrounding cell centres (unpainted cells contribute the caller's base color in `out`), so painted paths and clearings get soft edges instead of grid-aligned stair steps.
2866
+ - `createTerrainPaintTextures` (function): function createTerrainPaintTextures(weights: TerrainPaintWeights): TerrainPaintTextures — Uploads packed paint coverage ({@link packTerrainPaintWeights}) as two linear-filtered RGBA textures plus per-slot hue, surface class, roughness and pattern scale for the terrain surface material.
2742
2867
  - `displaceHeightfieldGeometry` (function): function displaceHeightfieldGeometry(geometry: THREE.BufferGeometry, sampleHeight: (x: number, z: number) => number, options: HeightfieldDisplaceOptions): void — Re-samples a `PlaneGeometry(width, depth, segments, segments).rotateX(-π/2)` heightfield mesh from a `sampleHeight` field in place: vertex Y, optional vertex color, grid central-difference normals, and an analytically maintained bounding sphere. With a dirty `region`, the work is O(region vertices) — heights/colors update inside the covering vertex window, normals inside the window plus a one-vertex ring — never a whole-mesh `computeVertexNormals`/`computeBoundingSphere` pass, which is what keeps per-frame brush stamps inside the editor's frame budget. The bounding sphere derives from the fixed plane extents plus a running height range kept on `geometry.userData`; partial passes only expand it, a full pass resets it exactly.
2868
+ - `packTerrainPaintWeights` (function): function packTerrainPaintWeights(source: TerrainPaintSource, into?: TerrainPaintWeights): TerrainPaintWeights | undefined — Packs a terrain's authored paint (layer stack, blend weights or hard surface ids) into per-cell coverage planes for the terrain surface shader. Returns `undefined` when the source exposes no cell grid. Pass `into` to rewrite an existing buffer in place after a paint stroke.
2869
+ - `resolvePathSurface` (function): function resolvePathSurface(kind: string, width: number, meta: Readonly<Record<string, unknown>> | undefined): RibbonSurfaceStyle | null — Default ribbon look for an authored path: the surface comes from `meta.surface`, else the path kind (`"road"` paves, `"gravel-track"` gravels), else dirt. Soft ground gets a noise-warped edge that fades into the terrain and wheel ruts; pavement keeps a near-hard edge and faint tyre lanes. `meta.surfaceDetail: false` (or `meta.surface: false`) returns `null` for the old flat ribbon; `meta.edgeSoftness` scales the fade (0 = hard) and `meta.wear` sets wear 0..1.
2870
+ - `ribbonEdgeMargin` (function): function ribbonEdgeMargin(style: Pick<RibbonSurfaceStyle, "feather" | "warp">): number — Extra ribbon width (metres, both sides together) a style needs so its fade straddles the authored edge.
2871
+ - `ribbonSurfaceFrame` (function): function ribbonSurfaceFrame(ribbons: readonly Float32Array[]): Float32Array — Per-vertex `(along, across, toEnd)` for a ribbon in `buildRoadRibbon`'s layout (left/right vertex pairs down the centreline). Pass several ribbons' position arrays to get one concatenated frame for a merged mesh; each ribbon's distances restart at its own start.
2872
+ - `terrainSurfaceClass` (function): function terrainSurfaceClass(surface: string): TerrainSurfaceClass — Classifies a painted surface id (e.g. `"grass"`, `"dry-grass"`, `"cobble-stone"`) into the procedural ground response the terrain shader draws for it; unknown ids get the generic `"ground"` response.
2873
+ - `terrainSurfaceClassIndex` (function): function terrainSurfaceClassIndex(cls: TerrainSurfaceClass): number — Shader index of a surface class (`jgSurface`'s `cls`); bit `1 << index` in a class mask.
2874
+ - `terrainSurfaceRoughness` (function): function terrainSurfaceRoughness(cls: TerrainSurfaceClass): number — Default roughness the terrain shades a surface class with.
2875
+ - `useSurfaceRibbonMaterial` (function): function useSurfaceRibbonMaterial(options: SurfaceRibbonMaterialOptions | null): THREE.MeshStandardMaterial | null — {@link createSurfaceRibbonMaterial} for a React mesh: rebuilt only when an option changes and disposed on unmount. Pass `null` to get `null` (the caller falls back to a flat material).
2876
+ - `useTerrainQuality` (function): function useTerrainQuality(): GraphicsQuality — Graphics tier for terrain surface shading (`low` takes the cheap path).
2743
2877
 
2744
2878
  ## @jgengine/shell/terrain
2745
2879
 
2880
+ - `ALL_TERRAIN_SURFACE_CLASSES` (const): const ALL_TERRAIN_SURFACE_CLASSES: 511 — Compile every surface response, for an editor viewport whose paint can change to any surface.
2746
2881
  - `CarvedTerrain` (function): function CarvedTerrain({ field, size, segments, center, colors, heightRange, paletteAt, paintColorAt, roughness = 0.95, metalness = 0, surfaceMaterial, receiveShadow = true, epoch = 0, ...meshProps }: CarvedTerrainProps): React.JSX.Element — Renders a `TerrainField` as a deformed ground mesh — the crater/mound view for destructible terrain. Because the geometry samples `field.sampleHeight`, a `CarvableField.carve(...)` shows as a real bowl once `epoch` changes. Pair with `InstancedBodies` to see debris resting in the crater it blasted.
2747
2882
  - `CarvedTerrainProps` (interface): interface CarvedTerrainProps extends Omit<ThreeElements["mesh"], "args" | "children" | "geometry" | "material"> { field: TerrainField; size?: FieldGroundOptions["size"]; segments?: FieldGroundOptions["segments"]; center?: FieldGroundOptions["center"]; colors?: FieldGroundOptions["colors"]; heightRange?: FieldGroundOptions["heightRange"]; paletteAt?: … — ⚠ undocumented · used by `CarvedTerrain`: Renders a `TerrainField` as a deformed ground mesh — the crater/mound view for destructible terrain.
2748
2883
  - `DEFAULT_GRASS_WIND` (const): const DEFAULT_GRASS_WIND: Required<GrassWindOptions> — ⚠ undocumented
@@ -2761,26 +2896,49 @@
2761
2896
  - `HeightfieldColorFn` (type): type HeightfieldColorFn = (x: number, z: number, height: number, out: THREE.Color) => void — Per-vertex color hook for {@link displaceHeightfieldGeometry}; write the tone into `out`.
2762
2897
  - `HeightfieldDisplaceOptions` (interface): interface HeightfieldDisplaceOptions — Options for {@link displaceHeightfieldGeometry}.
2763
2898
  - `HeightfieldRect` (interface): interface HeightfieldRect — XZ rectangle in world units — matches the shape of a terraform snapshot's `bounds`.
2899
+ - `MAX_TERRAIN_PAINT_LAYERS` (const): const MAX_TERRAIN_PAINT_LAYERS: 8 — At most this many distinct paint layers reach the GPU; extra layers fold into the last slot.
2764
2900
  - `NoiseFieldConfig` (interface): interface NoiseFieldConfig — Configuration for {@link noiseField}: seed, amplitude, and fractal noise shaping.
2765
2901
  - `ProceduralGround` (function): function ProceduralGround({ terrain, colors, roughness = 0.94, metalness = 0, receiveShadow = true, ...meshProps }: ProceduralGroundProps): React.JSX.Element — ⚠ undocumented
2766
2902
  - `ProceduralGroundProps` (interface): interface ProceduralGroundProps extends Omit<ThreeElements["mesh"], "args" | "children" | "geometry" | "material"> { terrain?: ProceduralTerrainConfig; colors?: TerrainVertexColorOptions; roughness?: number; metalness?: number } — ⚠ undocumented
2767
2903
  - `ProceduralTerrainConfig` (interface): interface ProceduralTerrainConfig { size?: TerrainArea; segments?: number | readonly [x: number, z: number]; seed?: TerrainSeed; height?: number; moundScale?: number; octaves?: number; ridged?: boolean; baseOffset?: number } — ⚠ undocumented
2904
+ - `RIBBON_FRAME_ATTRIBUTE` (const): const RIBBON_FRAME_ATTRIBUTE: "jgRibbon" — Vertex attribute {@link ribbonSurfaceFrame} fills: metres along the ribbon, signed metres across it, metres to its far end.
2768
2905
  - `ResolvedGrassBladeGeometryOptions` (interface): interface ResolvedGrassBladeGeometryOptions { count: number; area: TerrainArea; seed: TerrainSeed; segments: number; height: readonly [min: number, max: number]; width: readonly [min: number, max: number]; bend: readonly [min: number, max: number]; tuftBlades: number; tuftRadius: num… — ⚠ undocumented
2769
2906
  - `ResolvedTerrainSegments` (interface): interface ResolvedTerrainSegments { x: number; z: number } — ⚠ undocumented
2770
2907
  - `ResolvedTerrainSize` (interface): interface ResolvedTerrainSize { width: number; depth: number } — ⚠ undocumented
2908
+ - `RibbonSurfaceStyle` (interface): interface RibbonSurfaceStyle — How a ground ribbon (path, road, sidewalk) shades: procedural surface, edge blend and wear.
2909
+ - `SurfaceRibbonMaterialOptions` (interface): interface SurfaceRibbonMaterialOptions extends Partial<RibbonSurfaceStyle> — Options for {@link createSurfaceRibbonMaterial}.
2771
2910
  - `TerraformBrushCursor` (function): function TerraformBrushCursor({ center, y = 0.05, radius, mode }: TerraformBrushCursorProps): React.JSX.Element | null — ⚠ undocumented
2772
2911
  - `TerraformBrushCursorProps` (interface): interface TerraformBrushCursorProps { center: readonly [number, number] | null; y?: number; radius: number; mode: TerraformMode } — ⚠ undocumented
2773
2912
  - `TerrainArea` (type): type TerrainArea = number | readonly [width: number, depth: number] — ⚠ undocumented
2913
+ - `TerrainDetailMaterialHandle` (interface): interface TerrainDetailMaterialHandle — The built procedural detail terrain material, ready to mount on the ground mesh.
2914
+ - `TerrainDetailMaterialOptions` (interface): interface TerrainDetailMaterialOptions — Surface-pass inputs beyond the resolved detail config.
2915
+ - `TerrainDetailMaterialTextures` (interface): interface TerrainDetailMaterialTextures — Loaded PBR textures matching a resolved `ResolvedTerrainDetailMaterial.maps`' roles. The caller (a React component, via `useTexture`/`useLoader`) owns loading and disposal; `core` never touches `THREE.Texture` and this shell function never fetches a URL itself. Omitted AO/roughness skip sampling.
2774
2916
  - `TerrainField` (interface): interface TerrainField — A sampleable ground surface: height and normal at any x/z, with optional bounds and water level.
2775
2917
  - `TerrainHeightSampler` (type): type TerrainHeightSampler = (x: number, z: number) => number — ⚠ undocumented
2776
2918
  - `TerrainNormal` (type): type TerrainNormal = readonly [number, number, number] — A surface normal vector at a terrain sample point.
2777
- - `TerrainPaintColorSampler` (type): type TerrainPaintColorSampler = (x: number, z: number, out: THREE.Color) => boolean — Writes authored paint into an existing linear color; returns false for unpainted ground.
2778
- - `TerrainPaintSource` (type): type TerrainPaintSource = Pick<EditableTerrain, "layers" | "weightsAt" | "surfaceAt"> — Live authored paint channels needed to sample a terrain's material stack without its heightfield.
2919
+ - `TerrainPaintColorSampler` (type): type TerrainPaintColorSampler = (x: number, z: number, out: THREE.Color) => boolean — Blends authored paint over the base ground color already in `out`; returns false (leaving `out` untouched) where no painted cell contributes.
2920
+ - `TerrainPaintSlot` (interface): interface TerrainPaintSlot — One GPU paint slot: authored hue plus the procedural response it renders with.
2921
+ - `TerrainPaintSource` (type): type TerrainPaintSource = Pick<EditableTerrain, "layers" | "weightsAt" | "surfaceAt"> & Partial<Pick<EditableTerrain, "gridBounds" | "cols" | "rows">> — Live authored paint channels needed to sample a terrain's material stack without its heightfield. With the grid fields present, paint is filtered across neighbouring cells instead of snapping to each cell.
2922
+ - `TerrainPaintTextures` (interface): interface TerrainPaintTextures — GPU-side paint coverage shared by the terrain surface material; `update` after each paint stroke.
2923
+ - `TerrainPaintWeights` (interface): interface TerrainPaintWeights — Per-cell paint coverage packed for the shader: two RGBA8 planes holding up to eight layer weights.
2924
+ - `TerrainQualityContext` (const): const TerrainQualityContext: React.Context<GraphicsQuality> — The player's graphics tier as terrain materials see it; the shell provides it, `high` outside one.
2779
2925
  - `TerrainSeed` (type): type TerrainSeed = number | string — ⚠ undocumented
2926
+ - `TerrainSurfaceClass` (type): type TerrainSurfaceClass = "ground" | "grass" | "dirt" | "gravel" | "rock" | "sand" | "mud" | "snow" | "road" — Procedural ground response the terrain shader renders for a painted surface id.
2780
2927
  - `TerrainSurfaceColorOptions` (interface): interface TerrainSurfaceColorOptions — Slope/noise surface shading laid over the height lerp so untextured ground reads like terrain instead of a smooth two-tone gradient. All knobs are optional with sane defaults; every field is a pure function of vertex position (+ `seed`), so the coloring is stable across rebuilds and tests.
2781
2928
  - `TerrainVertexColorOptions` (interface): interface TerrainVertexColorOptions { low?: THREE.ColorRepresentation; high?: THREE.ColorRepresentation; waterline?: THREE.ColorRepresentation; waterlineHeight?: number } — ⚠ undocumented
2782
- - `createTerrainPaintColorSampler` (function): function createTerrainPaintColorSampler(terrain: TerrainPaintSource): TerrainPaintColorSampler — Shares layer tint, opacity and linear weighted-color blending between editor and runtime ground.
2929
+ - `createSurfaceRibbonMaterial` (function): function createSurfaceRibbonMaterial(options: SurfaceRibbonMaterialOptions): THREE.MeshStandardMaterial — A `MeshStandardMaterial` that shades a ground ribbon (path, road, sidewalk) with the terrain's own procedural surface functions, so a dirt track or asphalt strip matches painted ground: the authored colour varied by the surface class's grain, roughness and micro-normal, wheel ruts or tyre lanes, and (with `feather > 0`) a noise-warped edge that fades into the terrain. The geometry needs a {@link RIBBON_FRAME_ATTRIBUTE} attribute from {@link ribbonSurfaceFrame}; build it wider by {@link ribbonEdgeMargin} so the fade straddles the authored edge. The caller disposes the material.
2930
+ - `createTerrainPaintColorSampler` (function): function createTerrainPaintColorSampler(terrain: TerrainPaintSource): TerrainPaintColorSampler — Shares layer tint, opacity and linear weighted-color blending between editor and runtime ground. Paint lives on a coarse cell grid; when the source exposes that grid, each sample smoothly blends the four surrounding cell centres (unpainted cells contribute the caller's base color in `out`), so painted paths and clearings get soft edges instead of grid-aligned stair steps.
2931
+ - `createTerrainPaintTextures` (function): function createTerrainPaintTextures(weights: TerrainPaintWeights): TerrainPaintTextures — Uploads packed paint coverage ({@link packTerrainPaintWeights}) as two linear-filtered RGBA textures plus per-slot hue, surface class, roughness and pattern scale for the terrain surface material.
2783
2932
  - `displaceHeightfieldGeometry` (function): function displaceHeightfieldGeometry(geometry: THREE.BufferGeometry, sampleHeight: (x: number, z: number) => number, options: HeightfieldDisplaceOptions): void — Re-samples a `PlaneGeometry(width, depth, segments, segments).rotateX(-π/2)` heightfield mesh from a `sampleHeight` field in place: vertex Y, optional vertex color, grid central-difference normals, and an analytically maintained bounding sphere. With a dirty `region`, the work is O(region vertices) — heights/colors update inside the covering vertex window, normals inside the window plus a one-vertex ring — never a whole-mesh `computeVertexNormals`/`computeBoundingSphere` pass, which is what keeps per-frame brush stamps inside the editor's frame budget. The bounding sphere derives from the fixed plane extents plus a running height range kept on `geometry.userData`; partial passes only expand it, a full pass resets it exactly.
2933
+ - `packTerrainPaintWeights` (function): function packTerrainPaintWeights(source: TerrainPaintSource, into?: TerrainPaintWeights): TerrainPaintWeights | undefined — Packs a terrain's authored paint (layer stack, blend weights or hard surface ids) into per-cell coverage planes for the terrain surface shader. Returns `undefined` when the source exposes no cell grid. Pass `into` to rewrite an existing buffer in place after a paint stroke.
2934
+ - `resolvePathSurface` (function): function resolvePathSurface(kind: string, width: number, meta: Readonly<Record<string, unknown>> | undefined): RibbonSurfaceStyle | null — Default ribbon look for an authored path: the surface comes from `meta.surface`, else the path kind (`"road"` paves, `"gravel-track"` gravels), else dirt. Soft ground gets a noise-warped edge that fades into the terrain and wheel ruts; pavement keeps a near-hard edge and faint tyre lanes. `meta.surfaceDetail: false` (or `meta.surface: false`) returns `null` for the old flat ribbon; `meta.edgeSoftness` scales the fade (0 = hard) and `meta.wear` sets wear 0..1.
2935
+ - `ribbonEdgeMargin` (function): function ribbonEdgeMargin(style: Pick<RibbonSurfaceStyle, "feather" | "warp">): number — Extra ribbon width (metres, both sides together) a style needs so its fade straddles the authored edge.
2936
+ - `ribbonSurfaceFrame` (function): function ribbonSurfaceFrame(ribbons: readonly Float32Array[]): Float32Array — Per-vertex `(along, across, toEnd)` for a ribbon in `buildRoadRibbon`'s layout (left/right vertex pairs down the centreline). Pass several ribbons' position arrays to get one concatenated frame for a merged mesh; each ribbon's distances restart at its own start.
2937
+ - `terrainSurfaceClass` (function): function terrainSurfaceClass(surface: string): TerrainSurfaceClass — Classifies a painted surface id (e.g. `"grass"`, `"dry-grass"`, `"cobble-stone"`) into the procedural ground response the terrain shader draws for it; unknown ids get the generic `"ground"` response.
2938
+ - `terrainSurfaceClassIndex` (function): function terrainSurfaceClassIndex(cls: TerrainSurfaceClass): number — Shader index of a surface class (`jgSurface`'s `cls`); bit `1 << index` in a class mask.
2939
+ - `terrainSurfaceRoughness` (function): function terrainSurfaceRoughness(cls: TerrainSurfaceClass): number — Default roughness the terrain shades a surface class with.
2940
+ - `useSurfaceRibbonMaterial` (function): function useSurfaceRibbonMaterial(options: SurfaceRibbonMaterialOptions | null): THREE.MeshStandardMaterial | null — {@link createSurfaceRibbonMaterial} for a React mesh: rebuilt only when an option changes and disposed on unmount. Pass `null` to get `null` (the caller falls back to a flat material).
2941
+ - `useTerrainQuality` (function): function useTerrainQuality(): GraphicsQuality — Graphics tier for terrain surface shading (`low` takes the cheap path).
2784
2942
 
2785
2943
  ## @jgengine/shell/terrain/CarvedTerrain
2786
2944
 
@@ -2848,9 +3006,22 @@
2848
3006
 
2849
3007
  - `SoilPatchEdge` (interface): interface SoilPatchEdge — Optional noise-eroded edge-fade config for {@link createSoilPatchMaterial} — the patch dissolves into the ground at its border.
2850
3008
 
3009
+ ## @jgengine/shell/terrain/surfaceRibbonMaterial
3010
+
3011
+ - `RIBBON_FRAME_ATTRIBUTE` (const): const RIBBON_FRAME_ATTRIBUTE: "jgRibbon" — Vertex attribute {@link ribbonSurfaceFrame} fills: metres along the ribbon, signed metres across it, metres to its far end.
3012
+ - `RibbonSurfaceStyle` (interface): interface RibbonSurfaceStyle — How a ground ribbon (path, road, sidewalk) shades: procedural surface, edge blend and wear.
3013
+ - `SurfaceRibbonMaterialOptions` (interface): interface SurfaceRibbonMaterialOptions extends Partial<RibbonSurfaceStyle> — Options for {@link createSurfaceRibbonMaterial}.
3014
+ - `createSurfaceRibbonMaterial` (function): function createSurfaceRibbonMaterial(options: SurfaceRibbonMaterialOptions): THREE.MeshStandardMaterial — A `MeshStandardMaterial` that shades a ground ribbon (path, road, sidewalk) with the terrain's own procedural surface functions, so a dirt track or asphalt strip matches painted ground: the authored colour varied by the surface class's grain, roughness and micro-normal, wheel ruts or tyre lanes, and (with `feather > 0`) a noise-warped edge that fades into the terrain. The geometry needs a {@link RIBBON_FRAME_ATTRIBUTE} attribute from {@link ribbonSurfaceFrame}; build it wider by {@link ribbonEdgeMargin} so the fade straddles the authored edge. The caller disposes the material.
3015
+ - `resolvePathSurface` (function): function resolvePathSurface(kind: string, width: number, meta: Readonly<Record<string, unknown>> | undefined): RibbonSurfaceStyle | null — Default ribbon look for an authored path: the surface comes from `meta.surface`, else the path kind (`"road"` paves, `"gravel-track"` gravels), else dirt. Soft ground gets a noise-warped edge that fades into the terrain and wheel ruts; pavement keeps a near-hard edge and faint tyre lanes. `meta.surfaceDetail: false` (or `meta.surface: false`) returns `null` for the old flat ribbon; `meta.edgeSoftness` scales the fade (0 = hard) and `meta.wear` sets wear 0..1.
3016
+ - `ribbonEdgeMargin` (function): function ribbonEdgeMargin(style: Pick<RibbonSurfaceStyle, "feather" | "warp">): number — Extra ribbon width (metres, both sides together) a style needs so its fade straddles the authored edge.
3017
+ - `ribbonSurfaceFrame` (function): function ribbonSurfaceFrame(ribbons: readonly Float32Array[]): Float32Array — Per-vertex `(along, across, toEnd)` for a ribbon in `buildRoadRibbon`'s layout (left/right vertex pairs down the centreline). Pass several ribbons' position arrays to get one concatenated frame for a merged mesh; each ribbon's distances restart at its own start.
3018
+ - `useSurfaceRibbonMaterial` (function): function useSurfaceRibbonMaterial(options: SurfaceRibbonMaterialOptions | null): THREE.MeshStandardMaterial | null — {@link createSurfaceRibbonMaterial} for a React mesh: rebuilt only when an option changes and disposed on unmount. Pass `null` to get `null` (the caller falls back to a flat material).
3019
+
2851
3020
  ## @jgengine/shell/terrain/terrainDetailMaterial
2852
3021
 
3022
+ - `ALL_TERRAIN_SURFACE_CLASSES` (const): const ALL_TERRAIN_SURFACE_CLASSES: 511 — Compile every surface response, for an editor viewport whose paint can change to any surface.
2853
3023
  - `TerrainDetailMaterialHandle` (interface): interface TerrainDetailMaterialHandle — The built procedural detail terrain material, ready to mount on the ground mesh.
3024
+ - `TerrainDetailMaterialOptions` (interface): interface TerrainDetailMaterialOptions — Surface-pass inputs beyond the resolved detail config.
2854
3025
  - `TerrainDetailMaterialTextures` (interface): interface TerrainDetailMaterialTextures — Loaded PBR textures matching a resolved `ResolvedTerrainDetailMaterial.maps`' roles. The caller (a React component, via `useTexture`/`useLoader`) owns loading and disposal; `core` never touches `THREE.Texture` and this shell function never fetches a URL itself. Omitted AO/roughness skip sampling.
2855
3026
 
2856
3027
  ## @jgengine/shell/terrain/terrainMath
@@ -2867,9 +3038,34 @@
2867
3038
 
2868
3039
  ## @jgengine/shell/terrain/terrainPaintColor
2869
3040
 
2870
- - `TerrainPaintColorSampler` (type): type TerrainPaintColorSampler = (x: number, z: number, out: THREE.Color) => boolean — Writes authored paint into an existing linear color; returns false for unpainted ground.
2871
- - `TerrainPaintSource` (type): type TerrainPaintSource = Pick<EditableTerrain, "layers" | "weightsAt" | "surfaceAt"> — Live authored paint channels needed to sample a terrain's material stack without its heightfield.
2872
- - `createTerrainPaintColorSampler` (function): function createTerrainPaintColorSampler(terrain: TerrainPaintSource): TerrainPaintColorSampler — Shares layer tint, opacity and linear weighted-color blending between editor and runtime ground.
3041
+ - `TerrainPaintColorSampler` (type): type TerrainPaintColorSampler = (x: number, z: number, out: THREE.Color) => boolean — Blends authored paint over the base ground color already in `out`; returns false (leaving `out` untouched) where no painted cell contributes.
3042
+ - `TerrainPaintSource` (type): type TerrainPaintSource = Pick<EditableTerrain, "layers" | "weightsAt" | "surfaceAt"> & Partial<Pick<EditableTerrain, "gridBounds" | "cols" | "rows">> — Live authored paint channels needed to sample a terrain's material stack without its heightfield. With the grid fields present, paint is filtered across neighbouring cells instead of snapping to each cell.
3043
+ - `createTerrainPaintColorSampler` (function): function createTerrainPaintColorSampler(terrain: TerrainPaintSource): TerrainPaintColorSampler — Shares layer tint, opacity and linear weighted-color blending between editor and runtime ground. Paint lives on a coarse cell grid; when the source exposes that grid, each sample smoothly blends the four surrounding cell centres (unpainted cells contribute the caller's base color in `out`), so painted paths and clearings get soft edges instead of grid-aligned stair steps.
3044
+
3045
+ ## @jgengine/shell/terrain/terrainQuality
3046
+
3047
+ - `TerrainQualityContext` (const): const TerrainQualityContext: React.Context<GraphicsQuality> — The player's graphics tier as terrain materials see it; the shell provides it, `high` outside one.
3048
+ - `useTerrainQuality` (function): function useTerrainQuality(): GraphicsQuality — Graphics tier for terrain surface shading (`low` takes the cheap path).
3049
+
3050
+ ## @jgengine/shell/terrain/terrainSurfaceShader
3051
+
3052
+ - `NOISE_GLSL` (const): const NOISE_GLSL: "\nuint jgHashUint(uvec2 v){\n uint x = v.x * 1664525u + v.y * 1013904223u;\n x = (x ^ (x >> 16u)) * 2246822519u;\n x = (x ^ (x >> 13u)) * 3266489917u;\n x = x ^ (x >> 16u);\n return x;\n}\nfloat jgHash(vec2 p){\n ivec2 i = ivec2(floor(p));\n return float(jgHashUint(uvec2(i))) * (1… — Integer-hash value noise (`jgHashUint`, `jgVNoise`, `jgFbm`) every surface chunk builds on.
3053
+ - `PAINT_PARS_GLSL` (const): const PAINT_PARS_GLSL: "\nuniform sampler2D uPaintA;\nuniform sampler2D uPaintB;\nuniform vec2 uPaintMin;\nuniform vec2 uPaintCell;\nuniform vec2 uPaintGrid;\nuniform float uPaintWarp;\nuniform vec3 uLayerColor[8];\nuniform float uLayerClass[8];\nuniform float uLayerRough[8];\nuniform float uLayerSc… — Paint-coverage uniforms and the warped, smoothed cell lookup.
3054
+ - `SURFACE_BUMP_NORMAL_GLSL` (const): const SURFACE_BUMP_NORMAL_GLSL: "\n#ifndef JG_SURFACE_LOW\n{\n // Bump from the blended surface height (metres) via screen-space derivatives, unnormalised so the\n // tilt is the true world slope of that height field at any distance.\n vec3 jgSigX = dFdx(-vViewPosition);\n vec3 jgSigY = dFdy(-vViewP… — Micro-normal from a surface height `jgBumpH` (metres) via screen-space derivatives; goes after `normal_fragment_maps`. Skipped on the low tier.
3055
+ - `SURFACE_FUNCTIONS_GLSL` (const): const SURFACE_FUNCTIONS_GLSL: "\nfloat jgFootprint;\nfloat jgFbm2(vec2 p){ return jgVNoise(p) * 0.62 + jgVNoise(p * 2.03 + 17.3) * 0.38; }\nfloat jgFbm3o(vec2 p){ return (jgVNoise(p) * 0.5 + jgVNoise(p * 2.03 + 17.3) * 0.3 + jgVNoise(p * 4.1 + 5.9) * 0.2); }\nfloat jgBand(float freq){ return 1.0 - s… — GLSL for the terrain surface pass: per-surface procedural ground (grass, dirt, gravel, rock, sand, mud, snow, road) and the height-aware blend between painted layers. Needs `NOISE_GLSL` first. Every octave is gated by `jgBand(frequency)` so detail finer than a pixel fades to its mean instead of shimmering at distance.
3056
+
3057
+ ## @jgengine/shell/terrain/terrainSurfaces
3058
+
3059
+ - `MAX_TERRAIN_PAINT_LAYERS` (const): const MAX_TERRAIN_PAINT_LAYERS: 8 — At most this many distinct paint layers reach the GPU; extra layers fold into the last slot.
3060
+ - `TerrainPaintSlot` (interface): interface TerrainPaintSlot — One GPU paint slot: authored hue plus the procedural response it renders with.
3061
+ - `TerrainPaintTextures` (interface): interface TerrainPaintTextures — GPU-side paint coverage shared by the terrain surface material; `update` after each paint stroke.
3062
+ - `TerrainPaintWeights` (interface): interface TerrainPaintWeights — Per-cell paint coverage packed for the shader: two RGBA8 planes holding up to eight layer weights.
3063
+ - `TerrainSurfaceClass` (type): type TerrainSurfaceClass = "ground" | "grass" | "dirt" | "gravel" | "rock" | "sand" | "mud" | "snow" | "road" — Procedural ground response the terrain shader renders for a painted surface id.
3064
+ - `createTerrainPaintTextures` (function): function createTerrainPaintTextures(weights: TerrainPaintWeights): TerrainPaintTextures — Uploads packed paint coverage ({@link packTerrainPaintWeights}) as two linear-filtered RGBA textures plus per-slot hue, surface class, roughness and pattern scale for the terrain surface material.
3065
+ - `packTerrainPaintWeights` (function): function packTerrainPaintWeights(source: TerrainPaintSource, into?: TerrainPaintWeights): TerrainPaintWeights | undefined — Packs a terrain's authored paint (layer stack, blend weights or hard surface ids) into per-cell coverage planes for the terrain surface shader. Returns `undefined` when the source exposes no cell grid. Pass `into` to rewrite an existing buffer in place after a paint stroke.
3066
+ - `terrainSurfaceClass` (function): function terrainSurfaceClass(surface: string): TerrainSurfaceClass — Classifies a painted surface id (e.g. `"grass"`, `"dry-grass"`, `"cobble-stone"`) into the procedural ground response the terrain shader draws for it; unknown ids get the generic `"ground"` response.
3067
+ - `terrainSurfaceClassIndex` (function): function terrainSurfaceClassIndex(cls: TerrainSurfaceClass): number — Shader index of a surface class (`jgSurface`'s `cls`); bit `1 << index` in a class mask.
3068
+ - `terrainSurfaceRoughness` (function): function terrainSurfaceRoughness(cls: TerrainSurfaceClass): number — Default roughness the terrain shades a surface class with.
2873
3069
 
2874
3070
  ## @jgengine/shell/touch/OrientationHint
2875
3071
 
@@ -3147,7 +3343,7 @@
3147
3343
  - `NameplateSample` (interface): interface NameplateSample — Projected nameplate with optional health and distance from the render camera.
3148
3344
  - `ProjectileTracers` (function): function ProjectileTracers({ lifeMs = 130 }: { lifeMs?: number }): React.JSX.Element — ⚠ undocumented
3149
3345
  - `Reticle` (function): function Reticle({ className }: { className?: string }): React.JSX.Element — ⚠ undocumented
3150
- - `WorldBarSample` (interface): interface WorldBarSample { entityId?: string; x: number; y: number; percent: number } — ⚠ undocumented
3346
+ - `WorldBarSample` (interface): interface WorldBarSample { entityId?: string; x: number; y: number; percent: number; pxPerMeter?: number } — ⚠ undocumented
3151
3347
  - `WorldEntityBars` (function): function WorldEntityBars({ statId, height = 2.2, roles, resolveRole, maxDistance = 60, occlude = true, tickMs = 80, maxSamples = 64, }: { statId: string; height?: number; roles?: readonly CatalogEntityRole[]; resolveRole?: (entity: SceneEntity) => CatalogEntityRole | undefined; /** Hide bars beyond … — ⚠ undocumented
3152
3348
  - `WorldFloatText` (function): function WorldFloatText({ height = 1.9, lifeMs = 950 }: { height?: number; lifeMs?: number }): React.JSX.Element — ⚠ undocumented
3153
3349
  - `WorldNameplates` (function): function WorldNameplates({ statId = "health", height = 2.3, roles, resolveRole, maxDistance = 40, occlude = true, tickMs = 120, maxSamples = 64, resolveName, showHealth = true, className, nameplateClassName, nameClassName, barClassName, fillClassName, renderNameplate, }: WorldNameplatesProps): React… — Billboarded name + 78×6px HP bar over every nearby non-local entity that passes `roles`/`maxDistance` — headless (className/data-* slots on every part, `renderNameplate` for a full swap), turned on declaratively via `defineGame({ nameplates })` rather than mounted by hand.
@@ -3199,7 +3395,7 @@
3199
3395
 
3200
3396
  - `NameplateSample` (interface): interface NameplateSample — Projected nameplate with optional health and distance from the render camera.
3201
3397
  - `Projectable` (interface): interface Projectable { set(x: number, y: number, z: number): this; project(camera: {matrixWorldInverse: unknown; projectionMatrix: unknown}): this; x: number; y: number; z: number } — ⚠ undocumented
3202
- - `WorldBarSample` (interface): interface WorldBarSample { entityId?: string; x: number; y: number; percent: number } — ⚠ undocumented
3398
+ - `WorldBarSample` (interface): interface WorldBarSample { entityId?: string; x: number; y: number; percent: number; pxPerMeter?: number } — ⚠ undocumented
3203
3399
  - `WorldOverlayCamera` (interface): interface WorldOverlayCamera — Render camera matrices; world translation supplies the overlay's viewpoint.
3204
3400
  - `WorldOverlaySampleOptions` (interface): interface WorldOverlaySampleOptions — Sampling policy shared by health bars and nameplates.
3205
3401