@jgengine/convex 0.55.0 → 0.56.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -26,6 +26,171 @@ At publish, rename this heading to the new version and mirror the entries into
26
26
  `packages/core/src/meta/changelog.ts` (the typed `CHANGELOG` export).
27
27
  -->
28
28
 
29
+ ## 0.56.1
30
+
31
+ ### Migrate
32
+
33
+ - **Bump lockstep SDK packages to `^0.56.1`:** `@jgengine/{core,rapier,ui,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.53.1`; `@jgengine/github` is `0.43.1`.
34
+
35
+ ### Added
36
+
37
+ - scripts/check-npm-trusted.ts refuses a release unless every package it publishes exists on npm and its latest version came through trusted publishing; bun run release runs it before cutting and the Publish workflow before its first npm publish, so a half-published release cannot happen.
38
+
39
+ ### Fixed
40
+
41
+ - the grass eviction test walks a 600 m field in six settles instead of 2 km in twelve; it took 27-32 s on CI runners against a 30 s limit and blocked the 0.56.0 publish.
42
+ - the engine UI package (packages/ui) publishes as @jgengine/react again. 0.56.0 renamed it @jgengine/ui, a name npm had never seen, so trusted publishing refused it part-way and @jgengine/core@0.56.0 on latest depended on a package that did not exist. Imports, dependencies, jgengine.json engine keys ("react"), scaffold aliases and doctor's @source check all name @jgengine/react; bun run check react-free treats it as the engine's own UI package.
43
+
44
+ ## 0.56.0
45
+
46
+ ### Migrate
47
+
48
+ - **Bump lockstep SDK packages to `^0.56.0`:** `@jgengine/{core,rapier,ui,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.53.0`; `@jgengine/github` is `0.43.0`.
49
+ - Native games compose `RuneRoot`, `RuneView`, `RuneLabel`, `RuneAction`, `RuneFor`, `RuneLayers`, `RuneTransient` and `RuneWorldAnchor` from `@jgengine/core/ui/rune/primitives.view`. Theme tokens and native style props replace game CSS; the new API supplies no finished widgets or screens. See the page-sized example in the jgengine-ui skill.
50
+ - `ActionBindingsProvider` and `useActionBindings` move from `ui/kit/uiKitRoot.view` to `input/actionBindings.view`, so native UI reads rebinds without importing a CSS kit. All in-repo imports move with them.
51
+ - `adjustSettingRow` now lives in `@jgengine/core/settings/settingAdjustment`; its arguments and result are unchanged. In-repo settings adopters use the new home.
52
+ - `createHudAwakeClock` and `sameWatched` now live in `@jgengine/core/hud/hudAwakeClock`, shared by the DOM and native HUD renderers.
53
+ - engine and game UI uses @jgengine/core/ui/jsx and @jgengine/react; the website and two framework examples retain their own React host and embed games with mountGamePlayer
54
+
55
+ ### Added
56
+
57
+ - RuneField native text field (controlled value, submit, autoFocus, focused-key capture, touch keyboard proxy) so native games can take chat, names and passwords.
58
+ - `RuneTextField` (`@jgengine/core/ui/rune/elements.view`): Native Rune-owned text field; edits and submits come back as events.
59
+ - `nativeFieldKey` (`@jgengine/core/ui/rune/nativeSurface.view`): ⚠ undocumented
60
+ - `RuneField` (`@jgengine/core/ui/rune/primitives.view`): Native text entry: Rune owns caret, selection and IME; the game owns the text. Also `RuneFieldProps`.
61
+ - Direct native Rune primitives for the engine JSX runtime, with one typed style set, bound slots/property patches, responsive/safe/world anchoring, keyed collections, native interaction and flow ownership. Set `uiRenderer: "rune"` and compose inside `RuneRoot` to bypass the CSS mirror.
62
+ - A headless native PNG renderer using the production builder, and native bounds/text for the game drive harness.
63
+ - Rune-native UI primitives (RuneRoot, RuneView, RuneLabel, RuneAction, RuneFor, RuneLayers, RuneTransient, RuneWorldAnchor, RuneGlyph) and Deepward rebuilt on them with no CSS or DOM host
64
+ - `ActionBindingsProvider` (`@jgengine/core/input/actionBindings.view`): Provide the game's live bindings, including player rebinds, without a presentation kit. Also `useActionBindings`.
65
+ - `adjustSettingRow` (`@jgengine/core/settings/settingAdjustment`): Step a slider within its bounds, flip a toggle or cycle a selection.
66
+ - `pageHost` (`@jgengine/core/ui/jsx/pageHost.view`): One JSX tree can contain native surfaces and ordinary page tooling, sharing all providers.
67
+ - `RuneActivation` (`@jgengine/core/ui/rune/activation`): Hold activation shared by pointer, touch, keyboard and pad, using an injected clock. Also `runeNextFocus`.
68
+ - `RuneBoundFont` (`@jgengine/core/ui/rune/element`): ⚠ undocumented Also `RuneBoundStyle`, `RuneColor`, `RuneElement`, `RuneElementProps`, `RuneFont`, `RuneGesture`, `RuneSize`, `RuneSpan` and 1 more.
69
+ - `RuneBox` (`@jgengine/core/ui/rune/elements.view`): Native Rune column. Also `RuneButton`, `RuneIcon`, `RuneRow`, `RuneScroll`, `RuneSurface`, `RuneText`, `RuneViewport`, `useRuneViewport`.
70
+ - `registerNativeRuneFont` (`@jgengine/core/ui/rune/nativeSurface.view`): Register native font bytes before the first surface mounts.
71
+ - `RuneNode` (`@jgengine/core/ui/rune/nativeTree`): A native host node; it has no browser element or computed style. Also `NativeWireNode`, `RuneTreeSink`, `nativeEnabled`, `nativeLabel`, `nativeSpec`, `nativeTreeHost`.
72
+ - `bind` (`@jgengine/core/ui/rune/primitives`): Bind any native visual property without scheduling a component render. Also `RuneFlow`, `RuneRoute`, `RuneSource`, `RuneTheme`, `RuneViewportClass`, `createRuneFlow`, `ownRuneFlow`, `runeFormat` and 3 more.
73
+ - `RuneAction` (`@jgengine/core/ui/rune/primitives.view`): Command interaction with native focus, pointer/touch, keyboard, pad, hold and drag/drop. Also `RuneActionProps`, `RuneAnchor`, `RuneFor`, `RuneGlyph`, `RuneLabel`, `RuneLabelProps`, `RuneLayers`, `RuneProjection` and 6 more.
74
+ - native Rune scroll views fade the edge with hidden content; flows can drag and touch
75
+ - one machine-wide limit on rendering browsers (JG_RENDER_SLOTS, sized to the machine; fairshare room for a second). Every Chrome launch takes a slot, a running but slow broker is joined instead of passed by, and devBroker status lists every broker and slot holder
76
+ - `createRunePassThrough` (`@jgengine/core/ui/runePassThrough`): ⚠ undocumented Also `RunePassThrough`.
77
+ - Rune rendering stays opt-in through ?ui=rune; DOM remains the default. The CSS styling path and CSS-to-Rune mirror are temporary while typed Rune-native props replace CSS
78
+ - `uiTour` (`@jgengine/core/play/uiTour`): ⚠ undocumented Also `TourStep`.
79
+ - `Children` (`@jgengine/core/ui/jsx`): ⚠ undocumented Also `ActionDispatch`, `AnyActionArg`, `CSSProperties`, `Component`, `ComponentClass`, `ComponentType`, `Context`, `DependencyList` and 58 more.
80
+ - `createPortal` (`@jgengine/core/ui/jsx/client.view`): ⚠ undocumented Also `Root`, `createRoot`, `flushSync`, `hydrateRoot`.
81
+ - `AllHTMLAttributes` (`@jgengine/core/ui/jsx/dom.view`): Every attribute of every tag, for the tags with no narrower set here. Also `AnchorHTMLAttributes`, `AnimationEvent`, `AnimationEventHandler`, `AriaAttributes`, `Attributes`, `AudioHTMLAttributes`, `ButtonHTMLAttributes`, `CanvasHTMLAttributes` and 56 more.
82
+ - `domHost` (`@jgengine/core/ui/jsx/domHost.view`): The host a DOM root renders through.
83
+ - `Children` (`@jgengine/core/ui/jsx/element`): ⚠ undocumented Also `AnyType`, `ClassInstance`, `ComponentClass`, `ContextType`, `ELEMENT`, `ElementType`, `Fragment`, `FunctionComponent` and 26 more.
84
+ - `Fragment` (`@jgengine/core/ui/jsx/jsx-dev-runtime`): ⚠ undocumented Also `jsx`, `jsxDEV`, `jsxs`.
85
+ - `Fragment` (`@jgengine/core/ui/jsx/jsx-runtime`): ⚠ undocumented Also `jsx`, `jsxDEV`, `jsxs`.
86
+ - `markupHost` (`@jgengine/core/ui/jsx/markup`): ⚠ undocumented Also `MarkupNode`, `renderToStaticMarkup`, `renderToString`, `renderToTree`, `styleText`, `treeToMarkup`.
87
+ - `Component` (`@jgengine/core/ui/jsx/reconciler`): Base class for the few class components (error boundaries). Also `ActionDispatch`, `AnyActionArg`, `Fiber`, `Host`, `PureComponent`, `Root`, `act`, `flushSync` and 17 more.
88
+ - `CSSProperties` (`@jgengine/core/ui/jsx/types`): An inline style object: CSS properties in camelCase, plus `--custom` properties. Also `ComponentClass`, `ComponentType`, `Context`, `DependencyList`, `Dispatch`, `EffectCallback`, `ElementType`, `ErrorInfo` and 19 more.
89
+ - `MirrorReport` (`@jgengine/core/ui/rune/cssMirror.view`): What the mirror could not express, counted by `property: value` and by where it was asked. Also `WireBox`, `boxOfElement`, `elementOf`, `idOf`, `mirrorContents`, `mirrorElement`, `mirrorRoot`, `onPaintResourceLoaded` and 4 more.
90
+ - `stateMatters` (`@jgengine/core/ui/rune/cssStates.view`): Whether marking `element` with the state's attribute would change how it or anything under it is styled. Also `twinStateRules`.
91
+ - `averageOfStops` (`@jgengine/core/ui/rune/cssValues`): The colour a gradient averages to: what a small repeated tile of it reads as. Also `WireDim`, `WireImageLayer`, `WireLayer`, `WireShadow`, `WireTextShadow`, `WireTrack`, `WireTransform`, `colorAlpha` and 17 more.
92
+ - `installRuneMeasure` (`@jgengine/core/ui/rune/domMeasure.view`): Make measurement of Rune-drawn elements answer from Rune's layout. Also `RuneScrollDriver`, `invalidateRuneLayout`, `runeRectOf`, `setRuneRect`, `setRuneScroll`.
93
+ - `compactPatches` (`@jgengine/core/ui/rune/patches`): Keep only attached replacement roots after all changes in one mirror pass.
94
+ - `RuneInput` (`@jgengine/core/ui/rune/runeInput.view`): ⚠ undocumented
95
+ - `RuneLayer` (`@jgengine/core/ui/rune/runeLayer.view`): Draws its children through Rune instead of the page: the elements exist (so styles, refs and events work as written) but the browser never lays them out or paints them; the Rune stage mirrors them onto its canvas over the game. Also `runeUiEnabled`.
96
+ - `RuneStage` (`@jgengine/core/ui/rune/runeStage.view`): ⚠ undocumented Also `registerRuneFont`.
97
+ - `VideoFrameSource` (`@jgengine/core/ui/rune/videoFrames.view`): The browser video methods used by a frame feed.
98
+ - `RuneScrollValues` (`@jgengine/core/ui/runeUi.view`): Scroll offset, viewport and content size in layout pixels.
99
+ - `mountGamePlayer` (`@jgengine/shell/mountGamePlayer`): Run a game inside any page: mounts the registry-driven player into `container` and returns the function that takes it down. Also `MountGamePlayerOptions`.
100
+ - `AccessibilityProvider` (`@jgengine/react`): Apply accessibility preferences to a subtree: exposes `--jg-text-scale` for text to scale off, and sets `data-reduced-motion` / `data-high-contrast` / `data-colorblind` / `data-captions` for CSS to respond to. Also `AbilitySlotBindingOptions`, `AchievementGallery`, `AchievementGalleryProps`, `AchievementToast`, `AchievementToastProps`, `ActionBar`, `ActionBarChrome`, `ActionBarKeyResult` and 626 more.
101
+ - `EventfulEngineStore` (`@jgengine/react/engineStore`): ⚠ undocumented Also `ReadableEngineStore`.
102
+ - `ENGINE_STYLESHEET` (`@jgengine/react/engineStyles`): The engine stylesheet: every `jg-*` keyframe the shared blocks and registry components animate with, the `[data-jg-*]` interaction-state rules behind {@link HudButton} / {@link HudSurface}, 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`). Also `ensureEngineStyles`, `useEngineStyles`.
103
+ - `PerFrameRenderError` (`@jgengine/react/hooks`): The error a component that re-renders every frame or tick from the store fails with in a dev build or test. Also `AbilitySlotBindingOptions`, `DomEventTarget`, `EventMeterView`, `GameClockControls`, `InventoryGridBinding`, `UseAxisChannelResult`, `WorldBrowserState`, `abilityKitNeedsHeartbeat` and 46 more.
104
+ - `GamePreviewComponent` (`@jgengine/react/preview`): ⚠ undocumented Also `GamePreviewProps`, `GamePreviewStates`.
105
+ - `PREVIEW_FIXTURES` (`@jgengine/react/previewFixtures`): Registry of deterministic, engine-level preview fixtures — the *real* exported `@jgengine/react` components (`HudThemePreview`, `IconsPreview`, …) that render identically every time from static values, so they can be screenshotted as regression evidence without booting a game. Also `PreviewFixture`, `previewFixtureNames`, `resolvePreviewFixture`.
106
+ - `GameContextBridge` (`@jgengine/react/provider`): Re-provide a captured {@link GameContext} across a nested React reconciler boundary. Also `GameProvider`, `useGameContext`, `useOptionalGameContext`.
107
+ - `selectionEqual` (`@jgengine/react/selectSnapshot`): Whether two selections show the same thing: the same value, or arrays and plain objects whose entries are the same all the way down (a record or class instance compares by identity). Also `HotSelectionPace`, `SelectCache`.
108
+ - `useKeyedStore` (`@jgengine/react/store`): Subscribe a component to one owner's slot of a typed keyed-family store defined with `defineKeyedStore`. Also `useStore`.
109
+ - `MENU_THEME_STYLE` (`@jgengine/react/testFixtures`): Menu theme tokens passed through the `style` prop with no cast.
110
+
111
+ ### Changed
112
+
113
+ - Reduced repo agent rules and restored Claude loading
114
+ - Removed nested rule files; retained a 2,726-byte root guide and a CLAUDE.md containing only @AGENTS.md
115
+ - Updated game and skill references to the surviving guidance; no runtime code changed
116
+ - Validation: setup and every documented command or its --help passed; skill-doc tests passed; links and the size target passed; verify-fast caught its injected regression
117
+ - Ember Command moves to Rune-native UI primitives and deletes its CSS UI. Engine: native patches before Rune's first frame now queue behind the spec (runeUi.view.test.ts). Verified by fixture tests; no browser drive (Jon's call). Unverified: phone-width browser view, world health bars under the Rune canvas, GPU.
118
+ - Fernwild moves to Rune-native UI primitives and deletes its CSS UI. Verified by fixture tests; no browser drive (Jon's call). Missing primitives worked around in-game: text entry (name list), 3D model preview in UI (body card), shift/double click (x5 button), focus return after native press. Unverified: phone browser view, death/respawn code test, GPU.
119
+ - Verified: cargo test -p jg-ui 33 pass; rune UI TS tests 44 pass (new tests broken once and restored); core, Deepward and Scrap Signal type-checks.
120
+ - Unverified: real typing, IME and the phone keyboard proxy in a browser; the press fix is confirmed by the Harbor Heat tour after this lands.
121
+ - Unverified: real typing, IME and the phone keyboard proxy in a browser.
122
+ - Harbor Heat now draws its whole UI on the Rune-native primitives; its CSS and old UI folder are deleted.
123
+ - Verified: headless play test walks title, settings, credits, a run, HUD, city phone, pause, save and resume at 1280x720 and 390x844 (assertion broken once and restored); 193 tests; type-check.
124
+ - Simplified: 3D portraits on the licence and talk screens are now silhouettes; the courier name uses a key stand-in until it moves to RuneField.
125
+ - Unverified: real mouse, touch and pad input in a browser.
126
+ - LLMville now draws its whole UI on the Rune-native primitives; style.css, Tailwind and shadcn are removed.
127
+ - Verified: headless play test walks sign-in, lab creation, chat, every tablet app's main action, settings and rebind at 1280x720 and 390x844 (assertion broken once and restored); 399 tests; type-check; react-free; template sync.
128
+ - Simplified: /dev workbench routes and an unused create-world screen removed; the map claims cells by tap or drag with no hover price.
129
+ - Unverified: real browser input and the phone keyboard; emoji in player chat render as empty boxes (no emoji font in Rune).
130
+ - Deepward composes its whole HUD, menus, settings, credits and game-specific panels from native Rune primitives. Its stylesheet, CSS kit and old visor composition are removed. Other games keep the temporary mirror during migration.
131
+ - Odd Orbit moves to Rune-native UI primitives and deletes its CSS UI. Unverified: real-click HUD after play starts (software Rune repaint on host), GPU cost.
132
+ - Scrap Signal now draws its whole UI on the Rune-native primitives; its CSS and old UI folder are deleted.
133
+ - Verified: 228 tests, type-check, boot probe and bundle check pass with no ceiling raised; 42 headless native screenshots at 1280x720, 844x390 and 390x844 in games/scrap-signal/proofs/rune-primitives.
134
+ - Unverified: real mouse, touch and pad drives need the PC (no WebGPU in Chrome on the Linux host); the call sign entry is a game-local key stand-in until RuneField lands.
135
+ - A bare wrangler deploy in apps/web now runs the site asset check and refuses a build without game models, LUTs or HDRIs
136
+ - Landing gates run in about half the time: game suites play their proofs across up to four worker threads (fernwild's suite 761 s to 176 s; an engine change's gate 432-599 s to 210-242 s), and verify:fast shares its pass cache and test timings across every worktree of a clone, so a unit another lane proved on the same bytes is not run again
137
+ - A game's README or PLAN edit runs none of its tests or type-checks (was 64 tests)
138
+ - lane:close marks its own draft PR ready and refuses a PR into another base before the gate, not after it; tally's .tally/ reports are ignored, so a rebased landing's re-verify can merge
139
+ - Beacon Bastion draws its whole UI natively through Rune; old CSS UI removed. Engine: clicks on Rune pass through to the 3D field. Tests prove keyboard-typed commander name and the menu confirm cue.
140
+ - Brightway Park draws its UI natively through Rune; CSS UI removed
141
+ - Drift Foundry draws its UI natively through Rune; CSS UI removed
142
+ - vendored Rune updated from dev d751f07 to c92e462 (250 commits). Re-applied 8 jgengine patches upstream still lacks (layout rects, layout_style, web transparency, WOFF fonts, zero-range scroll, canvas wheel, synthetic bold for regular-only game fonts, SVG via the browser) and dropped 10 that served only the removed CSS host. Added a web touch build fix upstream dev needs. Text takes upstream's new WindfoilChromium default on Windows and Linux. jg-ui wasm grows from 7.7 to 10.3 MB (2.9 to 4.9 MB gzipped).
143
+ - Unverified: LLMville main flow needs its local backend; framework-host and website demo browser checks remain pending. Rune fidelity is outside this React-removal landing. Performance was not measured
144
+
145
+ ### Fixed
146
+
147
+ - native Rune presses now wake a frame, so a click on a still HUD reaches the game at once instead of waiting for the next input.
148
+ - the rule effect registry test no longer fails when Scrap Signal's perks load first in the same worker.
149
+ - gamepad taps, held input and landing lock checks no longer race host scheduling
150
+ - Deepward's pause menu no longer covers the save-failure, reprint and blocked-record sheets
151
+ - a finger tap on a non-modal Rune control reaches it instead of the game below
152
+ - Deepward sheet titles and item names wrap on phones; the Fitter windup no longer re-renders each frame
153
+ - convert game package declarations and browser fixtures, preserve static form selections, and remove stale React Three Fiber documentation
154
+
155
+ ### Removed
156
+
157
+ - `@jgengine/core/settings/settingsBlocks.view` no longer exports `adjustSettingRow`.
158
+ - `@jgengine/core/ui/kit/uiKitRoot.view` no longer exports `ActionBindingsProvider`, `useActionBindings`.
159
+ - `@jgengine/react` no longer exports `ActionBindingsProvider`, `useActionBindings`.
160
+ - verify:fast's rerun of timed-out test files; a timeout stays red and names the file. The bg-tasks exit-event test that timed out under load is quarantined with #3118
161
+ - jg-ui's CSS box host (css.rs, style.rs), its canvas-frame and box patch/scroll exports, and the old widget spec; the Rune wasm takes only native nodes and RuneUi drops patch, patchText, scrollTo, canvasFrame, removeCanvas and rects.
162
+ - the CSS-to-Rune mirror (RuneLayer, ?ui=rune, cssMirror, cssStates, domMeasure, runeInput, patches, boxTransform, videoFrames, cssValues) and the uiTour flow built on it. Every game draws its UI on native Rune surfaces; loadGameFonts now registers fonts with the native surface.
163
+ - `@jgengine/core/play/uiTour` no longer exports `TourStep`, `uiTour`.
164
+ - `@jgengine/core/ui/rune/cssMirror.view` no longer exports `MirrorReport`, `WireBox`, `boxOfElement`, `elementOf`, `idOf`, `mirrorContents`, `mirrorElement`, `mirrorRoot` and 5 more.
165
+ - `@jgengine/core/ui/rune/cssStates.view` no longer exports `stateMatters`, `twinStateRules`.
166
+ - `@jgengine/core/ui/rune/cssValues` no longer exports `WireDim`, `WireImageLayer`, `WireLayer`, `WireShadow`, `WireTextShadow`, `WireTrack`, `WireTransform`, `averageOfStops` and 18 more.
167
+ - `@jgengine/core/ui/rune/domMeasure.view` no longer exports `RuneScrollDriver`, `installRuneMeasure`, `invalidateRuneLayout`, `runeRectOf`, `setRuneRect`, `setRuneScroll`.
168
+ - `@jgengine/core/ui/rune/patches` no longer exports `compactPatches`.
169
+ - `@jgengine/core/ui/rune/runeInput.view` no longer exports `RuneInput`.
170
+ - `@jgengine/core/ui/rune/runeLayer.view` no longer exports `RuneLayer`, `runeUiEnabled`.
171
+ - `@jgengine/core/ui/rune/runeStage.view` no longer exports `RuneStage`, `registerRuneFont`.
172
+ - `@jgengine/core/ui/rune/videoFrames.view` no longer exports `VideoFrameSource`.
173
+ - `@jgengine/react` no longer exports `AbilitySlotBindingOptions`, `AccessibilityProvider`, `AchievementGallery`, `AchievementGalleryProps`, `AchievementToast`, `AchievementToastProps`, `ActionBar`, `ActionBarChrome` and 627 more.
174
+ - `@jgengine/react/engineStore` no longer exports `EventfulEngineStore`, `ReadableEngineStore`.
175
+ - `@jgengine/react/engineStyles` no longer exports `ENGINE_STYLESHEET`, `ensureEngineStyles`, `useEngineStyles`.
176
+ - `@jgengine/react/hooks` no longer exports `AbilitySlotBindingOptions`, `DomEventTarget`, `EventMeterView`, `GameClockControls`, `InventoryGridBinding`, `PerFrameRenderError`, `UseAxisChannelResult`, `WorldBrowserState` and 47 more.
177
+ - `@jgengine/react/preview` no longer exports `GamePreviewComponent`, `GamePreviewProps`, `GamePreviewStates`.
178
+ - `@jgengine/react/previewFixtures` no longer exports `PREVIEW_FIXTURES`, `PreviewFixture`, `previewFixtureNames`, `resolvePreviewFixture`.
179
+ - `@jgengine/react/provider` no longer exports `GameContextBridge`, `GameProvider`, `useGameContext`, `useOptionalGameContext`.
180
+ - `@jgengine/react/selectSnapshot` no longer exports `HotSelectionPace`, `SelectCache`, `selectionEqual`.
181
+ - `@jgengine/react/store` no longer exports `useKeyedStore`, `useStore`.
182
+ - `@jgengine/react/testFixtures` no longer exports `MENU_THEME_STYLE`.
183
+
184
+ ## 0.55.1
185
+
186
+ ### Migrate
187
+
188
+ - **Bump lockstep SDK packages to `^0.55.1`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.52.1`; `@jgengine/github` is `0.42.1`.
189
+
190
+ ### Fixed
191
+
192
+ - a save whose stored environment (wet surfaces, fires, flocks) no longer fits the game's authored world loads the rest of the save and starts the environment fresh, with a degraded: console line. On 0.55.0 it threw, so Harbor Heat showed a runtime error instead of its menu for anyone holding a save from 0.54.0.
193
+
29
194
  ## 0.55.0
30
195
 
31
196
  ### Migrate
@@ -1,4 +1,4 @@
1
- import { useMemo } from "react";
1
+ import { useMemo } from "@jgengine/core/ui/jsx";
2
2
  import { useMutation, useQuery } from "convex/react";
3
3
  /**
4
4
  * Wires a game's Convex chat functions into the engine's ChatTransport
@@ -1,4 +1,4 @@
1
- import { useMemo } from "react";
1
+ import { useMemo } from "@jgengine/core/ui/jsx";
2
2
  import { useMutation, useQuery } from "convex/react";
3
3
  /**
4
4
  * Wires a game's Convex presence functions into the engine's PresenceTransport
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jgengine/convex",
3
- "version": "0.55.0",
3
+ "version": "0.56.1",
4
4
  "description": "Convex adapters for JGengine: game transport, presence transport, and backend wiring over @jgengine/core.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  "check-types": "tsgo --noEmit -p tsconfig.json"
31
31
  },
32
32
  "dependencies": {
33
- "@jgengine/core": "^0.55.0"
33
+ "@jgengine/core": "^0.56.1"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "convex": "^1.31.2",
@@ -57,8 +57,8 @@ World content is editor-first: anything that adds, moves or restyles something v
57
57
  2. `npx jgengine recipe <name>` prints a typechecked snippet; `npx jgengine recipe` lists them. [recipes/minimal-game.md](recipes/minimal-game.md) is the whole-game walkthrough; each domain has `recipes/`.
58
58
  3. Open `api.md` or a `.d.ts` only for exact signatures, with grep.
59
59
 
60
- If nothing fits, name the shared owner first (AGENTS.md, "Reusable capability goes upstream first"); report SDK gaps upstream outside this repo.
60
+ If nothing fits, name the shared owner first ([AGENTS.md](../../../AGENTS.md#game-changes-improve-the-framework)); report SDK gaps upstream outside this repo.
61
61
 
62
62
  ## Finish
63
63
 
64
- Declare the game's genres in `jgengine.json` (`suite`) and prove each required player verb with a headless proof; `jgengine suite <game>` is the score ([games/AGENTS.md](../../../games/AGENTS.md#game-suite)). Keep scene coordinates in the editor document, state serializable, randomness injected. A game is not done while its entities and props are box markers: run the asset pass in `jgengine-assets`. Prove it with `jgengine-verify`.
64
+ Declare the game's genres in `jgengine.json` (`suite`) and prove each required player verb with a headless proof; `jgengine suite <game>` is the score ([game proofs](../../../games/README.md#design-checklist-and-loop-proofs)). Keep scene coordinates in the editor document, state serializable, randomness injected. A game is not done while its entities and props are box markers: run the asset pass in `jgengine-assets`. Prove it with `jgengine-verify`.
@@ -6,7 +6,7 @@
6
6
 
7
7
  - `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
8
8
  - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
9
- - `VERSION` (const): const VERSION: "0.55.0" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
9
+ - `VERSION` (const): const VERSION: "0.56.1" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
10
10
 
11
11
  ## @jgengine/core/authoring
12
12
 
@@ -107,11 +107,11 @@
107
107
 
108
108
  ## @jgengine/core/devtools/ColPanel.view
109
109
 
110
- - `ColPanel` (function): function ColPanel(): React.JSX.Element — ⚠ undocumented
110
+ - `ColPanel` (function): function ColPanel(): JSX.Element — ⚠ undocumented
111
111
 
112
112
  ## @jgengine/core/devtools/CollisionDebugWorld.draw
113
113
 
114
- - `CollisionDebugWorld` (function): function CollisionDebugWorld(): React.JSX.Element | null — World-space collision debugger. Performs zero scene scans and zero raycasts when every layer is off. Mount only when shell devtools are enabled.
114
+ - `CollisionDebugWorld` (function): function CollisionDebugWorld(): JSX.Element | null — World-space collision debugger. Performs zero scene scans and zero raycasts when every layer is off. Mount only when shell devtools are enabled.
115
115
 
116
116
  ## @jgengine/core/devtools/FpsCounter.draw
117
117
 
@@ -119,23 +119,23 @@
119
119
 
120
120
  ## @jgengine/core/devtools/KeysPanel.view
121
121
 
122
- - `KeysPanel` (function): function KeysPanel({ input }: { input: ActionCodesMap | undefined }): React.JSX.Element — ⚠ undocumented
122
+ - `KeysPanel` (function): function KeysPanel({ input }: { input: ActionCodesMap | undefined }): JSX.Element — ⚠ undocumented
123
123
 
124
124
  ## @jgengine/core/devtools/LogsPanel.view
125
125
 
126
- - `LogsPanel` (function): function LogsPanel(): React.JSX.Element — ⚠ undocumented
126
+ - `LogsPanel` (function): function LogsPanel(): JSX.Element — ⚠ undocumented
127
127
 
128
128
  ## @jgengine/core/devtools/NetPanel.draw
129
129
 
130
- - `NetPanel` (function): function NetPanel({ multiplayer }: { multiplayer: ShellMultiplayer | null }): React.JSX.Element — ⚠ undocumented
130
+ - `NetPanel` (function): function NetPanel({ multiplayer }: { multiplayer: ShellMultiplayer | null }): JSX.Element — ⚠ undocumented
131
131
 
132
132
  ## @jgengine/core/devtools/PerfPanel.view
133
133
 
134
- - `PerfPanel` (function): function PerfPanel({ ctx }: { ctx: GameContext }): React.JSX.Element — ⚠ undocumented
134
+ - `PerfPanel` (function): function PerfPanel({ ctx }: { ctx: GameContext }): JSX.Element — ⚠ undocumented
135
135
 
136
136
  ## @jgengine/core/devtools/TunePanel.view
137
137
 
138
- - `TunePanel` (function): function TunePanel({ gameName }: { gameName: string }): React.JSX.Element — ⚠ undocumented
138
+ - `TunePanel` (function): function TunePanel({ gameName }: { gameName: string }): JSX.Element — ⚠ undocumented
139
139
  - `tuneControlLabels` (function): function tuneControlLabels(name: string): { range: string; number: string } — Accessible names for one tunable's inputs: the number field answers to the control name itself, so `drive --fill "<name>=<value>"` and a screen reader both find it; the range is `<name> slider`.
140
140
 
141
141
  ## @jgengine/core/devtools/agentBridge.draw
@@ -247,8 +247,8 @@
247
247
 
248
248
  ## @jgengine/core/devtools/diagnosticOverlay.view
249
249
 
250
- - `DiagnosticOverlay` (function): function DiagnosticOverlay({ diagnostics, gameName }: { diagnostics: RuntimeDiagnostic[]; gameName: string }): React.JSX.Element | null — ⚠ undocumented
251
- - `ErrorReportActions` (function): function ErrorReportActions({ report, issueTitle }: { report: string; issueTitle: string }): React.JSX.Element — "Copy error" + "File issue" buttons for any error surface. `report` is the full text to copy / prefill.
250
+ - `DiagnosticOverlay` (function): function DiagnosticOverlay({ diagnostics, gameName }: { diagnostics: RuntimeDiagnostic[]; gameName: string }): JSX.Element | null — ⚠ undocumented
251
+ - `ErrorReportActions` (function): function ErrorReportActions({ report, issueTitle }: { report: string; issueTitle: string }): JSX.Element — "Copy error" + "File issue" buttons for any error surface. `report` is the full text to copy / prefill.
252
252
  - `GameUiErrorBoundary` (class): class GameUiErrorBoundary extends Component< { children: ReactNode; onRuntimeError: (error: unknown, phase: string, componentStack?: string) => void }, { failed: boolean } > — ⚠ undocumented
253
253
  - `RuntimeDiagnostic` (interface): interface RuntimeDiagnostic { id: number; phase: string; message: string; stack?: string | undefined; componentStack?: string | undefined; capturedAt: string } — ⚠ undocumented
254
254
  - `errorReportContext` (function): function errorReportContext(): string — Standalone error footer text (page, browser, engine) shared by every error surface.
@@ -335,7 +335,7 @@
335
335
 
336
336
  ## @jgengine/core/devtools/panelAtoms.view
337
337
 
338
- - `SectionLabel` (function): function SectionLabel({ children }: { children: string }): React.JSX.Element — ⚠ undocumented
338
+ - `SectionLabel` (function): function SectionLabel({ children }: { children: string }): JSX.Element — ⚠ undocumented
339
339
  - `ms` (function): function ms(value: number): string — ⚠ undocumented
340
340
 
341
341
  ## @jgengine/core/devtools/perfDiagnose.view
@@ -350,11 +350,11 @@
350
350
 
351
351
  ## @jgengine/core/devtools/renderCounter
352
352
 
353
- - `ComponentRenders` (interface): interface ComponentRenders — Which React components rendered, and how often: the evidence that a changing value re-renders only its own leaf. It stands in for React DevTools' global hook, so it has to be installed before React loads: a page gets it as a script ahead of its own (`bun run drive --renders`), and every `bun test` gets it from the test preload, so a test can count renders of a real game HUD.
353
+ - `ComponentRenders` (interface): interface ComponentRenders — Which components rendered, and how often: the evidence that a changing value re-renders only its own leaf. The engine's UI runtime reports every component render and every commit to the counter installed on the page (`bun run drive --renders`) or by the test preload, so a test can count renders of a real game HUD. It can be installed before or after the runtime loads.
354
354
  - `RenderCounter` (interface): interface RenderCounter — The installed counter, at `globalThis.__jgRenders`.
355
355
  - `RenderCounts` (interface): interface RenderCounts — Every commit and component render since the counter was installed or reset.
356
- - `installRenderCounter` (function): function installRenderCounter(scope: { __REACT_DEVTOOLS_GLOBAL_HOOK__?: unknown; __jgRenders?: RenderCounter | undefined }): RenderCounter | null — Install the counter as React's devtools hook on `scope`. A component fiber rendered in a commit when it mounted or carries React's PerformedWork flag; a subtree whose child pointer did not change bailed out whole and is not walked, the same test React DevTools uses. Returns null when another devtools hook was there first. Self-contained, so a harness can ship its source to a page.
357
- - `renderCounter` (function): function renderCounter(): RenderCounter | null — The counter the test preload or the page script installed, or null when React loaded first.
356
+ - `installRenderCounter` (function): function installRenderCounter(scope: { __jgRenders?: RenderCounter | undefined; __jgRenderSink?: { render(type: unknown): void; commit(): void } | undefined }): RenderCounter — Install the counter on `scope`: the UI runtime calls its sink once per component render (a component that bailed out is never called) and once per commit. Self-contained, so a harness can ship its source to a page.
357
+ - `renderCounter` (function): function renderCounter(): RenderCounter | null — The counter the test preload or the page script installed, or null when there is none.
358
358
 
359
359
  ## @jgengine/core/devtools/report
360
360
 
@@ -461,7 +461,7 @@
461
461
 
462
462
  - `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
463
463
  - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
464
- - `VERSION` (const): const VERSION: "0.55.0" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
464
+ - `VERSION` (const): const VERSION: "0.56.1" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
465
465
 
466
466
  ## @jgengine/core/runtime/adapter
467
467
 
@@ -597,7 +597,7 @@
597
597
  - `HeadlessGameSource` (type): type HeadlessGameSource<TAssetRef extends ModelAssetRef, TMultiplayer> = | { game: HeadlessPlayable; definition?: never; content?: never; loop?: never; movement?: never; collision?: never; } | { definition: GameDefinition<TAssetRef, TMultiplayer>; game?: never; /** Catalog resolvers (`entityById`/`o… — Where a headless run gets its game. Pass the whole playable (`game`, what `defineGame` returns) and the runner reads its content, its phase-synced loop, its movement and collision config and whether the shell walks the player, the way play does. Pass a bare `definition` only for a test that builds a partial game; then content, loop, movement and collision are yours to pass, and passing them alongside `game` is a type error.
598
598
  - `HeadlessInput` (interface): interface HeadlessInput — One step's worth of player intent handed to {@link HeadlessRunner.step} — the held-action set and pointer state the shell would otherwise publish from the browser each frame.
599
599
  - `HeadlessPlayable` (type): type HeadlessPlayable<TProbe extends ProbeReadings = ProbeReadings> = PlayableGameContract<unknown, unknown, unknown, unknown, unknown, unknown, TProbe> — Any playable game, whatever its UI and render types (what `defineGame` returns); `TProbe` is what its `capture.probe` returns.
600
- - `HeadlessRunner` (interface): interface HeadlessRunner — A renderer-free driver for a game loop: builds a {@link GameContext} from a game, runs the init hooks, then advances the simulation one step at a time from injected input. No React, R3F, or three.js — the whole play path (time, input, `onTick`, behaviour nav, player movement) runs from `core` primitives alone, so a non-React host (a server tick, a test, a CLI replay) can play a real game and read its world snapshot. The shell's FrameDriver is one such driver bolted to `useFrame`; this is the same step distilled out of the render tree.
600
+ - `HeadlessRunner` (interface): interface HeadlessRunner — A renderer-free driver for a game loop: builds a {@link GameContext} from a game, runs the init hooks, then advances the simulation one step at a time from injected input. No UI runtime or three.js — the whole play path (time, input, `onTick`, behaviour nav, player movement) runs from `core` primitives alone, so a non-React host (a server tick, a test, a CLI replay) can play a real game and read its world snapshot. The shell's FrameDriver is one such driver bolted to `useFrame`; this is the same step distilled out of the render tree.
601
601
  - `HeadlessRunnerOptions` (type): type HeadlessRunnerOptions<TAssetRef extends ModelAssetRef, TMultiplayer> = HeadlessGameSource<TAssetRef, TMultiplayer> & { /** The single local player; defaults to `{ userId: "player", isNew: true }`. */ player?: { userId: string; isNew: boolean }; /** Wall-clock source for the sim clock — inject a… — ⚠ undocumented
602
602
  - `createHeadlessRunner` (function): function createHeadlessRunner<TAssetRef extends ModelAssetRef, TMultiplayer>(options: HeadlessRunnerOptions<TAssetRef, TMultiplayer>): HeadlessRunner — ⚠ undocumented
603
603
 
@@ -851,7 +851,7 @@
851
851
  ## @jgengine/shell/GameHost
852
852
 
853
853
  - `EditorSummonModule` (interface): interface EditorSummonModule — Structural shape of the module `GameHost`'s `editor` loader resolves — `import("@jgengine/editor")` satisfies it.
854
- - `GameHost` (function): function GameHost({ playable, gameId, wsUrl, multiplayer, resolveMultiplayer, editor, creator, creatorOpen = false, onCreatorOpenChange }: GameHostProps): React.JSX.Element — The one documented mount: resolves multiplayer for the playable and renders the shell. With the `editor` loader prop it also owns the whole editor summon (F2+E, `?mode=editor`, dev save endpoint).
854
+ - `GameHost` (function): function GameHost({ playable, gameId, wsUrl, multiplayer, resolveMultiplayer, editor, creator, creatorOpen = false, onCreatorOpenChange }: GameHostProps): JSX.Element — The one documented mount: resolves multiplayer for the playable and renders the shell. With the `editor` loader prop it also owns the whole editor summon (F2+E, `?mode=editor`, dev save endpoint).
855
855
  - `GameHostProps` (interface): interface GameHostProps — Props for {@link GameHost}: the playable, optional multiplayer overrides, and the optional editor loader that enables the engine-owned F2+E summon.
856
856
 
857
857
  ## @jgengine/shell/defineGame
@@ -861,8 +861,8 @@
861
861
 
862
862
  ## @jgengine/shell/gameKit
863
863
 
864
- - `Clock` (const): const Clock: React.NamedExoticComponent<{ format?: "24h" | "12h" | undefined; showDay?: boolean | undefined; controls?: boolean | undefined; style?: React.CSSProperties | undefined; className?: string | undefined; }> — A time-of-day clock reading the sim calendar — `Day N · HH:MM`, 24h or 12h. `controls` adds pause + the game's speed multipliers as clickable pills (the "fast-forward" bar), off by default so a game opts into letting the player scrub time.
865
- - `Coins` (function): function Coins({ currencyId, icon = "🪙", style, className, }: { currencyId: string; icon?: ReactNode; style?: CSSProperties; className?: string; }): React.JSX.Element — A currency counter — an icon (emoji/char, default a coin) plus the live amount for `currencyId`. *
864
+ - `Clock` (const): const Clock: NamedExoticComponent<{ format?: "24h" | "12h" | undefined; showDay?: boolean | undefined; controls?: boolean | undefined; style?: CSSProperties | undefined; className?: string | undefined; }> — A time-of-day clock reading the sim calendar — `Day N · HH:MM`, 24h or 12h. `controls` adds pause + the game's speed multipliers as clickable pills (the "fast-forward" bar), off by default so a game opts into letting the player scrub time.
865
+ - `Coins` (function): function Coins({ currencyId, icon = "🪙", style, className, }: { currencyId: string; icon?: ReactNode; style?: CSSProperties; className?: string; }): JSX.Element — A currency counter — an icon (emoji/char, default a coin) plus the live amount for `currencyId`. *
866
866
  - `DEFAULT_DRIVE_CODES` (const): const DEFAULT_DRIVE_CODES: { readonly throttle: readonly ["KeyW", "ArrowUp"]; readonly brake: readonly ["KeyS", "ArrowDown"]; readonly steerLeft: readonly ["KeyA", "ArrowLeft"]; readonly steerRight: readonly ["KeyD", "ArrowRight"]; readonly handbrake: readonly ["Space"]; } — Stock keyboard codes for driving a vehicle: WASD or the arrows for throttle, brake and steering, Space for the handbrake. Spread it as the base of a racing map and override what the game uses differently (`input: { ...DEFAULT_DRIVE_CODES, handbrake: ["KeyX"], hop: ["Space"] }`).
867
867
  - `DEFAULT_WALK_CODES` (const): const DEFAULT_WALK_CODES: { readonly moveForward: readonly ["KeyW"]; readonly moveBack: readonly ["KeyS"]; readonly moveLeft: readonly ["KeyA"]; readonly moveRight: readonly ["KeyD"]; readonly jump: readonly ["Space"]; } — Stock keyboard codes for the shell walk controller: WASD to move, Space to jump. Spread it as the base of a game's map (`input: { ...DEFAULT_WALK_CODES, sprint: ["ShiftLeft"], interact: ["KeyE"] }`) instead of re-typing the same five bindings in every game.
868
868
  - `EditorCatalogDefinition` (interface): interface EditorCatalogDefinition — Game-exported catalog definition: a `ParamSchema` plus default entries. Schemas stay in code; entry values merge into `document.catalogs` and are what the editor/RPC edits and saves.
@@ -871,13 +871,13 @@
871
871
  - `GameCameraConfig` (interface): interface GameCameraConfig — Camera tuning for the shell's rig stack: pick the rig via `rig`, then tune it through its matching config block. All fields optional — the default is the third-person orbit rig.
872
872
  - `GameConfig` (type): type GameConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TProbe extends ProbeReadings = ProbeReadings> = EngineFields<TAssetRef> & PresentationFields<TProbe> — ⚠ undocumented · used by `defineGame`: The one public authoring entry point: compose engine fields (systems, world, physics, input) and presentation fields (camera, HUD, audio, au…
873
873
  - `GameContext` (interface): interface GameContext — The live engine handle a game's loop, systems, commands, and UI read and mutate — entities, objects, the reactive store, opted-in `game.*` subsystems, world queries, and the sim clock. One context = one running world.
874
- - `GameHost` (function): function GameHost({ playable, gameId, wsUrl, multiplayer, resolveMultiplayer, editor, creator, creatorOpen = false, onCreatorOpenChange }: GameHostProps): React.JSX.Element — The one documented mount: resolves multiplayer for the playable and renders the shell. With the `editor` loader prop it also owns the whole editor summon (F2+E, `?mode=editor`, dev save endpoint).
874
+ - `GameHost` (function): function GameHost({ playable, gameId, wsUrl, multiplayer, resolveMultiplayer, editor, creator, creatorOpen = false, onCreatorOpenChange }: GameHostProps): JSX.Element — The one documented mount: resolves multiplayer for the playable and renders the shell. With the `editor` loader prop it also owns the whole editor summon (F2+E, `?mode=editor`, dev save endpoint).
875
875
  - `GameHostProps` (interface): interface GameHostProps — Props for {@link GameHost}: the playable, optional multiplayer overrides, and the optional editor loader that enables the engine-owned F2+E summon.
876
876
  - `GameLoop` (interface): interface GameLoop<TContext = unknown> — Lifecycle hooks a game implements to drive init, per-tick simulation, and player join/leave.
877
- - `Hotbar` (const): const Hotbar: React.NamedExoticComponent<{ inventoryId: string; activeSlot?: number | undefined; keys?: readonly string[] | undefined; slotSize?: number | undefined; itemIcon?: ((itemId: string) => React.ReactNode) | undefined; style?: React.CSSProperties | undefined; className?: string | undefined;… — A numbered hotbar bound to an inventory — painted iconed slots (a `GameIcon` glyph over a school-keyed gradient with a count badge, #1035), an active-slot highlight, and a keycap per slot. Place it and pass the `inventoryId`; `activeSlot` highlights the equipped one. Supply `itemIcon` to map your item ids to your own glyphs — the default resolves a `GameIcon` from the item id.
878
- - `HudCanvas` (const): const HudCanvas: React.NamedExoticComponent<{ layout: HudLayoutStore; editChord?: false | HudEditChord | undefined; showDuring?: readonly GamePhase[] | undefined; className?: string | undefined; style?: React.CSSProperties | undefined; children?: React.ReactNode; }> — Full-viewport HUD surface. Each `HudPanel` names an anchor and a priority; the canvas measures every panel and places them all at once so none lands on another, at any screen size. Panels sharing an anchor stack outward from that edge; a panel that would cover a neighbour slides along its edge to the nearest free spot; when the screen is too small, the lowest-priority panels fold into a toggle, and only then does the whole HUD shrink. Device safe areas and every touch control (any region registered as kind `control` or `system`) are kept clear. Games declare panels, never place them per size.
879
- - `HudFade` (const): const HudFade: React.NamedExoticComponent<HudFadeProps> — A HUD group that steps back when nothing is happening: it fades after a quiet spell and comes back the moment a watched value changes, so the world fills the screen and the HUD speaks only when it has news. Hovering it also brings it back. It reads `watch` and `hold` every frame and sets its own opacity, so it never re-renders, and neither do its children, for a value it watches.
880
- - `HudPanel` (const): const HudPanel: React.NamedExoticComponent<{ id: string; anchor?: HudAnchor | undefined; order?: number | undefined; chip?: string | undefined; interactive?: boolean | undefined; inset?: { x: number; y: number; } | undefined; locked?: boolean | undefined; showDuring?: readonly GamePhase[] | undefine… — A HUD block anchored to a corner, edge or the centre. `HudCanvas` places every panel together: panels sharing an anchor stack outward from the screen edge in ascending `order`, and a panel that would cover another slides to the nearest free spot. When the screen is too small, lower-`priority` panels fold into a toggle first. On fine pointers panels stay draggable through the edit chord; a dragged panel leaves the flow and keeps its custom placement.
877
+ - `Hotbar` (const): const Hotbar: NamedExoticComponent<{ inventoryId: string; activeSlot?: number | undefined; keys?: readonly string[] | undefined; slotSize?: number | undefined; itemIcon?: ((itemId: string) => UiNode) | undefined; style?: CSSProperties | undefined; className?: string | undefined; }> — A numbered hotbar bound to an inventory — painted iconed slots (a `GameIcon` glyph over a school-keyed gradient with a count badge, #1035), an active-slot highlight, and a keycap per slot. Place it and pass the `inventoryId`; `activeSlot` highlights the equipped one. Supply `itemIcon` to map your item ids to your own glyphs — the default resolves a `GameIcon` from the item id.
878
+ - `HudCanvas` (const): const HudCanvas: NamedExoticComponent<{ layout: HudLayoutStore; editChord?: false | HudEditChord | undefined; showDuring?: readonly GamePhase[] | undefined; className?: string | undefined; style?: CSSProperties | undefined; children?: UiNode; }> — Full-viewport HUD surface. Each `HudPanel` names an anchor and a priority; the canvas measures every panel and places them all at once so none lands on another, at any screen size. Panels sharing an anchor stack outward from that edge; a panel that would cover a neighbour slides along its edge to the nearest free spot; when the screen is too small, the lowest-priority panels fold into a toggle, and only then does the whole HUD shrink. Device safe areas and every touch control (any region registered as kind `control` or `system`) are kept clear. Games declare panels, never place them per size.
879
+ - `HudFade` (const): const HudFade: NamedExoticComponent<HudFadeProps> — A HUD group that steps back when nothing is happening: it fades after a quiet spell and comes back the moment a watched value changes, so the world fills the screen and the HUD speaks only when it has news. Hovering it also brings it back. It reads `watch` and `hold` every frame and sets its own opacity, so it never re-renders, and neither do its children, for a value it watches.
880
+ - `HudPanel` (const): const HudPanel: NamedExoticComponent<{ id: string; anchor?: HudAnchor | undefined; order?: number | undefined; chip?: string | undefined; interactive?: boolean | undefined; inset?: { x: number; y: number; } | undefined; locked?: boolean | undefined; showDuring?: readonly GamePhase[] | undefined; pri… — A HUD block anchored to a corner, edge or the centre. `HudCanvas` places every panel together: panels sharing an anchor stack outward from the screen edge in ascending `order`, and a panel that would cover another slides to the nearest free spot. When the screen is too small, lower-`priority` panels fold into a toggle first. On fine pointers panels stay draggable through the edit chord; a dragged panel leaves the flow and keeps its custom placement.
881
881
  - `HudSlots` (function): function HudSlots({ storageKey, slots = {}, panels = {}, overlay, showDuring = DEFAULT_SLOT_PHASES, className }: HudSlotsProps): ReactNode — The play HUD in one line: a few small panels pinned to the screen's edges, scaled from a 1600x900 design to any window and movable by the player (the layout is remembered per `storageKey`). Keep it to a handful of slots; the world is the picture.
882
882
  - `PlayableGame` (type): type PlayableGame<TProbe extends ProbeReadings = ProbeReadings> = PlayableGameContract< ComponentType, ComponentType, RenderEntity, RenderObject, ComponentType<ViewmodelProps>, ComponentType<WorldOverlayProps>, TProbe, ComponentType<RemotePlayersProps> > & { /** HUD theme the shell applies at the `G… — The concrete React-bound playable a shell `defineGame` returns and `GameHost` mounts — the one PlayableGame games handle. `TProbe` is what the game's `capture.probe` returns, so a proof flow over `typeof game` names only real probes.
883
883
  - `StoreHandle` (interface): interface StoreHandle<T> — A typed, cast-free handle onto one slot of the reactive game store (`ctx.game.store`). The single type parameter is fixed at definition; `read`/`write`/`update` never widen to `unknown`, so no call site repeats `store.get(key) as T`. Writes flow through the engine store, so they bump `ctx.version()`, serialize into a {@link WorldSnapshot}, and replay under host authority — the same guarantees a hand-rolled `store.get`/`store.set` pair gives up the moment it forks run state into a module-level singleton.
@@ -249,11 +249,11 @@
249
249
  - `BetterAuthUserShape` (interface): interface BetterAuthUserShape { id: string; name?: string | null; email?: string | null; image?: string | null } — ⚠ undocumented
250
250
  - `ClerkUserShape` (interface): interface ClerkUserShape { id: string; fullName?: string | null; username?: string | null; imageUrl?: string | null; primaryEmailAddress?: {emailAddress: string} | null; createdAt?: Date | null; lastSignInAt?: Date | null } — ⚠ undocumented
251
251
  - `ClerkUserState` (interface): interface ClerkUserState { isLoaded: boolean; isSignedIn: boolean | undefined; user: ClerkUserShape | null | undefined } — ⚠ undocumented
252
- - `GameIdentityProvider` (function): function GameIdentityProvider({ source, children, }: { source: IdentitySource; children?: ReactNode; }): React.JSX.Element — ⚠ undocumented
252
+ - `GameIdentityProvider` (function): function GameIdentityProvider({ source, children, }: { source: IdentitySource; children?: ReactNode; }): JSX.Element — ⚠ undocumented
253
253
  - `IdentitySource` (interface): interface IdentitySource { session: AuthSession | null; isLoading: boolean; signOut?: () => void } — ⚠ undocumented
254
- - `RequireSession` (function): function RequireSession({ fallback, loading, children, }: { fallback?: ReactNode; loading?: ReactNode; children?: ReactNode; }): React.JSX.Element — ⚠ undocumented
255
- - `SignOutButton` (function): function SignOutButton({ className, children, }: { className?: string; children?: ReactNode; }): React.JSX.Element | null — ⚠ undocumented
256
- - `UserBadge` (function): function UserBadge({ className, avatarClassName, nameClassName, renderBadge, }: { className?: string; avatarClassName?: string; nameClassName?: string; renderBadge?: (session: AuthSession) => ReactNode; }): React.JSX.Element | null — ⚠ undocumented
254
+ - `RequireSession` (function): function RequireSession({ fallback, loading, children, }: { fallback?: ReactNode; loading?: ReactNode; children?: ReactNode; }): JSX.Element — ⚠ undocumented
255
+ - `SignOutButton` (function): function SignOutButton({ className, children, }: { className?: string; children?: ReactNode; }): JSX.Element | null — ⚠ undocumented
256
+ - `UserBadge` (function): function UserBadge({ className, avatarClassName, nameClassName, renderBadge, }: { className?: string; avatarClassName?: string; nameClassName?: string; renderBadge?: (session: AuthSession) => ReactNode; }): JSX.Element | null — ⚠ undocumented
257
257
  - `betterAuthIdentity` (function): function betterAuthIdentity(state: BetterAuthSessionState, options?: { signOut?: () => void }): IdentitySource — ⚠ undocumented
258
258
  - `clerkIdentity` (function): function clerkIdentity(state: ClerkUserState, options?: { signOut?: () => void }): IdentitySource — ⚠ undocumented
259
259
  - `guestIdentity` (function): function guestIdentity(seed?: string): IdentitySource — ⚠ undocumented
@@ -339,11 +339,11 @@
339
339
 
340
340
  ## @jgengine/core/multiplayer/voice.view
341
341
 
342
- - `MicToggle` (function): function MicToggle({ voice, className, mutedLabel, unmutedLabel, }: { voice: VoiceState; className?: string; mutedLabel?: ReactNode; unmutedLabel?: ReactNode; }): React.JSX.Element — ⚠ undocumented
343
- - `PushToTalkButton` (function): function PushToTalkButton({ voice, className, children, }: { voice: VoiceState; className?: string; children?: ReactNode; }): React.JSX.Element — ⚠ undocumented
344
- - `SpeakingIndicator` (function): function SpeakingIndicator({ voice, userId, className, threshold = 0.01, children, }: { voice: VoiceState; userId: string; className?: string; threshold?: number; children?: ReactNode; }): React.JSX.Element — ⚠ undocumented
342
+ - `MicToggle` (function): function MicToggle({ voice, className, mutedLabel, unmutedLabel, }: { voice: VoiceState; className?: string; mutedLabel?: ReactNode; unmutedLabel?: ReactNode; }): JSX.Element — ⚠ undocumented
343
+ - `PushToTalkButton` (function): function PushToTalkButton({ voice, className, children, }: { voice: VoiceState; className?: string; children?: ReactNode; }): JSX.Element — ⚠ undocumented
344
+ - `SpeakingIndicator` (function): function SpeakingIndicator({ voice, userId, className, threshold = 0.01, children, }: { voice: VoiceState; userId: string; className?: string; threshold?: number; children?: ReactNode; }): JSX.Element — ⚠ undocumented
345
345
  - `UseVoiceOptions` (interface): interface UseVoiceOptions { transport?: VoiceTransport; channelId?: string; mode?: PushToTalkMode; resolveRoutes?: () => readonly VoiceRoute[]; getUserMedia?: (constraints: MediaStreamConstraints) => Promise<MediaStream> } — ⚠ undocumented · used by `useVoice`: Mic capture + push-to-talk + channel roster over the VoiceTransport signaling seam.
346
- - `VoiceRoster` (function): function VoiceRoster({ voice, className, participantClassName, renderParticipant, }: { voice: VoiceState; className?: string; participantClassName?: string; renderParticipant?: (participant: VoiceParticipant, gain: number) => ReactNode; }): React.JSX.Element — ⚠ undocumented
346
+ - `VoiceRoster` (function): function VoiceRoster({ voice, className, participantClassName, renderParticipant, }: { voice: VoiceState; className?: string; participantClassName?: string; renderParticipant?: (participant: VoiceParticipant, gain: number) => ReactNode; }): JSX.Element — ⚠ undocumented
347
347
  - `VoiceState` (interface): interface VoiceState { supported: boolean; micStream: MediaStream | null; micError: string | null; requestMic(): Promise<boolean>; transmitting: boolean; status: PushToTalkStatus; mode: PushToTalkMode; setMode(mode: PushToTalkMode): void; muted: boolean; setMut… — ⚠ undocumented · used by `useVoice`: Mic capture + push-to-talk + channel roster over the VoiceTransport signaling seam.
348
348
  - `useVoice` (function): function useVoice(options?: UseVoiceOptions): VoiceState — Mic capture + push-to-talk + channel roster over the VoiceTransport signaling seam. Transmission gates the captured tracks' `enabled` flag; the media plane that actually moves audio bytes (WebRTC/SFU) stays behind the transport, host-supplied. Call once per voice channel and hand the returned state to the voice components.
349
349