@jgengine/react 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/dist/accessibilitySettings.js +32 -7
- package/dist/canvasCover.d.ts +26 -0
- package/dist/canvasCover.js +53 -0
- package/dist/hooks.d.ts +34 -0
- package/dist/hooks.js +115 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/menuScreen.d.ts +7 -1
- package/dist/menuScreen.js +3 -1
- package/dist/titleMenu.d.ts +3 -1
- package/dist/titleMenu.js +2 -2
- package/package.json +2 -2
- package/skills/jgengine/api.md +2 -2
- package/skills/jgengine-ui/api.md +45 -24
- package/skills/jgengine-ui/capabilities.md +8 -0
- package/skills/jgengine-ui/reference-primitives.md +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -26,6 +26,64 @@ 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.34.0
|
|
30
|
+
|
|
31
|
+
### Migrate
|
|
32
|
+
|
|
33
|
+
- **Bump lockstep SDK packages to `^0.34.0`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.31.0`; `@jgengine/github` is `0.21.0`.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- `createRunLoop` (`@jgengine/core/run/runLoop`) holds a run's at-risk haul, banks it scaled by the line or loses it on a failure, and draws one authored change for each next run from a seeded table, so the same seed and run history replay the same sequence. State round-trips through `snapshot`/`restore`; the money stays in `progression/careerBank`.
|
|
38
|
+
|
|
39
|
+
## 0.33.0
|
|
40
|
+
|
|
41
|
+
### Migrate
|
|
42
|
+
|
|
43
|
+
- **Bump lockstep SDK packages to `^0.33.0`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.30.0`; `@jgengine/github` is `0.20.0`.
|
|
44
|
+
- `useSceneObjects` (`@jgengine/react/hooks`) keeps one list identity across `move` and `rotate`, so a record's `position`/`rotationY` is what it was at the last render. Read the live placement with `ctx.scene.object.get(instanceId)` (a `useFrame` loop or a rAF-ticked HUD) when a mesh or readout must follow a moving object.
|
|
45
|
+
- A read of a mismatched stored value now converts losslessly where it can (a number as its digits, a boolean as `"true"`/`"false"`, those strings back) and throws where it cannot. No in-repo caller relied on the old fallback; `useSetting` (`@jgengine/react/settings`) is the generic path that could, so a game that stored a number under a key it reads with a string fallback now gets the digits.
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- `releasePostfxOverlayTracking(scene)` (`@jgengine/shell/postfx/postfxOverlay`) drops the cached prepass overlay set and its listeners for a scene that is being torn down.
|
|
50
|
+
- `canvasCover` (`@jgengine/react/canvasCover`): one page-wide hold a covering surface takes, plus `useCanvasCover(declared)` for a React overlay. While it is held the shell stops drawing the world, its shadow passes and postfx; input, audio and the DOM HUD keep running, and the first frame after release draws the untouched scene. A scrim or side panel must not take it.
|
|
51
|
+
- `MenuScreen` and `TitleMenu` take `coversCanvas` (default false) to declare that the screen paints over the whole canvas. A menu with only a translucent scrim leaves the live scene drawing.
|
|
52
|
+
- The dev runner pins the cover with `?cover=1`, and the devtools agent bridge answers `canvas_cover`, so a perf run or `bun run drive` can measure a covered frame on any game.
|
|
53
|
+
- `combat.damage` carries an optional `position`: the effect path fills it from the target's own spot, and `createDamageRuntime` takes one per hit (`hit({ position })`) or from a `positionOf` reader, so a status tick is placed too. A cue with a position pans and attenuates; one without stays centred.
|
|
54
|
+
- `createHapticsRoute` (`@jgengine/shell/audio/hapticsRoute`) sends a feedback pulse to a connected gamepad, and to `navigator.vibrate` on a phone with no pad. `FeedbackHost` wires it; reduced motion still drops the camera shake only.
|
|
55
|
+
- `PerfReport.render.drawsFrame` is every draw a frame issues — shadow, main, postfx prepasses, AO and DOF together — as the 95th percentile of the measured window, with `drawsFrames` naming how many frames it covers. `bun run perf:gate` compares it against each game's re-based `drawsFrame` budget; the renderer's own single-sample `draws` is printed beside it and is no longer gated.
|
|
56
|
+
- `ObjectStore.version()` / `ObjectStore.markSpatialChange()` (`ctx.scene.object`) — a counter that moves whenever membership, a position, a rotation or a consumer-owned collider set changes, so derived spells spatial data can share one cache key.
|
|
57
|
+
- `SolidObstacleSource.version?()` (`@jgengine/core/movement/solidObstacles`) — a source that reports one gets its blocking obstacles resolved once per version into a coarse XZ index; a source without it keeps the per-query `inBox` gather.
|
|
58
|
+
- `SettingsTypeError` and `asSettingType` (`@jgengine/core/settings/settingsModel`) report and resolve a stored setting read as another type.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- `bun run land` queues landings on one lock per repo instead of running them beside each other, so two lanes no longer stack two pull requests' sixteen jobs against the account's 20-job cap. A waiting landing names the branch and pid it waits on; a lock whose process died, or that is older than 30 minutes, is broken with a warning.
|
|
63
|
+
- The scene io browser test skips Playwright's two-frame click stability check and lets three rendered frames pass instead of ten: on a runner its second test went from 53s to 11-24s and the file from 76s to 50s, of which 16-36s is the fixture's own start.
|
|
64
|
+
- `check-game-front-end` recognizes the shipped `CreditsPanel`, so a game that renders credits through the engine's panel is no longer flagged as having no credits screen.
|
|
65
|
+
- The shell's frame loop switches to `frameloop="never"` under a cover instead of drawing a world nobody can see (measured on Brightway Park's pause menu: 1,095 whole-frame draws and 5.2 ms/frame before, 0 draws and 0.11 ms/frame covered). A frame-rate cap no longer advances a covered canvas.
|
|
66
|
+
- `FeedbackDeps.play` takes an optional `position`, which `FeedbackHost` forwards to `ctx.game.audio.play`, so a positioned damage or death cue reaches the spatial engine.
|
|
67
|
+
- Each game's `perf-budget.json` entry now budgets `drawsFrame` and records the budget it replaced as `previousDraws`; an entry that still carries only `draws` fails by name instead of gating a count from a different measurement.
|
|
68
|
+
- The in-page report reads the devtools render probe's frame sample instead of the live renderer counter, so `window.__JG.report()` and the devtools panel no longer report a mid-frame partial count.
|
|
69
|
+
- Movement gathers obstacles from that index instead of re-resolving every nearby object every frame. On Harbor Heat the query fell from 0.62-0.65 ms to 0.10 ms a frame (2.0-2.1 ms to 1.4 ms of simulation a frame).
|
|
70
|
+
- `objectStore.inBox` walks a coarse grid for big boxes instead of scanning every placed object; a 1 m cell lookup costs a string hash, so the switch happens past 32 cells rather than 4096.
|
|
71
|
+
- Ambient occlusion reads the beauty pass's depth buffer instead of rendering the scene a second time for its normal/depth GBuffer. That prepass cost 537 extra draws and 2.6-2.9 ms/frame in Harbor Heat and 2,105 extra draws in Drift Foundry at 1600x900; AO now draws nothing of its own. A post chain whose antialiasing is `"smaa"` (the default) keeps SMAA and drops the beauty target's extra MSAA resolve, because WebGL2 cannot sample a multisampled depth texture; `"msaa"` chains keep the old prepass. Fixed-viewpoint captures differ only on geometry edges (#2173).
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
- Postfx prepasses no longer walk the whole scene twice a frame to hide overlays, points and lines: the set is cached per scene and rescanned when the scene graph changes. Drift Foundry 2.11 ms/frame and Harbor Heat 0.66 ms/frame of `traverse` removed.
|
|
76
|
+
- A rig posed from an animation graph no longer allocates a motion sample, a position triple and a fresh pose record every frame, and foot IK no longer allocates a probe input, filter, closure and per-leg records every frame.
|
|
77
|
+
- The Graphics tab lists a post-processing stage toggle only when the game's resolved chain can build that stage. Depth of field, which no reference game authors, hid behind a row that switched a pass the composer never created, so the toggle changed nothing. The rule covers every stage: a chain with `ao: false`, `bloom: false` or `aa: "msaa"`/`false` no longer offers those rows, and a game with no post chain gets none of them. Every other settings row is unchanged (`@jgengine/shell/settings/settingsController`, `availablePostfxStages` in `@jgengine/shell/postfx/PostProcessing`).
|
|
78
|
+
- Positional loops declared through `entitySounds`/`objectSounds` are tracked instead of rebuilt: spawn, despawn and a moved emitter update one retained loop each, and a frame where nothing moved reads only the emitters that already sound. On scrap-signal's 560 placed props (69 sounding), the tracking path drops from 16.7 µs to 1.2 µs per frame and a steady frame allocates no Set, array or record.
|
|
79
|
+
- `useAccessibilitySettings`, `useHoldVsToggle` and `useAccessibilityRows` show a setting written before the component subscribed — a saved profile or the demo path seeding values before first paint — instead of waiting for the player's next change (#2180).
|
|
80
|
+
- `useFeed`, `useQuestJournal` and `useFriends` (`@jgengine/react/hooks`) keep one list identity while membership holds instead of allocating a new array on every store write and comparing it with `Object.is`, so a HUD reading them no longer re-renders on every entity pose write. A pushed feed entry, an accepted, progressed or abandoned quest, and an accepted or removed friend each re-render once. Measured on the three read sites (`ToastStack`, `FriendsList`, `JournalQuestTracker`) with 15 quests, 20 friends and 20 feed rows: 900 renders → 0 across 300 pose writes, 0.14 ms/frame → 0.04 ms/frame.
|
|
81
|
+
- `useWorldItems` (`@jgengine/react/hooks`) keeps one list identity until an item spawns, is picked up or is removed. It read `worldItem.list()`, which allocates a new array per call, against a store signal that also fires on every entity pose write, so a HUD re-rendered every dropped-item marker each frame (0.97 ms/frame of React work in Harbor Heat).
|
|
82
|
+
- `WorldItems` (`@jgengine/shell`) reads each marker's live pose in `useFrame` instead of taking it as a prop, so a moving item still follows without re-rendering its marker.
|
|
83
|
+
- `useSceneEntities` and `useSceneObjects` (`@jgengine/react/hooks`) keep one list identity while membership holds, instead of allocating a new array on every store write and comparing it with `Object.is`. A pose write no longer re-renders every HUD that reads them (30-entity roster: 0.066 ms/frame → 0; the whole loop 0.24 → 0.05 ms/frame), while a spawn, despawn, replace, place or remove still re-renders. Non-pose object writes (state, visual, animation, slots, catalog id) still re-render.
|
|
84
|
+
- `RevealHighlights` (`@jgengine/shell/vision`) drives its own per-frame refresh only while Dark Sight is enabled, so a disabled reveal overlay no longer re-renders (0.019 ms/frame → 0) and an enabled one still follows the origin and its targets.
|
|
85
|
+
- `createSettingsStore().get` returns a stored setting of another type instead of quietly returning the fallback, so a numeric setting read with a string fallback applies instead of vanishing (#2189). Root cause: `coerce` treated the fallback's type as the stored type and replaced every other type with the fallback. A stored value that cannot be read as the asked-for type now throws `SettingsTypeError` naming the key, the stored type and the fallback type.
|
|
86
|
+
|
|
29
87
|
## 0.32.0
|
|
30
88
|
|
|
31
89
|
### Migrate
|
|
@@ -1,10 +1,35 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from "react";
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { accessibilitySettingRows, readAccessibilitySettings, readHoldVsToggle, } from "@jgengine/core/settings/accessibilitySettings";
|
|
3
3
|
import { useSettingsStore } from "./settings.js";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/** Every accessibility value as one comparable string, read with the same fallbacks the hooks use. */
|
|
5
|
+
function accessibilitySignature(store) {
|
|
6
|
+
const { state, holdVsToggle } = readAccessibilitySettings(store);
|
|
7
|
+
return JSON.stringify([
|
|
8
|
+
state.reducedMotion,
|
|
9
|
+
state.highContrast,
|
|
10
|
+
state.textScale,
|
|
11
|
+
state.colorblind,
|
|
12
|
+
state.captions,
|
|
13
|
+
holdVsToggle,
|
|
14
|
+
]);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A counter that changes on every settings write, so the hooks below re-render. React runs this
|
|
18
|
+
* subscription in a passive effect, after the render that showed the old value; a write landing in
|
|
19
|
+
* that window (a saved profile or the demo path seeding settings) would otherwise stay invisible
|
|
20
|
+
* until the player changed something else. Reading the value again on subscribe closes the window.
|
|
21
|
+
*/
|
|
22
|
+
function useSettingsRevision(store) {
|
|
6
23
|
const [revision, setRevision] = useState(0);
|
|
7
|
-
|
|
24
|
+
const rendered = useRef("");
|
|
25
|
+
rendered.current = accessibilitySignature(store);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const bump = () => setRevision((value) => value + 1);
|
|
28
|
+
const unsubscribe = store.subscribe(bump);
|
|
29
|
+
if (accessibilitySignature(store) !== rendered.current)
|
|
30
|
+
bump();
|
|
31
|
+
return unsubscribe;
|
|
32
|
+
}, [store]);
|
|
8
33
|
return revision;
|
|
9
34
|
}
|
|
10
35
|
/**
|
|
@@ -16,7 +41,7 @@ function useSettingsRevision() {
|
|
|
16
41
|
*/
|
|
17
42
|
export function useAccessibilitySettings() {
|
|
18
43
|
const store = useSettingsStore();
|
|
19
|
-
const revision = useSettingsRevision();
|
|
44
|
+
const revision = useSettingsRevision(store);
|
|
20
45
|
return useMemo(() => readAccessibilitySettings(store).state, [store, revision]);
|
|
21
46
|
}
|
|
22
47
|
/**
|
|
@@ -27,7 +52,7 @@ export function useAccessibilitySettings() {
|
|
|
27
52
|
*/
|
|
28
53
|
export function useHoldVsToggle() {
|
|
29
54
|
const store = useSettingsStore();
|
|
30
|
-
const revision = useSettingsRevision();
|
|
55
|
+
const revision = useSettingsRevision(store);
|
|
31
56
|
return useMemo(() => readHoldVsToggle(store), [store, revision]);
|
|
32
57
|
}
|
|
33
58
|
/**
|
|
@@ -39,6 +64,6 @@ export function useHoldVsToggle() {
|
|
|
39
64
|
*/
|
|
40
65
|
export function useAccessibilityRows() {
|
|
41
66
|
const store = useSettingsStore();
|
|
42
|
-
const revision = useSettingsRevision();
|
|
67
|
+
const revision = useSettingsRevision(store);
|
|
43
68
|
return useMemo(() => accessibilitySettingRows(store), [store, revision]);
|
|
44
69
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** A page-wide declaration that some surface paints over the whole game canvas. */
|
|
2
|
+
export interface CanvasCover {
|
|
3
|
+
/** True while at least one surface covers the canvas. */
|
|
4
|
+
covered(): boolean;
|
|
5
|
+
/** How many surfaces currently hold the cover. */
|
|
6
|
+
holders(): number;
|
|
7
|
+
subscribe(listener: () => void): () => void;
|
|
8
|
+
/**
|
|
9
|
+
* Declare the canvas fully covered until the returned function runs. Calling it twice declares
|
|
10
|
+
* twice; the cover lifts when the last hold releases.
|
|
11
|
+
*/
|
|
12
|
+
hold(): () => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The page's canvas cover. A full-screen menu (or a game's own opaque overlay) holds it; the
|
|
16
|
+
* shell stops drawing the world, its shadow passes and postfx while it is held, and draws again
|
|
17
|
+
* from the untouched scene when it lifts. A translucent scrim or a side panel must not hold it —
|
|
18
|
+
* anything the scene shows through has to keep drawing.
|
|
19
|
+
*
|
|
20
|
+
* @capability canvas-cover stop world rendering behind a full-screen surface while input, audio, the HUD and the menu keep running
|
|
21
|
+
*/
|
|
22
|
+
export declare const canvasCover: CanvasCover;
|
|
23
|
+
/** Declare the canvas fully covered while `declared` is true, and release on unmount. */
|
|
24
|
+
export declare function useCanvasCover(declared: boolean): void;
|
|
25
|
+
/** True while a surface covers the canvas. */
|
|
26
|
+
export declare function useCanvasCovered(): boolean;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useEffect, useSyncExternalStore } from "react";
|
|
2
|
+
const listeners = new Set();
|
|
3
|
+
let holders = 0;
|
|
4
|
+
function notifyIfPresenceChanged(previous, next) {
|
|
5
|
+
if (previous > 0 === next > 0)
|
|
6
|
+
return;
|
|
7
|
+
for (const listener of [...listeners])
|
|
8
|
+
listener();
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The page's canvas cover. A full-screen menu (or a game's own opaque overlay) holds it; the
|
|
12
|
+
* shell stops drawing the world, its shadow passes and postfx while it is held, and draws again
|
|
13
|
+
* from the untouched scene when it lifts. A translucent scrim or a side panel must not hold it —
|
|
14
|
+
* anything the scene shows through has to keep drawing.
|
|
15
|
+
*
|
|
16
|
+
* @capability canvas-cover stop world rendering behind a full-screen surface while input, audio, the HUD and the menu keep running
|
|
17
|
+
*/
|
|
18
|
+
export const canvasCover = {
|
|
19
|
+
covered: () => holders > 0,
|
|
20
|
+
holders: () => holders,
|
|
21
|
+
subscribe(listener) {
|
|
22
|
+
listeners.add(listener);
|
|
23
|
+
return () => {
|
|
24
|
+
listeners.delete(listener);
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
hold() {
|
|
28
|
+
const previous = holders;
|
|
29
|
+
holders += 1;
|
|
30
|
+
notifyIfPresenceChanged(previous, holders);
|
|
31
|
+
let released = false;
|
|
32
|
+
return () => {
|
|
33
|
+
if (released)
|
|
34
|
+
return;
|
|
35
|
+
released = true;
|
|
36
|
+
const was = holders;
|
|
37
|
+
holders = Math.max(0, holders - 1);
|
|
38
|
+
notifyIfPresenceChanged(was, holders);
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
/** Declare the canvas fully covered while `declared` is true, and release on unmount. */
|
|
43
|
+
export function useCanvasCover(declared) {
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!declared)
|
|
46
|
+
return;
|
|
47
|
+
return canvasCover.hold();
|
|
48
|
+
}, [declared]);
|
|
49
|
+
}
|
|
50
|
+
/** True while a surface covers the canvas. */
|
|
51
|
+
export function useCanvasCovered() {
|
|
52
|
+
return useSyncExternalStore(canvasCover.subscribe, canvasCover.covered, () => false);
|
|
53
|
+
}
|
package/dist/hooks.d.ts
CHANGED
|
@@ -42,7 +42,21 @@ export declare function useGamePhase(): {
|
|
|
42
42
|
};
|
|
43
43
|
/** Live run phase that degrades to `"playing"` when rendered outside a `GameProvider` (component showcases, previews), so phase-gated chrome never throws. */
|
|
44
44
|
export declare function useOptionalGamePhase(): GamePhase;
|
|
45
|
+
/**
|
|
46
|
+
* Live entities: the returned list keeps one identity until an entity spawns, despawns, is replaced
|
|
47
|
+
* or the store is hydrated — a pose write does not re-render its readers (#2212). `entity.list()`
|
|
48
|
+
* allocates a new array per write against an `Object.is` comparison, so every pose write re-rendered
|
|
49
|
+
* every HUD reading this hook. The records themselves are mutated in place, so a reader that needs a
|
|
50
|
+
* pose right now can still take `entity.position`; render-loop work reads it in `useFrame`.
|
|
51
|
+
*/
|
|
45
52
|
export declare function useSceneEntities(): readonly SceneEntity[];
|
|
53
|
+
/**
|
|
54
|
+
* Placed objects: the returned list keeps one identity across move and rotate writes, so a HUD
|
|
55
|
+
* reading it no longer re-renders on every pose write (#2212). Place, remove and the non-pose fields
|
|
56
|
+
* (catalog id, parent space, animation, visual, state, slots) still re-render. `move`/`rotate`
|
|
57
|
+
* replace the record, so take live placement from `ctx.scene.object.get(instanceId)` rather than
|
|
58
|
+
* the record's `position`.
|
|
59
|
+
*/
|
|
46
60
|
export declare function useSceneObjects(): readonly SceneObject[];
|
|
47
61
|
/**
|
|
48
62
|
* Membership-only entity id list: the returned array keeps a stable identity across per-frame pose
|
|
@@ -53,6 +67,12 @@ export declare function useSceneObjects(): readonly SceneObject[];
|
|
|
53
67
|
export declare function useSceneEntityIds(): readonly string[];
|
|
54
68
|
/** Membership-only object id list — the object counterpart of {@link useSceneEntityIds}; stable across move/rotate/setVisual, changes only on place/remove. */
|
|
55
69
|
export declare function useSceneObjectIds(): readonly string[];
|
|
70
|
+
/**
|
|
71
|
+
* Live dropped-item records: the returned list keeps one identity until an item spawns, is picked up
|
|
72
|
+
* or is removed. `worldItem.list()` allocates a new array per call and the shared store signal fires
|
|
73
|
+
* on every pose write, so a raw read re-rendered every marker each frame (#2175). A marker reads its
|
|
74
|
+
* own live pose imperatively (useFrame).
|
|
75
|
+
*/
|
|
56
76
|
export declare function useWorldItems(): readonly WorldItemRecord[];
|
|
57
77
|
/** Nearest ground item within `radius` of the local player — drives a pickup prompt/highlight. */
|
|
58
78
|
export declare function useNearestWorldItem(radius: number): WorldItemRecord | null;
|
|
@@ -76,11 +96,25 @@ export interface InventoryGridBinding {
|
|
|
76
96
|
*/
|
|
77
97
|
export declare function useInventoryGrid(inventoryId: string): InventoryGridBinding;
|
|
78
98
|
export declare function useCurrency(currencyId: string): number;
|
|
99
|
+
/**
|
|
100
|
+
* Recent feed entries for `action`, newest last: the list keeps one identity until an entry is pushed,
|
|
101
|
+
* so a HUD ticker or kill feed no longer re-renders on every pose write (#2216).
|
|
102
|
+
*/
|
|
79
103
|
export declare function useFeed({ action, limit }: {
|
|
80
104
|
action: string;
|
|
81
105
|
limit?: number;
|
|
82
106
|
}): FeedEntry[];
|
|
107
|
+
/**
|
|
108
|
+
* The local player's journal: the list keeps one identity while the accepted quests, their status and
|
|
109
|
+
* their objective progress are unchanged, so a tracker HUD no longer re-renders on every pose write
|
|
110
|
+
* (#2216). Accepting, progressing, completing or revoking a quest re-renders once.
|
|
111
|
+
*/
|
|
83
112
|
export declare function useQuestJournal(): QuestInstance[];
|
|
113
|
+
/**
|
|
114
|
+
* The local player's friends with their presence: the list keeps one identity until a request is
|
|
115
|
+
* accepted, a friend is removed or a friend's presence flips, so a friends HUD no longer re-renders on
|
|
116
|
+
* every pose write (#2216).
|
|
117
|
+
*/
|
|
84
118
|
export declare function useFriends(): FriendEntry[];
|
|
85
119
|
export declare function useParty(): PartyMemberEntry[];
|
|
86
120
|
export declare function usePresence(userId: string): PresenceInfo;
|
package/dist/hooks.js
CHANGED
|
@@ -44,11 +44,56 @@ export function useOptionalGamePhase() {
|
|
|
44
44
|
const getSnapshot = useCallback(() => (ctx === null ? "playing" : gamePhase(ctx)), [ctx]);
|
|
45
45
|
return useSyncExternalStore(ctx?.subscribe ?? noopSubscribe, getSnapshot, getSnapshot);
|
|
46
46
|
}
|
|
47
|
+
/** Same records in the same order: the list keeps one identity while membership holds (#2211, #2212). */
|
|
48
|
+
function sameRecordList(previous, next) {
|
|
49
|
+
if (previous.length !== next.length)
|
|
50
|
+
return false;
|
|
51
|
+
for (let index = 0; index < previous.length; index += 1) {
|
|
52
|
+
if (previous[index] !== next[index])
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Object records are replaced on every write, so compare the placement and leave pose out: a move or
|
|
59
|
+
* rotate keeps the list identity. Read a live pose with `ctx.scene.object.get(instanceId)`.
|
|
60
|
+
*/
|
|
61
|
+
function sameObjectPlacements(previous, next) {
|
|
62
|
+
if (previous.length !== next.length)
|
|
63
|
+
return false;
|
|
64
|
+
for (let index = 0; index < previous.length; index += 1) {
|
|
65
|
+
const before = previous[index];
|
|
66
|
+
const after = next[index];
|
|
67
|
+
if (before === after)
|
|
68
|
+
continue;
|
|
69
|
+
if (before.instanceId !== after.instanceId || before.catalogId !== after.catalogId)
|
|
70
|
+
return false;
|
|
71
|
+
if (before.parentSpace !== after.parentSpace || before.animation !== after.animation)
|
|
72
|
+
return false;
|
|
73
|
+
if (before.visual !== after.visual || before.state !== after.state || before.slots !== after.slots)
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Live entities: the returned list keeps one identity until an entity spawns, despawns, is replaced
|
|
80
|
+
* or the store is hydrated — a pose write does not re-render its readers (#2212). `entity.list()`
|
|
81
|
+
* allocates a new array per write against an `Object.is` comparison, so every pose write re-rendered
|
|
82
|
+
* every HUD reading this hook. The records themselves are mutated in place, so a reader that needs a
|
|
83
|
+
* pose right now can still take `entity.position`; render-loop work reads it in `useFrame`.
|
|
84
|
+
*/
|
|
47
85
|
export function useSceneEntities() {
|
|
48
|
-
return useGameStore((ctx) => ctx.scene.entity.list());
|
|
86
|
+
return useGameStore((ctx) => ctx.scene.entity.list(), sameRecordList);
|
|
49
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Placed objects: the returned list keeps one identity across move and rotate writes, so a HUD
|
|
90
|
+
* reading it no longer re-renders on every pose write (#2212). Place, remove and the non-pose fields
|
|
91
|
+
* (catalog id, parent space, animation, visual, state, slots) still re-render. `move`/`rotate`
|
|
92
|
+
* replace the record, so take live placement from `ctx.scene.object.get(instanceId)` rather than
|
|
93
|
+
* the record's `position`.
|
|
94
|
+
*/
|
|
50
95
|
export function useSceneObjects() {
|
|
51
|
-
return useGameStore((ctx) => ctx.scene.object.list());
|
|
96
|
+
return useGameStore((ctx) => ctx.scene.object.list(), sameObjectPlacements);
|
|
52
97
|
}
|
|
53
98
|
/**
|
|
54
99
|
* Membership-only entity id list: the returned array keeps a stable identity across per-frame pose
|
|
@@ -67,8 +112,14 @@ export function useSceneObjectIds() {
|
|
|
67
112
|
const object = ctx.scene.object;
|
|
68
113
|
return useSyncExternalStore(object.subscribeMembership, object.ids, object.ids);
|
|
69
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Live dropped-item records: the returned list keeps one identity until an item spawns, is picked up
|
|
117
|
+
* or is removed. `worldItem.list()` allocates a new array per call and the shared store signal fires
|
|
118
|
+
* on every pose write, so a raw read re-rendered every marker each frame (#2175). A marker reads its
|
|
119
|
+
* own live pose imperatively (useFrame).
|
|
120
|
+
*/
|
|
70
121
|
export function useWorldItems() {
|
|
71
|
-
return useGameStore((ctx) => ctx.scene.worldItem.list());
|
|
122
|
+
return useGameStore((ctx) => ctx.scene.worldItem.list(), sameRecordList);
|
|
72
123
|
}
|
|
73
124
|
/** Nearest ground item within `radius` of the local player — drives a pickup prompt/highlight. */
|
|
74
125
|
export function useNearestWorldItem(radius) {
|
|
@@ -112,12 +163,51 @@ export function useInventoryGrid(inventoryId) {
|
|
|
112
163
|
export function useCurrency(currencyId) {
|
|
113
164
|
return useGameStore((ctx) => ctx.game.economy.balance(ctx.player.userId, currencyId));
|
|
114
165
|
}
|
|
166
|
+
/** Feed entries are immutable once pushed, so a bound list is the same entries in the same order (#2216). */
|
|
167
|
+
function sameFeedEntries(previous, next) {
|
|
168
|
+
return sameRecordList(previous, next);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Recent feed entries for `action`, newest last: the list keeps one identity until an entry is pushed,
|
|
172
|
+
* so a HUD ticker or kill feed no longer re-renders on every pose write (#2216).
|
|
173
|
+
*/
|
|
115
174
|
export function useFeed({ action, limit }) {
|
|
116
|
-
return useGameStore((ctx) => ctx.game.feed.recent(action, limit === undefined ? undefined : { limit }));
|
|
175
|
+
return useGameStore((ctx) => ctx.game.feed.recent(action, limit === undefined ? undefined : { limit }), sameFeedEntries);
|
|
117
176
|
}
|
|
118
177
|
const EMPTY_QUESTS = [];
|
|
178
|
+
/** `list()` builds fresh rows per call, so compare the fields it emits and leave nothing else to identity. */
|
|
179
|
+
function sameQuestViews(previous, next) {
|
|
180
|
+
if (previous.length !== next.length)
|
|
181
|
+
return false;
|
|
182
|
+
for (let index = 0; index < previous.length; index += 1) {
|
|
183
|
+
const before = previous[index];
|
|
184
|
+
const after = next[index];
|
|
185
|
+
if (before === after)
|
|
186
|
+
continue;
|
|
187
|
+
if (before.questId !== after.questId || before.status !== after.status)
|
|
188
|
+
return false;
|
|
189
|
+
if (before.objectives.length !== after.objectives.length)
|
|
190
|
+
return false;
|
|
191
|
+
for (let row = 0; row < before.objectives.length; row += 1) {
|
|
192
|
+
const was = before.objectives[row];
|
|
193
|
+
const is = after.objectives[row];
|
|
194
|
+
if (was === is)
|
|
195
|
+
continue;
|
|
196
|
+
if (was.id !== is.id || was.kind !== is.kind || was.count !== is.count)
|
|
197
|
+
return false;
|
|
198
|
+
if (was.progress !== is.progress || was.complete !== is.complete)
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The local player's journal: the list keeps one identity while the accepted quests, their status and
|
|
206
|
+
* their objective progress are unchanged, so a tracker HUD no longer re-renders on every pose write
|
|
207
|
+
* (#2216). Accepting, progressing, completing or revoking a quest re-renders once.
|
|
208
|
+
*/
|
|
119
209
|
export function useQuestJournal() {
|
|
120
|
-
return useGameStore((ctx) => ctx.game.quest?.list(ctx.player.userId) ?? EMPTY_QUESTS);
|
|
210
|
+
return useGameStore((ctx) => ctx.game.quest?.list(ctx.player.userId) ?? EMPTY_QUESTS, sameQuestViews);
|
|
121
211
|
}
|
|
122
212
|
const EMPTY_FRIENDS = [];
|
|
123
213
|
const EMPTY_PARTY = [];
|
|
@@ -126,8 +216,27 @@ const EMPTY_CHAT = [];
|
|
|
126
216
|
const EMPTY_FRIEND_REQUESTS = [];
|
|
127
217
|
const EMPTY_PARTY_INVITES = [];
|
|
128
218
|
const OFFLINE_PRESENCE = { online: false };
|
|
219
|
+
/** Friends rows are rebuilt per call too, so compare the friend and their presence rather than the row object. */
|
|
220
|
+
function sameFriendViews(previous, next) {
|
|
221
|
+
if (previous.length !== next.length)
|
|
222
|
+
return false;
|
|
223
|
+
for (let index = 0; index < previous.length; index += 1) {
|
|
224
|
+
const before = previous[index];
|
|
225
|
+
const after = next[index];
|
|
226
|
+
if (before === after)
|
|
227
|
+
continue;
|
|
228
|
+
if (before.userId !== after.userId || before.online !== after.online)
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The local player's friends with their presence: the list keeps one identity until a request is
|
|
235
|
+
* accepted, a friend is removed or a friend's presence flips, so a friends HUD no longer re-renders on
|
|
236
|
+
* every pose write (#2216).
|
|
237
|
+
*/
|
|
129
238
|
export function useFriends() {
|
|
130
|
-
return useGameStore((ctx) => ctx.game.social?.friends.list(ctx.player.userId) ?? EMPTY_FRIENDS);
|
|
239
|
+
return useGameStore((ctx) => ctx.game.social?.friends.list(ctx.player.userId) ?? EMPTY_FRIENDS, sameFriendViews);
|
|
131
240
|
}
|
|
132
241
|
export function useParty() {
|
|
133
242
|
return useGameStore((ctx) => ctx.game.social?.party.list(ctx.player.userId) ?? EMPTY_PARTY);
|
package/dist/index.d.ts
CHANGED
|
@@ -58,6 +58,7 @@ export * from "./interactionPrompt.js";
|
|
|
58
58
|
export * from "./startScreen.js";
|
|
59
59
|
export * from "./menuNav.js";
|
|
60
60
|
export * from "./menuScreen.js";
|
|
61
|
+
export * from "./canvasCover.js";
|
|
61
62
|
export * from "./titleMenu.js";
|
|
62
63
|
export * from "./loadingScreen.js";
|
|
63
64
|
export * from "./creditsPanel.js";
|
package/dist/index.js
CHANGED
|
@@ -58,6 +58,7 @@ export * from "./interactionPrompt.js";
|
|
|
58
58
|
export * from "./startScreen.js";
|
|
59
59
|
export * from "./menuNav.js";
|
|
60
60
|
export * from "./menuScreen.js";
|
|
61
|
+
export * from "./canvasCover.js";
|
|
61
62
|
export * from "./titleMenu.js";
|
|
62
63
|
export * from "./loadingScreen.js";
|
|
63
64
|
export * from "./creditsPanel.js";
|
package/dist/menuScreen.d.ts
CHANGED
|
@@ -11,6 +11,12 @@ export interface MenuScreenProps {
|
|
|
11
11
|
transition?: MenuScreenTransition;
|
|
12
12
|
/** Dim/blur treatment over the scene behind. Default `"dim"`. */
|
|
13
13
|
scrim?: MenuScreenScrim;
|
|
14
|
+
/**
|
|
15
|
+
* The screen paints over the whole canvas, so the engine can stop drawing the world behind it.
|
|
16
|
+
* Leave false for a scrim over a live scene or a side panel: anything the scene shows through
|
|
17
|
+
* must keep drawing. Default false.
|
|
18
|
+
*/
|
|
19
|
+
coversCanvas?: boolean;
|
|
14
20
|
/** Enter duration in ms. Default 220. */
|
|
15
21
|
enterMs?: number;
|
|
16
22
|
/** Exit duration in ms. Default 160. */
|
|
@@ -40,4 +46,4 @@ export interface MenuScreenProps {
|
|
|
40
46
|
*
|
|
41
47
|
* @capability menu-screen animated enter/exit menu screen over a live scene, with translucent scrim, backdrop slot, reduced-motion support and exit unmount
|
|
42
48
|
*/
|
|
43
|
-
export declare function MenuScreen({ open, transition, scrim, enterMs, exitMs, reducedMotion, label, role, backdrop, className, style, contentClassName, contentStyle, children, onClosed, }: MenuScreenProps): ReactNode;
|
|
49
|
+
export declare function MenuScreen({ open, transition, scrim, coversCanvas, enterMs, exitMs, reducedMotion, label, role, backdrop, className, style, contentClassName, contentStyle, children, onClosed, }: MenuScreenProps): ReactNode;
|
package/dist/menuScreen.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect, useMemo, useSyncExternalStore } from "react";
|
|
3
3
|
import { createMenuTransition, menuTransitionDurations } from "@jgengine/core/ui/menuTransition";
|
|
4
4
|
import { useReducedMotion } from "./accessibility.js";
|
|
5
|
+
import { useCanvasCover } from "./canvasCover.js";
|
|
5
6
|
const KEYFRAMES = {
|
|
6
7
|
fade: { enter: "jg-fade-in", exit: "jg-fade-out" },
|
|
7
8
|
slide: { enter: "jg-slide-up", exit: "jg-fade-out" },
|
|
@@ -28,9 +29,10 @@ function now() {
|
|
|
28
29
|
*
|
|
29
30
|
* @capability menu-screen animated enter/exit menu screen over a live scene, with translucent scrim, backdrop slot, reduced-motion support and exit unmount
|
|
30
31
|
*/
|
|
31
|
-
export function MenuScreen({ open = true, transition = "fade", scrim = "dim", enterMs = 220, exitMs = 160, reducedMotion, label, role = "dialog", backdrop, className, style, contentClassName, contentStyle, children, onClosed, }) {
|
|
32
|
+
export function MenuScreen({ open = true, transition = "fade", scrim = "dim", coversCanvas = false, enterMs = 220, exitMs = 160, reducedMotion, label, role = "dialog", backdrop, className, style, contentClassName, contentStyle, children, onClosed, }) {
|
|
32
33
|
const prefersReduced = useReducedMotion();
|
|
33
34
|
const motion = reducedMotion === undefined ? prefersReduced : reducedMotion || prefersReduced;
|
|
35
|
+
useCanvasCover(open && coversCanvas);
|
|
34
36
|
const machine = useMemo(() => {
|
|
35
37
|
const config = { enterMs, exitMs, reducedMotion: motion };
|
|
36
38
|
const initial = open
|
package/dist/titleMenu.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export interface TitleMenuProps {
|
|
|
39
39
|
transition?: MenuScreenTransition;
|
|
40
40
|
/** Dim/blur treatment over the scene behind. Default `"dim"`. */
|
|
41
41
|
scrim?: MenuScreenScrim;
|
|
42
|
+
/** The title paints over the whole canvas, so the engine may stop drawing the world behind it. Default false. */
|
|
43
|
+
coversCanvas?: boolean;
|
|
42
44
|
enterMs?: number;
|
|
43
45
|
exitMs?: number;
|
|
44
46
|
reducedMotion?: boolean;
|
|
@@ -57,4 +59,4 @@ export interface TitleMenuProps {
|
|
|
57
59
|
*
|
|
58
60
|
* @capability title-menu title-screen block — wordmark, tagline, navigable verb list, footer, backdrop slot, animated over a live scene
|
|
59
61
|
*/
|
|
60
|
-
export declare function TitleMenu({ title, eyebrow, tagline, items, onSelect, onBack, footer, controlHint, backdrop, children, open, transition, scrim, enterMs, exitMs, reducedMotion, onClosed, className, style, contentClassName, contentStyle, }: TitleMenuProps): ReactNode;
|
|
62
|
+
export declare function TitleMenu({ title, eyebrow, tagline, items, onSelect, onBack, footer, controlHint, backdrop, children, open, transition, scrim, coversCanvas, enterMs, exitMs, reducedMotion, onClosed, className, style, contentClassName, contentStyle, }: TitleMenuProps): ReactNode;
|
package/dist/titleMenu.js
CHANGED
|
@@ -25,7 +25,7 @@ const itemButton = {
|
|
|
25
25
|
*
|
|
26
26
|
* @capability title-menu title-screen block — wordmark, tagline, navigable verb list, footer, backdrop slot, animated over a live scene
|
|
27
27
|
*/
|
|
28
|
-
export function TitleMenu({ title, eyebrow, tagline, items, onSelect, onBack, footer, controlHint, backdrop, children, open = true, transition = "slide", scrim = "dim", enterMs, exitMs, reducedMotion, onClosed, className, style, contentClassName, contentStyle, }) {
|
|
28
|
+
export function TitleMenu({ title, eyebrow, tagline, items, onSelect, onBack, footer, controlHint, backdrop, children, open = true, transition = "slide", scrim = "dim", coversCanvas, enterMs, exitMs, reducedMotion, onClosed, className, style, contentClassName, contentStyle, }) {
|
|
29
29
|
const disabled = items.filter((item) => item.disabled === true).map((item) => item.id);
|
|
30
30
|
const nav = useMenuNav({
|
|
31
31
|
ids: items.map((item) => item.id),
|
|
@@ -34,7 +34,7 @@ export function TitleMenu({ title, eyebrow, tagline, items, onSelect, onBack, fo
|
|
|
34
34
|
onConfirm: onSelect,
|
|
35
35
|
...(onBack === undefined ? {} : { onBack }),
|
|
36
36
|
});
|
|
37
|
-
return (_jsx(MenuScreen, { open: open, transition: transition, scrim: scrim, ...(enterMs === undefined ? {} : { enterMs }), ...(exitMs === undefined ? {} : { exitMs }), ...(reducedMotion === undefined ? {} : { reducedMotion }), ...(onClosed === undefined ? {} : { onClosed }), label: "Title menu", ...(backdrop === undefined ? {} : { backdrop }), className: className, style: style, contentClassName: contentClassName, contentStyle: contentStyle, children: _jsxs("div", { "data-title-menu": "", style: column, ...nav.containerProps, children: [eyebrow === undefined ? null : (_jsx("p", { "data-title-eyebrow": "", style: {
|
|
37
|
+
return (_jsx(MenuScreen, { open: open, transition: transition, scrim: scrim, ...(coversCanvas === undefined ? {} : { coversCanvas }), ...(enterMs === undefined ? {} : { enterMs }), ...(exitMs === undefined ? {} : { exitMs }), ...(reducedMotion === undefined ? {} : { reducedMotion }), ...(onClosed === undefined ? {} : { onClosed }), label: "Title menu", ...(backdrop === undefined ? {} : { backdrop }), className: className, style: style, contentClassName: contentClassName, contentStyle: contentStyle, children: _jsxs("div", { "data-title-menu": "", style: column, ...nav.containerProps, children: [eyebrow === undefined ? null : (_jsx("p", { "data-title-eyebrow": "", style: {
|
|
38
38
|
margin: 0,
|
|
39
39
|
fontSize: hudType("xs"),
|
|
40
40
|
letterSpacing: "0.24em",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "React UI layer for JGengine: GameProvider, hooks, and headless primitives over @jgengine/core.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"styles": "bun scripts/styles.ts"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@jgengine/core": "^0.
|
|
38
|
+
"@jgengine/core": "^0.34.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"react": "^19.0.0",
|
package/skills/jgengine/api.md
CHANGED
|
@@ -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.
|
|
9
|
+
- `VERSION` (const): const VERSION: "0.34.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
10
10
|
|
|
11
11
|
## @jgengine/core/authoring
|
|
12
12
|
|
|
@@ -285,7 +285,7 @@
|
|
|
285
285
|
|
|
286
286
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
287
287
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
288
|
-
- `VERSION` (const): const VERSION: "0.
|
|
288
|
+
- `VERSION` (const): const VERSION: "0.34.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
289
289
|
|
|
290
290
|
## @jgengine/core/runtime/adapter
|
|
291
291
|
|
|
@@ -186,16 +186,18 @@
|
|
|
186
186
|
- `SettingCategoryDef` (interface): interface SettingCategoryDef — Declares or relabels/reorders a category tab; use it for a custom category or to reshape the built-ins.
|
|
187
187
|
- `SettingKind` (type): type SettingKind = "slider" | "toggle" | "select" — ⚠ undocumented
|
|
188
188
|
- `SettingOption` (interface): interface SettingOption { value: string; label: string } — ⚠ undocumented · used by `COLORBLIND_OPTIONS` (@jgengine/core/settings/accessibilitySettings): Colorblind palette choices.
|
|
189
|
-
- `SettingValue` (type): type SettingValue = number | boolean | string — ⚠ undocumented · used by `
|
|
189
|
+
- `SettingValue` (type): type SettingValue = number | boolean | string — ⚠ undocumented · used by `asSettingType`: Read a stored value as the type the caller asked for.
|
|
190
190
|
- `SettingsActionDef` (interface): interface SettingsActionDef — A game-state action (Restart, Quit to menu, …) shown as rows in the first "Game" settings tab — never a floating button or a rebindable key.
|
|
191
191
|
- `SettingsStore` (interface): interface SettingsStore { get<T extends SettingValue>(id: string, fallback: T): T; set(id: string, value: SettingValue): void; subscribe(listener: () => void): () => void } — ⚠ undocumented · used by `createSettingsStore`: Reactive, localStorage-backed settings store shared by the shell wiring and React hooks.
|
|
192
192
|
- `SettingsSurface` (type): type SettingsSurface = "quick" — `quick` shows compact on-screen volume/graphics buttons; `false` (default) mounts no engine trigger — open the menu from your own UI with `<SettingsTrigger>` or `useSettings().open()`.
|
|
193
|
+
- `SettingsTypeError` (class): class SettingsTypeError extends Error — A stored setting holds a value the read asked for as another type, so no honest value exists.
|
|
193
194
|
- `SettingsVariant` (type): type SettingsVariant = "panel" | "sheet" | "sidebar" | "fullscreen" — The four themed settings layouts, chosen with `defineGame({ settings: { variant } })`. All read the game's `--jg-*` theme tokens.
|
|
194
195
|
- `UI_SCALE_MAX` (const): const UI_SCALE_MAX: 1.5 — ⚠ undocumented
|
|
195
196
|
- `UI_SCALE_MIN` (const): const UI_SCALE_MIN: 0.5 — ⚠ undocumented
|
|
197
|
+
- `asSettingType` (function): function asSettingType<T extends SettingValue>(id: string, stored: unknown, fallback: T): T — Read a stored value as the type the caller asked for. A value of another primitive type converts when the result is the same value in the asked-for type (a number as its digits, a boolean as `"true"`/`"false"`); otherwise it throws {@link SettingsTypeError}, naming the key and both types, instead of handing back the fallback as if nothing were stored.
|
|
196
198
|
- `busVolumeSettingId` (function): function busVolumeSettingId(busId: string): string — ⚠ undocumented
|
|
197
199
|
- `createSettingsStore` (function): function createSettingsStore(storage: Pick<WebStorageLike, "getItem" | "setItem"> | null | undefined = defaultStorage()): SettingsStore — Reactive, localStorage-backed settings store shared by the shell wiring and React hooks.
|
|
198
|
-
- `loadSettingValue` (function): function loadSettingValue<T extends SettingValue>(id: string, fallback: T, storage: Pick<WebStorageLike, "getItem"> | null | undefined = defaultStorage()): T —
|
|
200
|
+
- `loadSettingValue` (function): function loadSettingValue<T extends SettingValue>(id: string, fallback: T, storage: Pick<WebStorageLike, "getItem"> | null | undefined = defaultStorage()): T — Read one persisted setting as `fallback`'s type. An absent or unreadable cell yields the fallback; a stored value of an incompatible type throws {@link SettingsTypeError}.
|
|
199
201
|
- `saveSettingValue` (function): function saveSettingValue(id: string, value: SettingValue, storage: Pick<WebStorageLike, "setItem"> | null | undefined = defaultStorage()): void — ⚠ undocumented
|
|
200
202
|
- `settingStorageKey` (function): function settingStorageKey(id: string): string — ⚠ undocumented
|
|
201
203
|
|
|
@@ -310,7 +312,7 @@
|
|
|
310
312
|
- `SettingCategoryDef` (interface): interface SettingCategoryDef — Declares or relabels/reorders a category tab; use it for a custom category or to reshape the built-ins.
|
|
311
313
|
- `SettingKind` (type): type SettingKind = "slider" | "toggle" | "select" — ⚠ undocumented
|
|
312
314
|
- `SettingOption` (interface): interface SettingOption { value: string; label: string } — ⚠ undocumented · used by `COLORBLIND_OPTIONS` (@jgengine/core/settings/accessibilitySettings): Colorblind palette choices.
|
|
313
|
-
- `SettingValue` (type): type SettingValue = number | boolean | string — ⚠ undocumented · used by `
|
|
315
|
+
- `SettingValue` (type): type SettingValue = number | boolean | string — ⚠ undocumented · used by `asSettingType` (@jgengine/core/settings/settingsModel): Read a stored value as the type the caller asked for.
|
|
314
316
|
- `SettingsActionDef` (interface): interface SettingsActionDef — A game-state action (Restart, Quit to menu, …) shown as rows in the first "Game" settings tab — never a floating button or a rebindable key.
|
|
315
317
|
- `SettingsStore` (interface): interface SettingsStore { get<T extends SettingValue>(id: string, fallback: T): T; set(id: string, value: SettingValue): void; subscribe(listener: () => void): () => void } — ⚠ undocumented · used by `createSettingsStore`: Reactive, localStorage-backed settings store shared by the shell wiring and React hooks.
|
|
316
318
|
- `SettingsSurface` (type): type SettingsSurface = "quick" — `quick` shows compact on-screen volume/graphics buttons; `false` (default) mounts no engine trigger — open the menu from your own UI with `<SettingsTrigger>` or `useSettings().open()`.
|
|
@@ -682,6 +684,7 @@
|
|
|
682
684
|
- `CameraShakeMeter` (function): function CameraShakeMeter({ controller, animate = false, title = "CAMERA SHAKE", kindLabels, variation = "glass", className, style, }: CameraShakeMeterProps): ReactNode — A drop-in HUD panel that visualizes a core {@link CameraShakeController}: a trauma meter (reusing the shared {@link StaminaBar} building block) plus a live percentage and the current impact `kind` label. Presentation only — it reads the controller and never interprets `kind`, mapping it through `kindLabels` for display. A shell component applies the same controller's `offset()` to the camera; this panel is the on-screen readout of how much shake is in flight.
|
|
683
685
|
- `CameraShakeMeterProps` (interface): interface CameraShakeMeterProps — Props for {@link CameraShakeMeter}.
|
|
684
686
|
- `CameraShakeReadout` (interface): interface CameraShakeReadout — A live read of a camera-shake controller: current trauma `0..1` and the last game-owned `kind`.
|
|
687
|
+
- `CanvasCover` (interface): interface CanvasCover — A page-wide declaration that some surface paints over the whole game canvas.
|
|
685
688
|
- `CaptureOdds` (function): function CaptureOdds({ chance, className, fillClassName, }: { chance: number; className?: string; fillClassName?: string; }): React.JSX.Element — ⚠ undocumented
|
|
686
689
|
- `CardFace` (function): function CardFace({ rank, suit, faceDown = false, width = 64, height = 90, className, style, children, }: { rank: Rank; suit: Suit; faceDown?: boolean; width?: number; height?: number; className?: string; style?: CSSProperties; children?: ReactNode; }): React.JSX.Element — A single playing-card face: rank + suit pips, or a patterned back when `faceDown`.
|
|
687
690
|
- `CardStack` (function): function CardStack({ pile, zone, cardOf, ...rest }: { pile: CardPileState; zone: string; cardOf: (id: string) => PlayingCard; } & Omit<Parameters<typeof StackedPile>[0], "cards">): React.JSX.Element — `StackedPile` bound to a headless `CardPileState` zone: reads the ordered card ids from `pile.zones[zone]` and resolves each to a `PlayingCard` via `cardOf`.
|
|
@@ -866,7 +869,7 @@
|
|
|
866
869
|
- `MenuNavController` (interface): interface MenuNavController — The live navigation controller returned by {@link useMenuNav}.
|
|
867
870
|
- `MenuNavItemProps` (interface): interface MenuNavItemProps — Props a menu item needs so {@link useMenuNav} can focus it and track pointer/focus.
|
|
868
871
|
- `MenuNavRender` (type): type MenuNavRender = (nav: MenuNavController) => ReactNode — Render prop for a menu whose items come from {@link useMenuNav}.
|
|
869
|
-
- `MenuScreen` (function): function MenuScreen({ open = true, transition = "fade", scrim = "dim", enterMs = 220, exitMs = 160, reducedMotion, label, role = "dialog", backdrop, className, style, contentClassName, contentStyle, children, onClosed, }: MenuScreenProps): ReactNode — One animated screen of a game front-end: full-bleed, laid out by the game, with an enter/exit animation that runs on transform/opacity only (no layout, no frame hitch) and unmounts when the exit finishes. The scrim is translucent and the optional `backdrop` slot paints behind the content, so a live 3D scene stays visible behind a title, pause or settings screen. Durations come from the caller or collapse to zero under reduced motion.
|
|
872
|
+
- `MenuScreen` (function): function MenuScreen({ open = true, transition = "fade", scrim = "dim", coversCanvas = false, enterMs = 220, exitMs = 160, reducedMotion, label, role = "dialog", backdrop, className, style, contentClassName, contentStyle, children, onClosed, }: MenuScreenProps): ReactNode — One animated screen of a game front-end: full-bleed, laid out by the game, with an enter/exit animation that runs on transform/opacity only (no layout, no frame hitch) and unmounts when the exit finishes. The scrim is translucent and the optional `backdrop` slot paints behind the content, so a live 3D scene stays visible behind a title, pause or settings screen. Durations come from the caller or collapse to zero under reduced motion.
|
|
870
873
|
- `MenuScreenProps` (interface): interface MenuScreenProps — Props for {@link MenuScreen}.
|
|
871
874
|
- `MenuScreenScrim` (type): type MenuScreenScrim = "none" | "dim" | "blur" — How much a {@link MenuScreen} dims the scene behind it. `none` keeps a live 3D scene fully visible.
|
|
872
875
|
- `MenuScreenTransition` (type): type MenuScreenTransition = "fade" | "slide" | "scale" | "none" — Which motion a {@link MenuScreen} uses when it enters and leaves.
|
|
@@ -994,7 +997,7 @@
|
|
|
994
997
|
- `TimerReadoutSource` (type): type TimerReadoutSource = "auto" | "remaining" | "elapsed" — Which value a readout leads with. `"auto"` follows the timer's direction — remaining for a countdown, elapsed for a countup charge.
|
|
995
998
|
- `TimerRing` (function): function TimerRing({ timer, id, size = 72, thickness = 8, mode = "fill", color = "var(--jg-accent, #38bdf8)", trackColor = "var(--jg-bar-track, rgba(148,163,184,0.25))", children, active = true, className, style, }: TimerRingProps): ReactNode — A radial charge/cooldown ring driven off a {@link TimerSet}'s `progress01`. Re-reads every frame and sweeps an SVG arc as the timer advances — ability charge, cooldown wipe, respawn ring, capture progress, all the same widget. `mode` chooses filling vs. draining; center `children` can hold a readout. HudTheme-skinned via `--jg-accent` / `--jg-bar-track`.
|
|
996
999
|
- `TimerRingProps` (interface): interface TimerRingProps — Props for {@link TimerRing}.
|
|
997
|
-
- `TitleMenu` (function): function TitleMenu({ title, eyebrow, tagline, items, onSelect, onBack, footer, controlHint, backdrop, children, open = true, transition = "slide", scrim = "dim", enterMs, exitMs, reducedMotion, onClosed, className, style, contentClassName, contentStyle, }: TitleMenuProps): ReactNode — A game front-end title screen: wordmark, tagline, the game's verbs in a keyboard/gamepad-navigable list, a footer and an optional backdrop. The engine owns navigation, focus and the enter/exit animation; the game owns the title markup, wording, verbs, art and every colour through HudTheme tokens. Pass the live scene (or artwork) as `backdrop` so the title renders over 3D instead of a blank page.
|
|
1000
|
+
- `TitleMenu` (function): function TitleMenu({ title, eyebrow, tagline, items, onSelect, onBack, footer, controlHint, backdrop, children, open = true, transition = "slide", scrim = "dim", coversCanvas, enterMs, exitMs, reducedMotion, onClosed, className, style, contentClassName, contentStyle, }: TitleMenuProps): ReactNode — A game front-end title screen: wordmark, tagline, the game's verbs in a keyboard/gamepad-navigable list, a footer and an optional backdrop. The engine owns navigation, focus and the enter/exit animation; the game owns the title markup, wording, verbs, art and every colour through HudTheme tokens. Pass the live scene (or artwork) as `backdrop` so the title renders over 3D instead of a blank page.
|
|
998
1001
|
- `TitleMenuItem` (interface): interface TitleMenuItem — One verb on a {@link TitleMenu}: New Game, Continue, Settings, Credits, Quit.
|
|
999
1002
|
- `TitleMenuProps` (interface): interface TitleMenuProps — Props for {@link TitleMenu}.
|
|
1000
1003
|
- `ToastStack` (function): function ToastStack({ action, limit = 4, className, renderToast, }: { action: string; limit?: number; className?: string; renderToast?: (entry: FeedEntry, index: number) => ReactNode; }): React.JSX.Element | null — Render `ctx.game.feed`'s entries for `action` as a newest-first toast stack — the feed-backed sibling of `@jgengine/core/game/toasts`' `createToastQueue`. Reach for this when the message source is already an engine event bound onto `ctx.game.feed` (kill feed, quest updates, loot log); reach for a `createToastQueue` when the game raises ad-hoc messages that need their own TTL independent of the feed's ring buffer.
|
|
@@ -1033,6 +1036,7 @@
|
|
|
1033
1036
|
- `actionFromAbilitySlot` (function): function actionFromAbilitySlot(slot: AbilitySlotSnapshot, extra?: Partial<ActionDef> & { resourceId?: string }): ActionDef — Composable React renderers over the headless action view model (`@jgengine/core/ui/actionModel`) — the data/renderer/chrome split for an RTS command card, an ability action bar, a build menu, or a radial. `useActionBar` is the DATA/HOOK layer (resolution + focus/hover/keyboard/hotkey), `ActionBarChrome` is the composable RENDERER (swap `renderItem` for custom chrome, or ignore it and consume the model directly for a radial), and `ActionBar` is the batteries-included default. A game swaps renderer or chrome without forking the logic. Layout, dimensions, hotkeys, and catalog-id mapping stay caller-owned.
|
|
1034
1037
|
- `barTokens` (function): function barTokens(tokens: BarTokens): CSSProperties — A style object binding vitals tokens to CSS custom properties — spread onto any HUD ancestor to restyle every atomic bar under it at once. Only the keys you pass are set; the rest fall back to {@link DEFAULT_BAR_TOKENS} inside the bars. This is the single global-theming seam.
|
|
1035
1038
|
- `betterAuthIdentity` (function): function betterAuthIdentity(state: BetterAuthSessionState, options?: { signOut?: () => void }): IdentitySource — ⚠ undocumented
|
|
1039
|
+
- `canvasCover` (const): const canvasCover: CanvasCover — The page's canvas cover. A full-screen menu (or a game's own opaque overlay) holds it; the shell stops drawing the world, its shadow passes and postfx while it is held, and draws again from the untouched scene when it lifts. A translucent scrim or a side panel must not hold it — anything the scene shows through has to keep drawing.
|
|
1036
1040
|
- `chatTransportFromSync` (function): function chatTransportFromSync(sync: ChatSync): ChatTransport — Lifts a callback-style ChatSync (e.g. createWsBackend().chatSyncFor(serverId)) into the hook-shaped ChatTransport contract. Create once per sync — outside render or inside useMemo — so subscriptions survive re-renders.
|
|
1037
1041
|
- `clerkIdentity` (function): function clerkIdentity(state: ClerkUserState, options?: { signOut?: () => void }): IdentitySource — ⚠ undocumented
|
|
1038
1042
|
- `createHeldKeyTracker` (function): function createHeldKeyTracker(target: HeldKeyEventTarget): { isDown: (code: string) => boolean; dispose: () => void; } — ⚠ undocumented
|
|
@@ -1077,6 +1081,8 @@
|
|
|
1077
1081
|
- `useAxisChannel` (function): function useAxisChannel(config: AxisChannelConfig): UseAxisChannelResult — Wires useHeldKeys into a fresh AxisChannel, ready for a per-frame `channel.sample(dt, isDown)`. The channel is recreated when `config` identity changes, so pass a stable config (useMemo/module constant at the call site) unless a rebind is intended.
|
|
1078
1082
|
- `useBrowserSuspension` (function): function useBrowserSuspension(onSuspend: (reason: BrowserSuspensionReason) => void, options: BrowserSuspensionOptions = {}): void — Observe browser interruptions while mounted, invoking the latest callback without replacing listeners.
|
|
1079
1083
|
- `useCameraShake` (function): function useCameraShake(controller: CameraShakeController, animate = true): CameraShakeReadout — Subscribe to a camera-shake controller and re-render on every change (add / decay / clear). Optionally drives `update()` on an animation frame so the trauma meter bleeds down live even when no game loop ticks the controller. Returns the current trauma + kind for a HUD readout.
|
|
1084
|
+
- `useCanvasCover` (function): function useCanvasCover(declared: boolean): void — Declare the canvas fully covered while `declared` is true, and release on unmount.
|
|
1085
|
+
- `useCanvasCovered` (function): function useCanvasCovered(): boolean — True while a surface covers the canvas.
|
|
1080
1086
|
- `useChat` (function): function useChat(channelId: string, options?: { limit?: number }): ChatMessage[] — ⚠ undocumented
|
|
1081
1087
|
- `useChatBubbles` (function): function useChatBubbles(options?: ChatBubblesOptions): readonly ChatBubble[] — ⚠ undocumented
|
|
1082
1088
|
- `useCoachMarks` (function): function useCoachMarks(sequence: CoachMarkSequence): CoachMarkView | null — Subscribe to a coach-mark sequence and re-render on every change, returning the current step view (or `null` when the tour is complete or waiting on a gate).
|
|
@@ -1094,10 +1100,10 @@
|
|
|
1094
1100
|
- `useEventMeter` (function): function useEventMeter(meter: EventMeter, options?: AbilitySlotBindingOptions): EventMeterView — Bind a `createEventMeter` (`@jgengine/core/stats/eventMeter`) heat/streak gauge to a component — the react-render half of the ult/adrenaline and streak/combo meters (`event-meter` capability) that lets a HUD gauge re-render on tick without the game hand-rolling a `useEffect`/`setInterval` heartbeat around `meter.value()`.
|
|
1095
1101
|
- `useEventTicker` (function): function useEventTicker(ticker: EventTicker, animate = true): EventTickerView[] — Subscribe to an event ticker and re-render it on an animation frame (unless the game already ticks it), returning the live entries newest-first with `fade` applied. Driving the frame loop is what makes entries visibly fade and drop as they age out.
|
|
1096
1102
|
- `useFastTravel` (function): function useFastTravel<TMeta = unknown>(network: FastTravelNetwork<TMeta>): FastTravelNetwork<TMeta> — Subscribe a component to a fast-travel network's discovery changes. Returns the network unchanged; the hook only forces a re-render when a point is discovered or the network is restored, so callers can read `destinations`/`list` in render.
|
|
1097
|
-
- `useFeed` (function): function useFeed({ action, limit }: { action: string; limit?: number }): FeedEntry[] —
|
|
1103
|
+
- `useFeed` (function): function useFeed({ action, limit }: { action: string; limit?: number }): FeedEntry[] — Recent feed entries for `action`, newest last: the list keeps one identity until an entry is pushed, so a HUD ticker or kill feed no longer re-renders on every pose write (#2216).
|
|
1098
1104
|
- `useFog` (function): function useFog(fog: FogField): ReturnType<FogField["cells"]> — ⚠ undocumented
|
|
1099
1105
|
- `useFriendRequests` (function): function useFriendRequests(): FriendRequestEntry[] — ⚠ undocumented
|
|
1100
|
-
- `useFriends` (function): function useFriends(): FriendEntry[] —
|
|
1106
|
+
- `useFriends` (function): function useFriends(): FriendEntry[] — The local player's friends with their presence: the list keeps one identity until a request is accepted, a friend is removed or a friend's presence flips, so a friends HUD no longer re-renders on every pose write (#2216).
|
|
1101
1107
|
- `useGame` (function): function useGame(): { commands: Omit<GameContext["game"]["commands"], "run"> & { run(name: string, input: unknown): DispatchedCommandResult }; events: GameEvents } — UI command surface. Hosted commands return a promise; await it to display acknowledgement or rejection. Registration and runAs remain trusted local APIs.
|
|
1102
1108
|
- `useGameClock` (function): function useGameClock(): ClockSnapshot & { controls: SimClock } — ⚠ undocumented
|
|
1103
1109
|
- `useGameContext` (function): function useGameContext(): GameContext — ⚠ undocumented
|
|
@@ -1140,7 +1146,7 @@
|
|
|
1140
1146
|
- `usePlural` (function): function usePlural(): (key: string, count: number, params?: TParams) => string — Return the plural-aware translator bound to the current locale.
|
|
1141
1147
|
- `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.
|
|
1142
1148
|
- `usePresence` (function): function usePresence(userId: string): PresenceInfo — ⚠ undocumented
|
|
1143
|
-
- `useQuestJournal` (function): function useQuestJournal(): QuestInstance[] —
|
|
1149
|
+
- `useQuestJournal` (function): function useQuestJournal(): QuestInstance[] — The local player's journal: the list keeps one identity while the accepted quests, their status and their objective progress are unchanged, so a tracker HUD no longer re-renders on every pose write (#2216). Accepting, progressing, completing or revoking a quest re-renders once.
|
|
1144
1150
|
- `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.
|
|
1145
1151
|
- `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.
|
|
1146
1152
|
- `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.
|
|
@@ -1149,10 +1155,10 @@
|
|
|
1149
1155
|
- `useRoster` (function): function useRoster(userId?: string): readonly RosterEntry[] — ⚠ undocumented
|
|
1150
1156
|
- `useSaveSlots` (function): function useSaveSlots(model: SaveSlots): SaveSlotMeta[] — Subscribe to a {@link SaveSlots} index and re-render on any change (write, clear, rename, restore). Returns the live list of slots in menu order. Wire it into a save-select screen and mutate the model in event handlers — the menu re-renders itself.
|
|
1151
1157
|
- `useSaveStore` (function): function useSaveStore<T>(store: SaveStore<T>, options?: { load?: boolean }): SaveStoreView<T> — Bind a {@link SaveStore} to React: returns the live `value` and `status` and re-renders whenever either changes. By default it calls `load()` once on mount (pass `{ load: false }` to hydrate yourself). The same hook works for offline (localStorage) and cloud (Convex/DB) saves — only the store's backend differs.
|
|
1152
|
-
- `useSceneEntities` (function): function useSceneEntities(): readonly SceneEntity[] —
|
|
1158
|
+
- `useSceneEntities` (function): function useSceneEntities(): readonly SceneEntity[] — Live entities: the returned list keeps one identity until an entity spawns, despawns, is replaced or the store is hydrated — a pose write does not re-render its readers (#2212). `entity.list()` allocates a new array per write against an `Object.is` comparison, so every pose write re-rendered every HUD reading this hook. The records themselves are mutated in place, so a reader that needs a pose right now can still take `entity.position`; render-loop work reads it in `useFrame`.
|
|
1153
1159
|
- `useSceneEntityIds` (function): function useSceneEntityIds(): readonly string[] — Membership-only entity id list: the returned array keeps a stable identity across per-frame pose writes and only changes when an entity spawns, despawns, or the store is hydrated (#625). A marker mapped from these ids reads its own live pose imperatively (useFrame), so the actor tree no longer re-reconciles every frame. Prefer this over {@link useSceneEntities} for large scenes.
|
|
1154
1160
|
- `useSceneObjectIds` (function): function useSceneObjectIds(): readonly string[] — Membership-only object id list — the object counterpart of {@link useSceneEntityIds}; stable across move/rotate/setVisual, changes only on place/remove.
|
|
1155
|
-
- `useSceneObjects` (function): function useSceneObjects(): readonly SceneObject[] —
|
|
1161
|
+
- `useSceneObjects` (function): function useSceneObjects(): readonly SceneObject[] — Placed objects: the returned list keeps one identity across move and rotate writes, so a HUD reading it no longer re-renders on every pose write (#2212). Place, remove and the non-pose fields (catalog id, parent space, animation, visual, state, slots) still re-render. `move`/`rotate` replace the record, so take live placement from `ctx.scene.object.get(instanceId)` rather than the record's `position`.
|
|
1156
1162
|
- `useSelectionView` (function): function useSelectionView(members: readonly EntitySummaryDef[], options?: UseSelectionViewOptions): SelectionModel — The DATA/HOOK layer: summarize a selected-entity list into a live model with a focused primary, ordered members, same-kind groups, keyboard focus stepping, and a virtualization window. Rendering-agnostic — feed it to {@link SelectionPanel} or read the `view` directly.
|
|
1157
1163
|
- `useSequenceDirector` (function): function useSequenceDirector(director: SequenceDirector): SequenceDirectorBinding — Bind a {@link SequenceDirector} to a component: subscribe to its state, and while it is playing run a `requestAnimationFrame` loop that ticks the director each frame off the same injected clock — so a game drops in one hook and the cutscene advances itself. Returns the current playback state (playhead, progress, playing/done) plus bound `play`/`pause`/`skip`/`seek`/`stop`. Cue side effects belong on `director.onCue`, not here — the hook never interprets a cue.
|
|
1158
1164
|
- `useSession` (function): function useSession(): IdentitySource — ⚠ undocumented
|
|
@@ -1171,7 +1177,7 @@
|
|
|
1171
1177
|
- `useWaypoints` (function): function useWaypoints(source: WaypointTracker | readonly Waypoint[]): readonly Waypoint[] — Subscribe to a {@link WaypointTracker} and re-render on every change, returning its current waypoints. Pass a plain array instead and it is returned as-is (no subscription), so a game can drive the host from a tracker or from its own derived state.
|
|
1172
1178
|
- `useWorldBrowser` (function): function useWorldBrowser(options: { fetchSessions: () => Promise<readonly SessionListing[]>; filter?: MatchFilter; limit?: number; refreshMs?: number; }): WorldBrowserState — Polls a host-supplied session fetcher (e.g. createWsBackend().browse) and filters through matchmaking's browseSessions. fetchSessions must be identity-stable (wrap in useCallback at the call site) or every render refetches.
|
|
1173
1179
|
- `useWorldInvites` (function): function useWorldInvites(): WorldInvite[] — ⚠ undocumented
|
|
1174
|
-
- `useWorldItems` (function): function useWorldItems(): readonly WorldItemRecord[] —
|
|
1180
|
+
- `useWorldItems` (function): function useWorldItems(): readonly WorldItemRecord[] — Live dropped-item records: the returned list keeps one identity until an item spawns, is picked up or is removed. `worldItem.list()` allocates a new array per call and the shared store signal fires on every pose write, so a raw read re-rendered every marker each frame (#2175). A marker reads its own live pose imperatively (useFrame).
|
|
1175
1181
|
|
|
1176
1182
|
## @jgengine/react/accessibility
|
|
1177
1183
|
|
|
@@ -1249,6 +1255,13 @@
|
|
|
1249
1255
|
- `CameraShakeReadout` (interface): interface CameraShakeReadout — A live read of a camera-shake controller: current trauma `0..1` and the last game-owned `kind`.
|
|
1250
1256
|
- `useCameraShake` (function): function useCameraShake(controller: CameraShakeController, animate = true): CameraShakeReadout — Subscribe to a camera-shake controller and re-render on every change (add / decay / clear). Optionally drives `update()` on an animation frame so the trauma meter bleeds down live even when no game loop ticks the controller. Returns the current trauma + kind for a HUD readout.
|
|
1251
1257
|
|
|
1258
|
+
## @jgengine/react/canvasCover
|
|
1259
|
+
|
|
1260
|
+
- `CanvasCover` (interface): interface CanvasCover — A page-wide declaration that some surface paints over the whole game canvas.
|
|
1261
|
+
- `canvasCover` (const): const canvasCover: CanvasCover — The page's canvas cover. A full-screen menu (or a game's own opaque overlay) holds it; the shell stops drawing the world, its shadow passes and postfx while it is held, and draws again from the untouched scene when it lifts. A translucent scrim or a side panel must not hold it — anything the scene shows through has to keep drawing.
|
|
1262
|
+
- `useCanvasCover` (function): function useCanvasCover(declared: boolean): void — Declare the canvas fully covered while `declared` is true, and release on unmount.
|
|
1263
|
+
- `useCanvasCovered` (function): function useCanvasCovered(): boolean — True while a surface covers the canvas.
|
|
1264
|
+
|
|
1252
1265
|
## @jgengine/react/cards
|
|
1253
1266
|
|
|
1254
1267
|
- `CardFace` (function): function CardFace({ rank, suit, faceDown = false, width = 64, height = 90, className, style, children, }: { rank: Rank; suit: Suit; faceDown?: boolean; width?: number; height?: number; className?: string; style?: CSSProperties; children?: ReactNode; }): React.JSX.Element — A single playing-card face: rank + suit pips, or a patterned back when `faceDown`.
|
|
@@ -1473,9 +1486,9 @@
|
|
|
1473
1486
|
- `useDomEvent` (function): function useDomEvent<E>(resolveTarget: () => DomEventTarget | null, type: string, handler: (event: E) => void, options?: { capture?: boolean; passive?: boolean }): void — Attach a DOM event listener with automatic cleanup. `resolveTarget` runs inside the effect, so `() => window` and ref-reading resolvers are SSR-safe; return null to skip attaching. The handler is kept in a ref, so a fresh closure per render never re-binds the listener. Re-binds only when `type` or `options.capture`/`options.passive` change.
|
|
1474
1487
|
- `useEntityStat` (function): function useEntityStat(instanceId: string, statId: string): StatValue | null — ⚠ undocumented
|
|
1475
1488
|
- `useEventMeter` (function): function useEventMeter(meter: EventMeter, options?: AbilitySlotBindingOptions): EventMeterView — Bind a `createEventMeter` (`@jgengine/core/stats/eventMeter`) heat/streak gauge to a component — the react-render half of the ult/adrenaline and streak/combo meters (`event-meter` capability) that lets a HUD gauge re-render on tick without the game hand-rolling a `useEffect`/`setInterval` heartbeat around `meter.value()`.
|
|
1476
|
-
- `useFeed` (function): function useFeed({ action, limit }: { action: string; limit?: number }): FeedEntry[] —
|
|
1489
|
+
- `useFeed` (function): function useFeed({ action, limit }: { action: string; limit?: number }): FeedEntry[] — Recent feed entries for `action`, newest last: the list keeps one identity until an entry is pushed, so a HUD ticker or kill feed no longer re-renders on every pose write (#2216).
|
|
1477
1490
|
- `useFriendRequests` (function): function useFriendRequests(): FriendRequestEntry[] — ⚠ undocumented
|
|
1478
|
-
- `useFriends` (function): function useFriends(): FriendEntry[] —
|
|
1491
|
+
- `useFriends` (function): function useFriends(): FriendEntry[] — The local player's friends with their presence: the list keeps one identity until a request is accepted, a friend is removed or a friend's presence flips, so a friends HUD no longer re-renders on every pose write (#2216).
|
|
1479
1492
|
- `useGame` (function): function useGame(): { commands: Omit<GameContext["game"]["commands"], "run"> & { run(name: string, input: unknown): DispatchedCommandResult }; events: GameEvents } — UI command surface. Hosted commands return a promise; await it to display acknowledgement or rejection. Registration and runAs remain trusted local APIs.
|
|
1480
1493
|
- `useGameClock` (function): function useGameClock(): ClockSnapshot & { controls: SimClock } — ⚠ undocumented
|
|
1481
1494
|
- `useGamePhase` (function): function useGamePhase(): { phase: GamePhase; setPhase: (phase: GamePhase) => void } — Live run phase + a setter that also gates the shell's touch controls. `menu`/`paused`/`ended` hide the touch dock; `playing` shows it.
|
|
@@ -1493,18 +1506,18 @@
|
|
|
1493
1506
|
- `usePartyInvites` (function): function usePartyInvites(): PartyInviteEntry[] — ⚠ undocumented
|
|
1494
1507
|
- `usePlayer` (function): function usePlayer(): { userId: string; isNew: boolean } — ⚠ undocumented
|
|
1495
1508
|
- `usePresence` (function): function usePresence(userId: string): PresenceInfo — ⚠ undocumented
|
|
1496
|
-
- `useQuestJournal` (function): function useQuestJournal(): QuestInstance[] —
|
|
1509
|
+
- `useQuestJournal` (function): function useQuestJournal(): QuestInstance[] — The local player's journal: the list keeps one identity while the accepted quests, their status and their objective progress are unchanged, so a tracker HUD no longer re-renders on every pose write (#2216). Accepting, progressing, completing or revoking a quest re-renders once.
|
|
1497
1510
|
- `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.
|
|
1498
1511
|
- `useRoster` (function): function useRoster(userId?: string): readonly RosterEntry[] — ⚠ undocumented
|
|
1499
|
-
- `useSceneEntities` (function): function useSceneEntities(): readonly SceneEntity[] —
|
|
1512
|
+
- `useSceneEntities` (function): function useSceneEntities(): readonly SceneEntity[] — Live entities: the returned list keeps one identity until an entity spawns, despawns, is replaced or the store is hydrated — a pose write does not re-render its readers (#2212). `entity.list()` allocates a new array per write against an `Object.is` comparison, so every pose write re-rendered every HUD reading this hook. The records themselves are mutated in place, so a reader that needs a pose right now can still take `entity.position`; render-loop work reads it in `useFrame`.
|
|
1500
1513
|
- `useSceneEntityIds` (function): function useSceneEntityIds(): readonly string[] — Membership-only entity id list: the returned array keeps a stable identity across per-frame pose writes and only changes when an entity spawns, despawns, or the store is hydrated (#625). A marker mapped from these ids reads its own live pose imperatively (useFrame), so the actor tree no longer re-reconciles every frame. Prefer this over {@link useSceneEntities} for large scenes.
|
|
1501
1514
|
- `useSceneObjectIds` (function): function useSceneObjectIds(): readonly string[] — Membership-only object id list — the object counterpart of {@link useSceneEntityIds}; stable across move/rotate/setVisual, changes only on place/remove.
|
|
1502
|
-
- `useSceneObjects` (function): function useSceneObjects(): readonly SceneObject[] —
|
|
1515
|
+
- `useSceneObjects` (function): function useSceneObjects(): readonly SceneObject[] — Placed objects: the returned list keeps one identity across move and rotate writes, so a HUD reading it no longer re-renders on every pose write (#2212). Place, remove and the non-pose fields (catalog id, parent space, animation, visual, state, slots) still re-render. `move`/`rotate` replace the record, so take live placement from `ctx.scene.object.get(instanceId)` rather than the record's `position`.
|
|
1503
1516
|
- `useTarget` (function): function useTarget(fromInstanceId: string): string | null — The entity `fromInstanceId` currently targets (set by `ctx.scene.entity.setTarget` or `cycleTarget`), for a target frame.
|
|
1504
1517
|
- `useTicker` (function): function useTicker(hz = 10): number — Re-render at a steady rate. Returns a monotonically increasing tick count driven by a setInterval, for HUD elements that display wall-clock-derived values (cooldowns, cast bars, swing timers) without an engine subscription to hang off. `hz <= 0` disables the ticker.
|
|
1505
1518
|
- `useWorldBrowser` (function): function useWorldBrowser(options: { fetchSessions: () => Promise<readonly SessionListing[]>; filter?: MatchFilter; limit?: number; refreshMs?: number; }): WorldBrowserState — Polls a host-supplied session fetcher (e.g. createWsBackend().browse) and filters through matchmaking's browseSessions. fetchSessions must be identity-stable (wrap in useCallback at the call site) or every render refetches.
|
|
1506
1519
|
- `useWorldInvites` (function): function useWorldInvites(): WorldInvite[] — ⚠ undocumented
|
|
1507
|
-
- `useWorldItems` (function): function useWorldItems(): readonly WorldItemRecord[] —
|
|
1520
|
+
- `useWorldItems` (function): function useWorldItems(): readonly WorldItemRecord[] — Live dropped-item records: the returned list keeps one identity until an item spawns, is picked up or is removed. `worldItem.list()` allocates a new array per call and the shared store signal fires on every pose write, so a raw read re-rendered every marker each frame (#2175). A marker reads its own live pose imperatively (useFrame).
|
|
1508
1521
|
|
|
1509
1522
|
## @jgengine/react/hud
|
|
1510
1523
|
|
|
@@ -1791,7 +1804,7 @@
|
|
|
1791
1804
|
|
|
1792
1805
|
## @jgengine/react/menuScreen
|
|
1793
1806
|
|
|
1794
|
-
- `MenuScreen` (function): function MenuScreen({ open = true, transition = "fade", scrim = "dim", enterMs = 220, exitMs = 160, reducedMotion, label, role = "dialog", backdrop, className, style, contentClassName, contentStyle, children, onClosed, }: MenuScreenProps): ReactNode — One animated screen of a game front-end: full-bleed, laid out by the game, with an enter/exit animation that runs on transform/opacity only (no layout, no frame hitch) and unmounts when the exit finishes. The scrim is translucent and the optional `backdrop` slot paints behind the content, so a live 3D scene stays visible behind a title, pause or settings screen. Durations come from the caller or collapse to zero under reduced motion.
|
|
1807
|
+
- `MenuScreen` (function): function MenuScreen({ open = true, transition = "fade", scrim = "dim", coversCanvas = false, enterMs = 220, exitMs = 160, reducedMotion, label, role = "dialog", backdrop, className, style, contentClassName, contentStyle, children, onClosed, }: MenuScreenProps): ReactNode — One animated screen of a game front-end: full-bleed, laid out by the game, with an enter/exit animation that runs on transform/opacity only (no layout, no frame hitch) and unmounts when the exit finishes. The scrim is translucent and the optional `backdrop` slot paints behind the content, so a live 3D scene stays visible behind a title, pause or settings screen. Durations come from the caller or collapse to zero under reduced motion.
|
|
1795
1808
|
- `MenuScreenProps` (interface): interface MenuScreenProps — Props for {@link MenuScreen}.
|
|
1796
1809
|
- `MenuScreenScrim` (type): type MenuScreenScrim = "none" | "dim" | "blur" — How much a {@link MenuScreen} dims the scene behind it. `none` keeps a live 3D scene fully visible.
|
|
1797
1810
|
- `MenuScreenTransition` (type): type MenuScreenTransition = "fade" | "slide" | "scale" | "none" — Which motion a {@link MenuScreen} uses when it enters and leaves.
|
|
@@ -2051,7 +2064,7 @@
|
|
|
2051
2064
|
|
|
2052
2065
|
## @jgengine/react/titleMenu
|
|
2053
2066
|
|
|
2054
|
-
- `TitleMenu` (function): function TitleMenu({ title, eyebrow, tagline, items, onSelect, onBack, footer, controlHint, backdrop, children, open = true, transition = "slide", scrim = "dim", enterMs, exitMs, reducedMotion, onClosed, className, style, contentClassName, contentStyle, }: TitleMenuProps): ReactNode — A game front-end title screen: wordmark, tagline, the game's verbs in a keyboard/gamepad-navigable list, a footer and an optional backdrop. The engine owns navigation, focus and the enter/exit animation; the game owns the title markup, wording, verbs, art and every colour through HudTheme tokens. Pass the live scene (or artwork) as `backdrop` so the title renders over 3D instead of a blank page.
|
|
2067
|
+
- `TitleMenu` (function): function TitleMenu({ title, eyebrow, tagline, items, onSelect, onBack, footer, controlHint, backdrop, children, open = true, transition = "slide", scrim = "dim", coversCanvas, enterMs, exitMs, reducedMotion, onClosed, className, style, contentClassName, contentStyle, }: TitleMenuProps): ReactNode — A game front-end title screen: wordmark, tagline, the game's verbs in a keyboard/gamepad-navigable list, a footer and an optional backdrop. The engine owns navigation, focus and the enter/exit animation; the game owns the title markup, wording, verbs, art and every colour through HudTheme tokens. Pass the live scene (or artwork) as `backdrop` so the title renders over 3D instead of a blank page.
|
|
2055
2068
|
- `TitleMenuItem` (interface): interface TitleMenuItem — One verb on a {@link TitleMenu}: New Game, Continue, Settings, Credits, Quit.
|
|
2056
2069
|
- `TitleMenuProps` (interface): interface TitleMenuProps — Props for {@link TitleMenu}.
|
|
2057
2070
|
|
|
@@ -2178,7 +2191,7 @@
|
|
|
2178
2191
|
## @jgengine/shell/audio/eventFeedback
|
|
2179
2192
|
|
|
2180
2193
|
- `AnyFeedbackBinding` (type): type AnyFeedbackBinding = { [Name in FeedbackEventName]: FeedbackBinding<Name> }[FeedbackEventName] — Any row of the event mapping, whichever event it binds.
|
|
2181
|
-
- `DEFAULT_FEEDBACK_BINDINGS` (const): const DEFAULT_FEEDBACK_BINDINGS: readonly AnyFeedbackBinding[] — The default mapping: each engine-announced gameplay event to the cue it raises. Damage to the local player reads as hurt, damage anywhere else as a hit; a wave start has no event of its own, so a game announces it through `notify("wave")` or `attachWaveFeedback`.
|
|
2194
|
+
- `DEFAULT_FEEDBACK_BINDINGS` (const): const DEFAULT_FEEDBACK_BINDINGS: readonly AnyFeedbackBinding[] — The default mapping: each engine-announced gameplay event to the cue it raises. Damage to the local player reads as hurt, damage anywhere else as a hit; an event that carries a `position` (damage, death) pans and attenuates its cue, and one without still plays centred. A wave start has no event of its own, so a game announces it through `notify("wave")` or `attachWaveFeedback`.
|
|
2182
2195
|
- `DEFAULT_FEEDBACK_REACTIONS` (const): const DEFAULT_FEEDBACK_REACTIONS: Readonly<Record<FeedbackCue, FeedbackReaction>> — The default reactions. They are data: a game overrides one through `audio.feedbackCues` (sound) or `createEventFeedback({ reactions })` (haptics, shake, caption), and replaces the mapping through `bindings` without touching the shell.
|
|
2183
2196
|
- `EventFeedback` (interface): interface EventFeedback — The event-to-reaction seam. One instance per running world.
|
|
2184
2197
|
- `EventFeedbackOptions` (interface): interface EventFeedbackOptions — Options a game or host may override.
|
|
@@ -2197,6 +2210,13 @@
|
|
|
2197
2210
|
|
|
2198
2211
|
- `installGestureResume` (function): function installGestureResume(engine: Pick<AudioEngine, "resume">, target: Pick<Document, "addEventListener" | "removeEventListener"> | null = typeof document === "undefined" ? null : document): () => void — Resume the audio context on the first real user gesture. Chrome keeps a freshly created `AudioContext` suspended until the page sees a trusted input, so pressing a key, tapping or clicking is the only moment sound may start; the engine then flushes any cue it held. One listener set per game, removed on unmount.
|
|
2199
2212
|
|
|
2213
|
+
## @jgengine/shell/audio/hapticsRoute
|
|
2214
|
+
|
|
2215
|
+
- `HapticsRoute` (interface): interface HapticsRoute — Route haptic pulses to whatever the player can feel them on.
|
|
2216
|
+
- `HapticsRouteOptions` (interface): interface HapticsRouteOptions — The browser reach of {@link createHapticsRoute}; injected so the route runs headless.
|
|
2217
|
+
- `HapticsSink` (type): type HapticsSink = "gamepad" | "vibration" | "none" — Where a pulse actually landed: a gamepad, the device's own motor, or nowhere.
|
|
2218
|
+
- `createHapticsRoute` (function): function createHapticsRoute(pad: (pulse: HapticPulse) => void, options: HapticsRouteOptions = {}): HapticsRoute — One pulse in, whichever motor the player has: a connected gamepad takes it through the haptic channels the gamepad loop already drives, and a phone with no pad falls back to `navigator.vibrate`. Pad presence is cached and refreshed on connection events, so a hit never asks the browser for its pad list. Reduced motion does not silence this: see `eventFeedback`.
|
|
2219
|
+
|
|
2200
2220
|
## @jgengine/shell/audio/loopParams
|
|
2201
2221
|
|
|
2202
2222
|
- `MAX_LOOP_CUTOFF` (const): const MAX_LOOP_CUTOFF: 22050 — Highest live filter cutoff, Hz; the engine also caps it at the context's Nyquist frequency.
|
|
@@ -2726,8 +2746,9 @@
|
|
|
2726
2746
|
## @jgengine/shell/postfx/postfxOverlay
|
|
2727
2747
|
|
|
2728
2748
|
- `POSTFX_OVERLAY_USERDATA` (const): const POSTFX_OVERLAY_USERDATA: { readonly jgPostfxOverlay: true } — Spread onto a group's `userData` to mark its subtree as a screen/world overlay effect — additive glows, telegraphs, tracers, debug gizmos — that post-processing scene prepasses (GTAO's normal/depth render, DOF's depth render) must skip. Those prepasses render the scene with `overrideMaterial`, which turns transparent overlay quads into opaque stamps in the AO/DOF buffers (visible as black squares over hit sparks). The shell marks its own combat VFX and collision-debug mounts; games mark custom overlay effects the same way.
|
|
2729
|
-
- `hidePostfxOverlays` (function): function hidePostfxOverlays(scene: THREE.Object3D, out: THREE.Object3D[]): void — Hide every currently visible
|
|
2749
|
+
- `hidePostfxOverlays` (function): function hidePostfxOverlays(scene: THREE.Object3D, out: THREE.Object3D[], withUnmodelled = false): void — Hide every currently visible object `scene`'s prepasses must skip, recording what was hidden into `out` (cleared first, no allocation on the hot path) so {@link restorePostfxOverlays} can undo exactly that set. `withUnmodelled` also hides points and lines, which three's GTAO pass excludes from its normal buffer but a depth prepass keeps.
|
|
2730
2750
|
- `isPostfxOverlay` (function): function isPostfxOverlay(object: { userData?: Record<string, unknown> }): boolean — True when the object opted out of postfx scene prepasses via {@link POSTFX_OVERLAY_USERDATA}.
|
|
2751
|
+
- `releasePostfxOverlayTracking` (function): function releasePostfxOverlayTracking(scene: THREE.Object3D): void — Drop the cached set and its listeners for a scene that is going away, so a torn-down world does not keep its node graph reachable through this module.
|
|
2731
2752
|
- `restorePostfxOverlays` (function): function restorePostfxOverlays(hidden: THREE.Object3D[]): void — Restore visibility for the objects hidden by {@link hidePostfxOverlays} and clear the list.
|
|
2732
2753
|
|
|
2733
2754
|
## @jgengine/shell/postfx/stylizePass
|
|
@@ -3508,12 +3529,12 @@
|
|
|
3508
3529
|
|
|
3509
3530
|
## @jgengine/shell/vision/RevealVision
|
|
3510
3531
|
|
|
3511
|
-
- `RevealHighlights` (function): function RevealHighlights(props: RevealHighlightsProps): React.JSX.Element | null — Screen-space reveal effect (#115) — highlights tagged entities through occluders (Dark Sight / detective-vision / wallhack style). Renders with `depthTest: false` so the highlight draws over any wall standing between the origin and the revealed entity, rather than the usual depth-sorted scene.
|
|
3532
|
+
- `RevealHighlights` (function): function RevealHighlights(props: RevealHighlightsProps): React.JSX.Element | null — Screen-space reveal effect (#115) — highlights tagged entities through occluders (Dark Sight / detective-vision / wallhack style). Renders with `depthTest: false` so the highlight draws over any wall standing between the origin and the revealed entity, rather than the usual depth-sorted scene. Highlights track the origin and their targets every frame while enabled, so a disabled mode costs no renders.
|
|
3512
3533
|
- `RevealHighlightsProps` (interface): interface RevealHighlightsProps extends RevealVisionOptions { enabled: boolean; color?: string } — ⚠ undocumented · used by `RevealHighlights`: Screen-space reveal effect (#115) — highlights tagged entities through occluders (Dark Sight / detective-vision / wallhack style).
|
|
3513
3534
|
- `RevealScreenTint` (function): function RevealScreenTint({ enabled, color = "rgba(56, 189, 248, 0.16)", className }: RevealScreenTintProps): React.JSX.Element | null — Full-screen desaturating tint that reads as "vision mode is on" (Dark Sight / thermal / detective vision).
|
|
3514
3535
|
- `RevealScreenTintProps` (interface): interface RevealScreenTintProps { enabled: boolean; color?: string; className?: string } — ⚠ undocumented · used by `RevealScreenTint`: Full-screen desaturating tint that reads as "vision mode is on" (Dark Sight / thermal / detective vision).
|
|
3515
|
-
- `RevealVisionOptions` (interface): interface RevealVisionOptions { originEntityId?: string; radius: number; tags: readonly string[]; resolveTags: (entity: SceneEntity) => readonly string[] } — ⚠ undocumented · used by `useRevealHits`: Occlusion-ignoring tagged-entity radius query (#115), bound to the live scene.
|
|
3516
|
-
- `useRevealHits` (function): function useRevealHits(options: RevealVisionOptions): readonly RevealHit[] — Occlusion-ignoring tagged-entity radius query (#115), bound to the live scene.
|
|
3536
|
+
- `RevealVisionOptions` (interface): interface RevealVisionOptions { originEntityId?: string; radius: number; tags: readonly string[]; resolveTags: (entity: SceneEntity) => readonly string[] } — ⚠ undocumented · used by `useRevealHits`: Occlusion-ignoring tagged-entity radius query (#115), bound to the live scene and refreshed on render.
|
|
3537
|
+
- `useRevealHits` (function): function useRevealHits(options: RevealVisionOptions): readonly RevealHit[] — Occlusion-ignoring tagged-entity radius query (#115), bound to the live scene and refreshed on render.
|
|
3517
3538
|
|
|
3518
3539
|
## @jgengine/shell/vision/frustumSampleEqual
|
|
3519
3540
|
|
|
@@ -126,6 +126,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
126
126
|
|
|
127
127
|
- `isCameraOccluderTransparent` (function) · `import { isCameraOccluderTransparent } from "@jgengine/shell/camera"`
|
|
128
128
|
|
|
129
|
+
## canvas-cover — stop world rendering behind a full-screen surface while input, audio, the HUD and the menu keep running
|
|
130
|
+
|
|
131
|
+
- `canvasCover` (const) · `import { canvasCover } from "@jgengine/react"`
|
|
132
|
+
|
|
129
133
|
## caption-feed — bounded, self-expiring, deduped caption lines for the accessibility captions overlay
|
|
130
134
|
|
|
131
135
|
- `createCaptionFeed` (function) · `import { createCaptionFeed } from "@jgengine/shell/audio/captions"`
|
|
@@ -347,6 +351,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
347
351
|
- `buildGroundCoverageMask` (function) · `import { buildGroundCoverageMask } from "@jgengine/shell/terrain"`
|
|
348
352
|
- `createGroundEdgeFade` (function) · `import { createGroundEdgeFade } from "@jgengine/shell/terrain"`
|
|
349
353
|
|
|
354
|
+
## haptics-route — send a feedback pulse to the gamepad when one is connected, else to the phone's vibration motor
|
|
355
|
+
|
|
356
|
+
- `createHapticsRoute` (function) · `import { createHapticsRoute } from "@jgengine/shell/audio/hapticsRoute"`
|
|
357
|
+
|
|
350
358
|
## heavy-models — models over the per-model triangle budget, for perf probes and smoke assertions
|
|
351
359
|
|
|
352
360
|
- `heavyModels` (function) · `import { heavyModels } from "@jgengine/shell/render/modelLoad"`
|
|
@@ -94,6 +94,8 @@ The post chain itself is never hand-wired render passes. Set `defineGame({ postP
|
|
|
94
94
|
|
|
95
95
|
AO and depth-of-field prepasses reuse the beauty pass's shadow maps; they do not redraw shadow casters. Prepass failure restores shadow update policy and marked overlay visibility. The composer resizes passes through `setSize`, and graph cleanup calls each pass's `dispose` once; avoid reading a pass's internal targets or materials.
|
|
96
96
|
|
|
97
|
+
The Graphics tab offers a `postStages` toggle only for a stage the resolved chain can build: an `ao` or `bloom` authored `false`, `aa: "msaa"`/`false` (no SMAA pass), or an unauthored `dof` drops that row, because a toggle over a pass the composer never creates changes nothing. A game with no post chain gets none of the stage rows.
|
|
98
|
+
|
|
97
99
|
**Orbit camera occlusion:** the third-person orbit rig takes `camera: { collision: { enabled, padding?, minTargetDistance? } }` — a spring-arm that raycasts target→camera each frame and pulls the boom in past walls/terrain so the camera never clips inside geometry. Off by default (unchanged chase feel); enable it for any world with interiors or dense structures.
|
|
98
100
|
|
|
99
101
|
**Camera clearance:** `camera: { firstPerson: { collision } }` keeps the `first` rig's eye out of geometry the body can reach. It is on by default: the boom anchors `armBack` (1.5) behind the eye along the look ray, `clearance` defaults to the near plane plus 0.15, and a blocking surface pulls the camera back toward that anchor so the near plane stays clear. `false` restores the unchecked camera. A game that drives its own camera calls the same seam: `clearCameraBoom({ pivot, desired, query: sceneCameraQuery(ctx.scene.raycast), previousArm, deltaSeconds })` returns the clear `position` and the `arm` to pass back next frame. The query reads `ctx.scene.raycast`, so world solids, scene-object colliders and walls all block it without a new collision layer; entities and terrain stay out so the followed body and the ground cannot pull the boom.
|