@jgengine/react 0.38.0 → 0.38.2
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 +47 -0
- package/dist/gameMenu.d.ts +10 -3
- package/dist/gameMenu.js +17 -2
- package/dist/gameMenuSaves.js +37 -25
- package/dist/hudTheme.js +6 -2
- package/package.json +2 -2
- package/skills/jgengine/api.md +2 -2
- package/skills/jgengine-ui/api.md +26 -1
- package/skills/jgengine-ui/capabilities.md +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -26,6 +26,53 @@ 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.38.2
|
|
30
|
+
|
|
31
|
+
### Migrate
|
|
32
|
+
|
|
33
|
+
- **Bump lockstep SDK packages to `^0.38.2`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.35.2`; `@jgengine/github` is `0.25.2`.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- `requestSceneCapture` and `requestSceneThumbnail` (`@jgengine/shell/render/sceneCapture`) ask the live scene for a frame or a save-card thumbnail. A request waits for `SceneCaptureBinding` to mount, waits for the canvas to paint, retries a blank read and resolves with the image or a stated failure (`no-binding`, `no-frame`, `undecodable`), so a game stops polling for the binding and waiting frames before a save screenshot.
|
|
38
|
+
- `registerSceneCapture` (`@jgengine/shell/render/sceneCapture`) lets a host that owns its own canvas hand the capture path a reader.
|
|
39
|
+
- `createDownedState` (`@jgengine/core/combat/downed`) exposes `restore(next)`, the counterpart to `snapshot()`, so a saved bleed-out resumes with the same remaining seconds, revive progress, phase and banner timer instead of being rebuilt around a guessed countdown.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- Pin Games to the 0.38.1 adoption
|
|
44
|
+
- `GameMenu`'s `style` and `detailStyle` accept `--jg-*` custom properties (`GameMenuStyle`), so a game passes `{ "--jg-menu-surface-radius": "12px" }` without a cast.
|
|
45
|
+
- `GameMenu` sets `pointer-events: auto` on its own root while its painted backdrop stays `none`, so a menu inside a HUD wrapper that passes clicks through to the scene no longer has dead buttons.
|
|
46
|
+
- `check-stateful-ratchet` skips a factory carrying `// stateless: <reason>`, the named case for a catalog whose only closure state is an index of its own config. `createBuildSocketCatalog` declares it instead of being baselined.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- `bun run shoot` refuses a scene shot whose page has no 3D canvas, and scores rendered geometry inside the canvas region before writing the file. `bun run shoot <game> --view <name>` defaults to `--mode ui`, which renders the game's HUD preview over a CSS backdrop and creates no canvas; the whole-frame flat test was skipped without a region and the backdrop's full-screen paint withheld the viewport verdict, so a HUD-only frame was written with exit 0.
|
|
51
|
+
- `countStaticBatchDraws` (`@jgengine/shell/world/staticObjectBatches`) counts the draws the static batches submit, so a variant level with no instance in a cell and a cell the camera culled are no longer reported as draws. `submitInstanceCount` (`@jgengine/shell/render/submittedDraws`) is the one place the batch, scatter, proxy and authored-primitive paths set an instance count and its visibility, and three issues a draw only when both say there is something to draw.
|
|
52
|
+
- `createCastRunner.state()` carries the cast's `moveTolerance`, `moveDeadzone` and `movedTotal`, and `restore(next)` resumes it. A cast saved mid-fight used to come back with a fresh movement budget, so the caster could walk further than the ability allowed.
|
|
53
|
+
- `createDecayMeterSet.serialize()` / `restore(next)` carry each meter's value, base rate and `setRateModifier` multiplier. A save used to drop a tuned rate, so a cold biome's faster warmth drain reset to the config rate on load.
|
|
54
|
+
- `createAreaEffectField.restore(state)`, `createSelectionBookmarks.restore(snapshot)` and `createPlotPermissions.restore(snapshot)` are the state-in counterparts to their existing `serialize()` / `snapshot()`; restoring into a live field releases the members the saved state does not carry.
|
|
55
|
+
- `check-stateful-ratchet` names the round-trip gap separately and caps it: a `snapshot()` with no `restore()`/`reset()` fails as `round-trip` debt, `--write` refuses to raise that ceiling, and a group paid below its ceiling must lower it. `createDownedState` was baselined when the ratchet shipped and never revisited; the restores in this release take the baseline from 113 entries to 93 and the `round-trip` ceiling from 8 to 2.
|
|
56
|
+
|
|
57
|
+
## 0.38.1
|
|
58
|
+
|
|
59
|
+
### Migrate
|
|
60
|
+
|
|
61
|
+
- **Bump lockstep SDK packages to `^0.38.1`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.35.1`; `@jgengine/github` is `0.25.1`.
|
|
62
|
+
- Nothing to migrate. `variantLevels()` was renamed to `ModelVariantConfig.shadows` before any release shipped it, so no published version ever exported it and no consumer can be holding one. A level that wants to stop paying the near level's shadow cost writes `{ url, shadows: "none" }`; leaving `shadows` out keeps the model's own answer.
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- `ModelVariantConfig.shadows` (`@jgengine/core/world/modelLod`): a model variant level declares its own shadow participation as a plain field, `"inherit" | "cast" | "receive" | "both" | "none"`. `"inherit"`, the default, keeps the model's own `shadows`, so every existing model draws as before; a far level set to `"none"` leaves the shadow pass while still drawing its mesh.
|
|
67
|
+
- `ModelShadowMode` (`@jgengine/core/world/modelLod`) names the four-value shadow vocabulary, and `ModelConfig.shadows` uses it.
|
|
68
|
+
- `variantLevelModel` (`@jgengine/shell/render/singleVariantLod`) is the one place that resolves what a level draws and how it shadows; the single prop, batched prop and scatter paths all ask it, so a level's asset and its shadow answer cannot disagree.
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
|
|
72
|
+
- `SaveSlotsPane` cards are one box each: equal tracks, `grid-auto-rows: 1fr`, a card edge that reads over a bright scene, and a name that ellipsises instead of widening the card. A save with a long name and one with none produce the same card box.
|
|
73
|
+
- `GameMenu` puts the primary verbs on their own scrim, so their labels stay legible over whatever the live scene is showing instead of relying on a text shadow.
|
|
74
|
+
- The theme emits three menu tokens the slot card and the verb surface read: `--jg-menu-card-height` (`clamp(244px, 27vh, 300px)`), `--jg-menu-card-thumb` (`clamp(88px, 14vh, 160px)`) and `--jg-menu-surface-radius` (`8px`). A game overrides them in its own `HudTheme` instead of patching the menu components.
|
|
75
|
+
|
|
29
76
|
## 0.38.0
|
|
30
77
|
|
|
31
78
|
### Migrate
|
package/dist/gameMenu.d.ts
CHANGED
|
@@ -36,6 +36,11 @@ export interface GameMenuLogo {
|
|
|
36
36
|
}
|
|
37
37
|
/** How a {@link GameMenu} is used. `pause` is the same system at a smaller scale. */
|
|
38
38
|
export type GameMenuVariant = "title" | "pause";
|
|
39
|
+
/**
|
|
40
|
+
* Inline style for a menu surface that also accepts `--jg-*` theme tokens, so a game sets a token
|
|
41
|
+
* with `{ "--jg-menu-radius": "12px" }` instead of casting the object to `CSSProperties`.
|
|
42
|
+
*/
|
|
43
|
+
export type GameMenuStyle = CSSProperties & Record<`--${string}`, string | number>;
|
|
39
44
|
/** Props for {@link GameMenu}. */
|
|
40
45
|
export interface GameMenuProps {
|
|
41
46
|
/** Primary verbs, in focus order. */
|
|
@@ -84,9 +89,9 @@ export interface GameMenuProps {
|
|
|
84
89
|
/** Accessible name of the surface. Default `"Main menu"` (`"Paused"` for the pause variant). */
|
|
85
90
|
label?: string;
|
|
86
91
|
className?: string;
|
|
87
|
-
style?:
|
|
92
|
+
style?: GameMenuStyle;
|
|
88
93
|
detailClassName?: string;
|
|
89
|
-
detailStyle?:
|
|
94
|
+
detailStyle?: GameMenuStyle;
|
|
90
95
|
}
|
|
91
96
|
/**
|
|
92
97
|
* Lock the page scroll while `active`, so a fixed full-bleed surface is exactly the viewport and the
|
|
@@ -104,7 +109,9 @@ export declare function useViewportScrollLock(active: boolean): void;
|
|
|
104
109
|
*
|
|
105
110
|
* The engine owns the surface, layout, motion, focus order, sound and the save/character panes it
|
|
106
111
|
* exports next to this component. The game owns the art, the logo, the wording, the verbs and what
|
|
107
|
-
* each detail pane shows.
|
|
112
|
+
* each detail pane shows. The surface takes pointer input for itself and leaves its painted
|
|
113
|
+
* backdrop transparent to input, so a HUD wrapper that passes clicks through does not make the
|
|
114
|
+
* verbs dead; `style` and `detailStyle` accept `--jg-*` tokens without a cast.
|
|
108
115
|
*
|
|
109
116
|
* @capability game-menu full-bleed game menu — living background, token-sized logo, animated navigable verbs, detail pane, selection sound, pause variant, no page scrollbar
|
|
110
117
|
*/
|
package/dist/gameMenu.js
CHANGED
|
@@ -92,7 +92,9 @@ function logoNode(logo, title, reduced) {
|
|
|
92
92
|
*
|
|
93
93
|
* The engine owns the surface, layout, motion, focus order, sound and the save/character panes it
|
|
94
94
|
* exports next to this component. The game owns the art, the logo, the wording, the verbs and what
|
|
95
|
-
* each detail pane shows.
|
|
95
|
+
* each detail pane shows. The surface takes pointer input for itself and leaves its painted
|
|
96
|
+
* backdrop transparent to input, so a HUD wrapper that passes clicks through does not make the
|
|
97
|
+
* verbs dead; `style` and `detailStyle` accept `--jg-*` tokens without a cast.
|
|
96
98
|
*
|
|
97
99
|
* @capability game-menu full-bleed game menu — living background, token-sized logo, animated navigable verbs, detail pane, selection sound, pause variant, no page scrollbar
|
|
98
100
|
*/
|
|
@@ -217,6 +219,9 @@ export function GameMenu({ items, initialId, onSelect, onBack, onHighlight, deta
|
|
|
217
219
|
? "clamp(16px, 2.4vw, 34px) clamp(20px, 3.2vw, 56px)"
|
|
218
220
|
: "clamp(18px, 3.2vw, 56px) clamp(20px, 3.6vw, 64px)",
|
|
219
221
|
boxSizing: "border-box",
|
|
222
|
+
// The surface is an overlay: it takes its own input even when the HUD wrapper around it
|
|
223
|
+
// passes every pointer through to the scene.
|
|
224
|
+
pointerEvents: "auto",
|
|
220
225
|
color: "var(--jg-text, #f5f7fa)",
|
|
221
226
|
fontFamily: hudFont.body,
|
|
222
227
|
animation: reduced ? undefined : `jg-fade-in ${hudDuration("slow")} ${hudEase("easeOut")} both`,
|
|
@@ -266,7 +271,17 @@ export function GameMenu({ items, initialId, onSelect, onBack, onHighlight, deta
|
|
|
266
271
|
nav.containerProps.onKeyDown(event);
|
|
267
272
|
if (event.key === "ArrowDown" || event.key === "ArrowUp")
|
|
268
273
|
event.stopPropagation();
|
|
269
|
-
}, children: [_jsx("div", { "data-jg-game-menu-
|
|
274
|
+
}, children: [_jsx("div", { "data-jg-game-menu-nav-surface": "", "aria-hidden": "true", style: {
|
|
275
|
+
position: "absolute",
|
|
276
|
+
inset: "-16px -22px",
|
|
277
|
+
borderRadius: "var(--jg-menu-surface-radius, 8px)",
|
|
278
|
+
background: "linear-gradient(96deg, rgba(4,7,11,0.6) 0%, rgba(4,7,11,0.34) 56%, rgba(4,7,11,0) 100%)",
|
|
279
|
+
maskImage: "linear-gradient(180deg, transparent 0, #000 9%, #000 91%, transparent 100%)",
|
|
280
|
+
WebkitMaskImage: "linear-gradient(180deg, transparent 0, #000 9%, #000 91%, transparent 100%)",
|
|
281
|
+
backdropFilter: "blur(2px)",
|
|
282
|
+
WebkitBackdropFilter: "blur(2px)",
|
|
283
|
+
pointerEvents: "none",
|
|
284
|
+
} }), _jsx("div", { "data-jg-game-menu-rail": "", "aria-hidden": "true", style: {
|
|
270
285
|
position: "absolute",
|
|
271
286
|
left: 0,
|
|
272
287
|
right: 8,
|
package/dist/gameMenuSaves.js
CHANGED
|
@@ -14,15 +14,18 @@ export const SAVE_SLOT_CHARACTER_KEY = "character";
|
|
|
14
14
|
const cardSurface = {
|
|
15
15
|
position: "relative",
|
|
16
16
|
width: "100%",
|
|
17
|
+
minWidth: 0,
|
|
18
|
+
height: "var(--jg-menu-card-height, clamp(244px, 27vh, 300px))",
|
|
17
19
|
boxSizing: "border-box",
|
|
20
|
+
overflow: "hidden",
|
|
18
21
|
display: "flex",
|
|
19
22
|
flexDirection: "column",
|
|
20
23
|
gap: 8,
|
|
21
|
-
minWidth: 0,
|
|
22
24
|
padding: 10,
|
|
23
25
|
borderRadius: "var(--jg-menu-card-radius, 12px)",
|
|
24
|
-
background: "linear-gradient(180deg, rgba(16,22,30,0.
|
|
25
|
-
|
|
26
|
+
background: "linear-gradient(180deg, rgba(16,22,30,0.9), rgba(8,11,16,0.94))",
|
|
27
|
+
border: "1px solid rgba(255,255,255,0.14)",
|
|
28
|
+
boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.05), 0 14px 34px -18px rgba(0,0,0,0.9)",
|
|
26
29
|
backdropFilter: "blur(6px)",
|
|
27
30
|
color: "var(--jg-text, #f2f6fb)",
|
|
28
31
|
fontFamily: hudFont.body,
|
|
@@ -67,9 +70,9 @@ export function SaveSlotCard({ slot, now = Date.now, recent = false, onSelect, o
|
|
|
67
70
|
cursor: onSelect === undefined ? "default" : "pointer",
|
|
68
71
|
alignItems: "flex-start",
|
|
69
72
|
justifyContent: "flex-end",
|
|
70
|
-
border: "
|
|
73
|
+
border: "1px solid rgba(255,255,255,0.1)",
|
|
71
74
|
boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.07), inset 0 0 40px -18px rgba(125,211,252,0.4), 0 10px 26px -20px rgba(0,0,0,0.9)",
|
|
72
|
-
background: "linear-gradient(180deg, rgba(12,17,24,0.
|
|
75
|
+
background: "linear-gradient(180deg, rgba(12,17,24,0.82), rgba(6,9,13,0.9))",
|
|
73
76
|
...style,
|
|
74
77
|
}, children: [_jsx("div", { "data-save-slot-thumb": "", "data-empty": "", style: {
|
|
75
78
|
width: "100%",
|
|
@@ -94,7 +97,8 @@ export function SaveSlotCard({ slot, now = Date.now, recent = false, onSelect, o
|
|
|
94
97
|
}, children: [_jsxs("div", { "data-save-slot-thumb": "", style: {
|
|
95
98
|
position: "relative",
|
|
96
99
|
width: "100%",
|
|
97
|
-
|
|
100
|
+
flexShrink: 0,
|
|
101
|
+
height: "var(--jg-menu-card-thumb, clamp(88px, 14vh, 160px))",
|
|
98
102
|
borderRadius: 8,
|
|
99
103
|
overflow: "hidden",
|
|
100
104
|
background: "radial-gradient(120% 120% at 30% 20%, rgba(125,211,252,0.22), rgba(8,12,18,0.9))",
|
|
@@ -124,6 +128,10 @@ export function SaveSlotCard({ slot, now = Date.now, recent = false, onSelect, o
|
|
|
124
128
|
fontWeight: 800,
|
|
125
129
|
letterSpacing: "0.01em",
|
|
126
130
|
textShadow: "0 2px 12px rgba(0,0,0,0.8)",
|
|
131
|
+
maxWidth: "calc(100% - 16px)",
|
|
132
|
+
whiteSpace: "nowrap",
|
|
133
|
+
overflow: "hidden",
|
|
134
|
+
textOverflow: "ellipsis",
|
|
127
135
|
}, children: slot.name ?? slot.id }), recent ? (_jsx("span", { "data-save-slot-recent": "", style: {
|
|
128
136
|
position: "absolute",
|
|
129
137
|
top: 6,
|
|
@@ -231,6 +239,10 @@ export function SaveSlotsPane({ model, onContinue, onLoad, onNew, onDelete, head
|
|
|
231
239
|
}, children: ["Continue ", recent.name ?? recent.id] }))] }), _jsx("div", { ...nav.containerProps, "data-save-slots-grid": "", style: {
|
|
232
240
|
display: "grid",
|
|
233
241
|
gridTemplateColumns: columns === "auto" ? "repeat(auto-fill, minmax(230px, 1fr))" : `repeat(${columns}, minmax(0, 1fr))`,
|
|
242
|
+
// Every card gets the same box: equal tracks, equal rows, and content that cannot widen it.
|
|
243
|
+
gridAutoRows: "auto",
|
|
244
|
+
alignItems: "stretch",
|
|
245
|
+
flex: 1,
|
|
234
246
|
gap: hudSpace("md"),
|
|
235
247
|
minHeight: 0,
|
|
236
248
|
overflowY: "auto",
|
|
@@ -240,25 +252,25 @@ export function SaveSlotsPane({ model, onContinue, onLoad, onNew, onDelete, head
|
|
|
240
252
|
paddingBottom: 4,
|
|
241
253
|
}, children: slots.map((slot) => {
|
|
242
254
|
const active = nav.activeId === slot.id;
|
|
243
|
-
return (_jsx(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
255
|
+
return (_jsx(SaveSlotCard, { slot: slot, now: now, recent: recent !== null && recent.id === slot.id, ...(onLoad === undefined && onNew === undefined
|
|
256
|
+
? {}
|
|
257
|
+
: {
|
|
258
|
+
onSelect: (chosen) => {
|
|
259
|
+
if (chosen.empty)
|
|
260
|
+
onNew?.(chosen);
|
|
261
|
+
else
|
|
262
|
+
onLoad?.(chosen);
|
|
263
|
+
},
|
|
264
|
+
}), ...(onDelete === undefined ? {} : { onDelete }), style: {
|
|
265
|
+
minWidth: 0,
|
|
266
|
+
...(active
|
|
267
|
+
? {
|
|
268
|
+
boxShadow: "inset 0 0 0 1px var(--jg-accent, #7dd3fc), 0 0 0 1px rgba(125,211,252,0.5), 0 18px 40px -18px rgba(0,0,0,0.95)",
|
|
269
|
+
transform: "translateY(-2px)",
|
|
270
|
+
transition: `transform ${hudDuration("fast")} ${hudEase("spring")}`,
|
|
271
|
+
}
|
|
272
|
+
: {}),
|
|
273
|
+
} }, slot.id));
|
|
262
274
|
}) })] }));
|
|
263
275
|
}
|
|
264
276
|
/**
|
package/dist/hudTheme.js
CHANGED
|
@@ -105,10 +105,14 @@ export function hudThemeVars(theme) {
|
|
|
105
105
|
"--jg-frame-bg": frame.bg,
|
|
106
106
|
"--jg-frame-border": frame.border,
|
|
107
107
|
"--jg-frame-radius": frame.radius,
|
|
108
|
-
// The game menu's
|
|
109
|
-
//
|
|
108
|
+
// The game menu's design tokens: a logo sized off the display step, card corners that follow the
|
|
109
|
+
// frame radius, and the card's fixed height and thumbnail band, so a slot card is the same box
|
|
110
|
+
// whatever it holds and a game can retune the menu from its theme.
|
|
110
111
|
"--jg-menu-logo-size": "clamp(30px, 4.6vw, 66px)",
|
|
111
112
|
"--jg-menu-card-radius": frame.radius,
|
|
113
|
+
"--jg-menu-card-height": "clamp(244px, 27vh, 300px)",
|
|
114
|
+
"--jg-menu-card-thumb": "clamp(88px, 14vh, 160px)",
|
|
115
|
+
"--jg-menu-surface-radius": "8px",
|
|
112
116
|
"--jg-frame-glow": frame.glow,
|
|
113
117
|
"--jg-slot-bg": slot.bg,
|
|
114
118
|
"--jg-slot-border": slot.border,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/react",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.2",
|
|
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.
|
|
38
|
+
"@jgengine/core": "^0.38.2"
|
|
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.38.
|
|
9
|
+
- `VERSION` (const): const VERSION: "0.38.2" — 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.38.
|
|
288
|
+
- `VERSION` (const): const VERSION: "0.38.2" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
289
289
|
|
|
290
290
|
## @jgengine/core/runtime/adapter
|
|
291
291
|
|
|
@@ -795,6 +795,7 @@
|
|
|
795
795
|
- `GameMenuSoundConfig` (interface): interface GameMenuSoundConfig — How {@link createGameMenuSound} is tuned.
|
|
796
796
|
- `GameMenuSoundKind` (type): type GameMenuSoundKind = "move" | "confirm" | "back" — The short, quiet click a menu makes when the selection moves or is confirmed. Generated with an oscillator instead of shipped as an audio file, so a game gets menu feedback without authoring a sound and the whole thing unmounts with the screen.
|
|
797
797
|
- `GameMenuSoundState` (interface): interface GameMenuSoundState — The sound player's readable state: on/off, gain, and whether audio is actually running.
|
|
798
|
+
- `GameMenuStyle` (type): type GameMenuStyle = CSSProperties & Record<`--${string}`, string | number> — Inline style for a menu surface that also accepts `--jg-*` theme tokens, so a game sets a token with `{ "--jg-menu-radius": "12px" }` instead of casting the object to `CSSProperties`.
|
|
798
799
|
- `GameMenuVariant` (type): type GameMenuVariant = "title" | "pause" — How a {@link GameMenu} is used. `pause` is the same system at a smaller scale.
|
|
799
800
|
- `GamePauseMenu` (function): function GamePauseMenu(props: Omit<GameMenuProps, "variant"> & { onResume?: () => void }): ReactNode — A pause menu built from {@link GameMenu}: the same surface and navigation at a smaller scale, so a game composes one menu system rather than a second one. Escape (or B/Start) resumes.
|
|
800
801
|
- `GamePreviewComponent` (type): type GamePreviewComponent = ComponentType<GamePreviewProps> — ⚠ undocumented
|
|
@@ -1483,6 +1484,7 @@
|
|
|
1483
1484
|
- `GameMenuItem` (interface): interface GameMenuItem — A game's front-end menu: a full-bleed surface over a living background, a logo, primary verbs on the left that animate as the selection moves, and a detail pane on the right that changes with the highlighted verb. It is the block a main menu, a pause menu and their sub-screens are built from, so a game composes one menu system instead of a stack of modal panels.
|
|
1484
1485
|
- `GameMenuLogo` (interface): interface GameMenuLogo — The logo slot. Give it `image` for art, or `text` for a styled wordmark.
|
|
1485
1486
|
- `GameMenuProps` (interface): interface GameMenuProps — Props for {@link GameMenu}.
|
|
1487
|
+
- `GameMenuStyle` (type): type GameMenuStyle = CSSProperties & Record<`--${string}`, string | number> — Inline style for a menu surface that also accepts `--jg-*` theme tokens, so a game sets a token with `{ "--jg-menu-radius": "12px" }` instead of casting the object to `CSSProperties`.
|
|
1486
1488
|
- `GameMenuVariant` (type): type GameMenuVariant = "title" | "pause" — How a {@link GameMenu} is used. `pause` is the same system at a smaller scale.
|
|
1487
1489
|
- `GamePauseMenu` (function): function GamePauseMenu(props: Omit<GameMenuProps, "variant"> & { onResume?: () => void }): ReactNode — A pause menu built from {@link GameMenu}: the same surface and navigation at a smaller scale, so a game composes one menu system rather than a second one. Escape (or B/Start) resumes.
|
|
1488
1490
|
- `useViewportScrollLock` (function): function useViewportScrollLock(active: boolean): void — Lock the page scroll while `active`, so a fixed full-bleed surface is exactly the viewport and the document behind it never gains a scrollbar. Nested and repeated locks are counted, and the previous inline styles are restored when the last one releases.
|
|
@@ -2115,6 +2117,10 @@
|
|
|
2115
2117
|
- `TalentTree` (function): function TalentTree<TStat extends string = string>({ view: viewProp, nodes, tree, onLearn, icon, label, branchLabel, title = "Talents", variation = "themed", nodeSize = 54, columnGap = 30, tierGap = 46, showPoints = true, className, style, }: TalentTreeProps<TStat>): ReactNode — Drop-in talent / skill-tree widget over the existing progression model (`@jgengine/core/game/talents` + its `talentTreeView` selector). The game passes its node definitions and a live `createTalentTree` instance; the widget lays nodes out by branch column and prerequisite-depth tier, draws SVG prerequisite edges, styles each node learned/available/locked/maxed, shows an icon + rank badge, and calls `onLearn(nodeId)` when the player clicks a node that is currently allocatable. All layout, edge-drawing, and eligibility come from the model/selector — the widget never re-derives topology. Unskinned and HudTheme-token driven; node ids/branches are opaque game data the game supplies icons and labels for.
|
|
2116
2118
|
- `TalentTreeProps` (interface): interface TalentTreeProps<TStat extends string = string> — Props for {@link TalentTree}.
|
|
2117
2119
|
|
|
2120
|
+
## @jgengine/react/testFixtures
|
|
2121
|
+
|
|
2122
|
+
- `MENU_THEME_STYLE` (const): const MENU_THEME_STYLE: GameMenuStyle — Menu theme tokens passed through the `style` prop with no cast. This file is type-checked and kept out of the build, so the compiler proves the prop accepts `--jg-*` custom properties on every typecheck while nothing ships from here.
|
|
2123
|
+
|
|
2118
2124
|
## @jgengine/react/tileLayerPreview
|
|
2119
2125
|
|
|
2120
2126
|
- `TileLayerPreview` (function): function TileLayerPreview({ className }: { className?: string }): React.JSX.Element — Deterministic canvas-backed tile-layer fixture for textured tile presentation evidence.
|
|
@@ -3078,9 +3084,28 @@
|
|
|
3078
3084
|
## @jgengine/shell/render/sceneCapture
|
|
3079
3085
|
|
|
3080
3086
|
- `CaptureRenderer` (interface): interface CaptureRenderer — The bit of a WebGL renderer scene capture needs — its backing `<canvas>`.
|
|
3081
|
-
- `
|
|
3087
|
+
- `SceneCapture` (type): type SceneCapture = () => string | null — Reads the current scene frame as an image data URL, or null when the frame cannot be read.
|
|
3088
|
+
- `SceneCaptureBinding` (function): function SceneCaptureBinding({ bind }: { bind?: (capture: () => string | null) => void } = {}): null — In-`<Canvas>` binder that hands the scene-capture function out to HUD code living outside the reconciler (a photo-mode Capture button, a save slot's thumbnail). Mount it inside the game's `WorldOverlay`; the reader is registered for `requestSceneCapture`/`requestSceneThumbnail`, which wait for this mount and the first painted frame. `bind` is for code that wants the function directly and can be left out when a game only requests captures. Renders nothing.
|
|
3089
|
+
- `SceneCaptureFailure` (type): type SceneCaptureFailure = "no-binding" | "no-frame" | "undecodable" — Why a capture request produced no image.
|
|
3090
|
+
- `SceneCaptureRequestOptions` (interface): interface SceneCaptureRequestOptions — How a capture request waits and retries.
|
|
3091
|
+
- `SceneCaptureResult` (type): type SceneCaptureResult = | { ok: true; capture: string } | { ok: false; reason: SceneCaptureFailure } — A captured frame, or the stated reason there is none.
|
|
3092
|
+
- `SceneThumbnailResult` (type): type SceneThumbnailResult = | { ok: true; thumbnail: string } | { ok: false; reason: SceneCaptureFailure } — A save-card thumbnail, or the stated reason there is none.
|
|
3082
3093
|
- `captureCanvas` (function): function captureCanvas(gl: CaptureRenderer): string | null — Read the current frame to a PNG data URL. Requires the R3F `<Canvas>` to have been created with `gl={{ preserveDrawingBuffer: true }}` (the shell's game canvas already is); returns null if the backing canvas can't be read.
|
|
3083
3094
|
- `downloadImage` (function): function downloadImage(dataUrl: string, filename = "screenshot.png"): void — Trigger a browser download of an image data URL (the photo-mode "save" action).
|
|
3095
|
+
- `registerSceneCapture` (function): function registerSceneCapture(capture: SceneCapture | null): () => void — Hand the capture path a reader for the live frame, or `null` to retire it. The returned function unbinds only that reader, so a canvas that remounts cannot be unbound by its predecessor.
|
|
3096
|
+
- `requestSceneCapture` (function): function requestSceneCapture(options: SceneCaptureRequestOptions = {}): Promise<SceneCaptureResult> — Request the current scene frame. Resolves once a binding exists and the canvas has painted, or with the reason no frame could be read; it never polls and never rejects.
|
|
3097
|
+
- `requestSceneThumbnail` (function): function requestSceneThumbnail(options: SceneCaptureRequestOptions & SaveThumbnailOptions = {}): Promise<SceneThumbnailResult> — Request a save-card thumbnail from the live scene: the shared capture request, then `shrinkCapture`. Pass what the slot card stores; omit everything for a 320x180 JPEG.
|
|
3098
|
+
|
|
3099
|
+
## @jgengine/shell/render/sceneCaptureRequest
|
|
3100
|
+
|
|
3101
|
+
- `SceneCapture` (type): type SceneCapture = () => string | null — Reads the current scene frame as an image data URL, or null when the frame cannot be read.
|
|
3102
|
+
- `SceneCaptureFailure` (type): type SceneCaptureFailure = "no-binding" | "no-frame" | "undecodable" — Why a capture request produced no image.
|
|
3103
|
+
- `SceneCaptureRequestOptions` (interface): interface SceneCaptureRequestOptions — How a capture request waits and retries.
|
|
3104
|
+
- `SceneCaptureResult` (type): type SceneCaptureResult = | { ok: true; capture: string } | { ok: false; reason: SceneCaptureFailure } — A captured frame, or the stated reason there is none.
|
|
3105
|
+
- `SceneThumbnailResult` (type): type SceneThumbnailResult = | { ok: true; thumbnail: string } | { ok: false; reason: SceneCaptureFailure } — A save-card thumbnail, or the stated reason there is none.
|
|
3106
|
+
- `registerSceneCapture` (function): function registerSceneCapture(capture: SceneCapture | null): () => void — Hand the capture path a reader for the live frame, or `null` to retire it. The returned function unbinds only that reader, so a canvas that remounts cannot be unbound by its predecessor.
|
|
3107
|
+
- `requestSceneCapture` (function): function requestSceneCapture(options: SceneCaptureRequestOptions = {}): Promise<SceneCaptureResult> — Request the current scene frame. Resolves once a binding exists and the canvas has painted, or with the reason no frame could be read; it never polls and never rejects.
|
|
3108
|
+
- `requestSceneThumbnail` (function): function requestSceneThumbnail(options: SceneCaptureRequestOptions & SaveThumbnailOptions = {}): Promise<SceneThumbnailResult> — Request a save-card thumbnail from the live scene: the shared capture request, then `shrinkCapture`. Pass what the slot card stores; omit everything for a 320x180 JPEG.
|
|
3084
3109
|
|
|
3085
3110
|
## @jgengine/shell/render/shaderWarmup
|
|
3086
3111
|
|
|
@@ -805,6 +805,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
805
805
|
|
|
806
806
|
- `RainImpactField` (function) · `import { RainImpactField } from "@jgengine/shell/weather"`
|
|
807
807
|
|
|
808
|
+
## request-scene-capture — request the live scene frame, waiting for a late binding and the first painted frame
|
|
809
|
+
|
|
810
|
+
- `requestSceneCapture` (function) · `import { requestSceneCapture } from "@jgengine/shell/render/sceneCapture"`
|
|
811
|
+
|
|
812
|
+
## request-scene-thumbnail — request a save-card thumbnail from the live scene, or a stated failure
|
|
813
|
+
|
|
814
|
+
- `requestSceneThumbnail` (function) · `import { requestSceneThumbnail } from "@jgengine/shell/render/sceneCapture"`
|
|
815
|
+
|
|
808
816
|
## resolve-game-look — expand a look preset into concrete lighting/backdrop/post knobs
|
|
809
817
|
|
|
810
818
|
- `resolveGameLook` (function) · `import { resolveGameLook } from "@jgengine/core/ui"`
|
|
@@ -821,7 +829,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
821
829
|
|
|
822
830
|
- `SaveSlotsPane` (function) · `import { SaveSlotsPane } from "@jgengine/react"`
|
|
823
831
|
|
|
824
|
-
## scene-capture-binding — expose the in-Canvas scene-capture function to outside-Canvas HUD (photo mode)
|
|
832
|
+
## scene-capture-binding — expose the in-Canvas scene-capture function to outside-Canvas HUD (photo mode, save thumbnails)
|
|
825
833
|
|
|
826
834
|
- `SceneCaptureBinding` (function) · `import { SceneCaptureBinding } from "@jgengine/shell/render/sceneCapture"`
|
|
827
835
|
|