@jgengine/core 0.30.0 → 0.32.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 +51 -0
- package/dist/ai/director/pressure.d.ts +10 -9
- package/dist/ai/director/pressure.js +9 -8
- package/dist/audio/feedbackCues.d.ts +30 -0
- package/dist/audio/feedbackCues.js +119 -0
- package/dist/game/events.d.ts +11 -0
- package/dist/game/gameSave.d.ts +6 -0
- package/dist/game/gameSave.js +6 -0
- package/dist/game/playableGame.d.ts +3 -0
- package/dist/game/saveStore.d.ts +7 -0
- package/dist/game/saveStore.js +35 -0
- package/dist/gameplay.d.ts +1 -1
- package/dist/gameplay.js +1 -1
- package/dist/item/affix.d.ts +2 -0
- package/dist/loot/drop.d.ts +47 -0
- package/dist/loot/drop.js +57 -0
- package/dist/loot/haul.d.ts +78 -0
- package/dist/loot/haul.js +126 -0
- package/dist/loot/lootGen.d.ts +7 -1
- package/dist/loot/lootGen.js +24 -5
- package/dist/loot/salvage.d.ts +74 -0
- package/dist/loot/salvage.js +68 -0
- package/dist/loot/vendor.d.ts +76 -0
- package/dist/loot/vendor.js +72 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +45 -1
- package/dist/mission/mission.d.ts +24 -0
- package/dist/mission/mission.js +28 -3
- package/dist/runtime/gameContext.js +3 -0
- package/dist/runtime/gameContextTypes.d.ts +16 -0
- package/package.json +1 -1
- package/skills/jgengine/api.md +4 -3
- package/skills/jgengine-gameplay/SKILL.md +10 -1
- package/skills/jgengine-gameplay/api.md +53 -3
- package/skills/jgengine-gameplay/capabilities.md +40 -0
- package/skills/jgengine-world/api.md +14 -4
- package/skills/jgengine-world/capabilities.md +4 -0
package/dist/meta/changelog.js
CHANGED
|
@@ -1,7 +1,51 @@
|
|
|
1
1
|
/** Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating. */
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.32.0";
|
|
3
3
|
/** Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`). */
|
|
4
4
|
export const CHANGELOG = {
|
|
5
|
+
"0.32.0": {
|
|
6
|
+
migrate: [
|
|
7
|
+
"Bump lockstep SDK packages to `^0.32.0`: `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.29.0`; `@jgengine/github` is `0.19.0`.",
|
|
8
|
+
],
|
|
9
|
+
added: [
|
|
10
|
+
"Audio mixing grew a mute that composes with gain: `AudioEngine.setBusMute` / `setMasterMute` keep the player's volume while silencing a bus, `masterLevel()` / `busLevel()` report the composed value, and Settings carries a mute row per bus plus \"Mute all\" (`sound.muted`, `sound.bus.<id>.muted`). `AudioEngine.play` and `playOneShot` hold cues in a bounded 16-slot queue while the browser keeps the context suspended and play them on the first gesture instead of dropping them; `installGestureResume` wires that gesture for both presentation paths.",
|
|
11
|
+
"`AudioEngine.measure()` reads master RMS and peak from a lazily built analyser, and `installAudioProbe` publishes `window.__jgengineAudio` so a headless capture can prove sound with a measured level rather than a screenshot.",
|
|
12
|
+
"`@jgengine/core/audio/feedbackCues` ships seven procedural reactions - `hit`, `hurt`, `kill`, `pickup`, `objective`, `wave`, `deny` - on an `sfx` bus, merged into every game's catalog as `feel.<cue>`. A game sound with that id replaces one; `audio.feedbackCues` replaces or silences them.",
|
|
13
|
+
"`@jgengine/shell/audio/eventFeedback` maps the events the engine already announces to those reactions: `combat.damage` (local player reads as hurt, anything else as a hit), `entity.died`, `worldItem.picked_up`, `quest.completed`, `stat.levelUp` and `command.refused`. Each row carries a sound, a gamepad pulse, camera trauma and a caption; the mapping and reactions are data (`DEFAULT_FEEDBACK_BINDINGS`, `DEFAULT_FEEDBACK_REACTIONS`) a game can replace, cooldowns fold a burst into one impact, reduced motion keeps sound and rumble but drops the camera kick, and the resolved reaction is pooled so handling an event allocates nothing per frame. `attachWaveFeedback(runner, ctx.game.feedback)` announces each new wave.",
|
|
14
|
+
"`ctx.game.feedback.cue(cue, caption?)` announces a moment no engine event covers and emits `feedback.cue`.",
|
|
15
|
+
"`@jgengine/shell/audio/captions` and `CaptionOverlay` render a bounded, deduping, self-expiring caption line under the accessibility captions setting; `FeedbackHost` mounts the whole layer - feedback mapping, gesture resume, audio probe and captions - for both the 3D and HUD presentation paths.",
|
|
16
|
+
],
|
|
17
|
+
changed: [
|
|
18
|
+
"The CI gate runs nine test shards instead of ten, and a `types` job whose stages overlap, so a docs-only pull request fits the account's 20-job runner cap with room for another run beside it. `scripts/test-shard.ts` owns which files each shard runs and what it prepares first; `bun scripts/type-shard.ts` runs the `check-types` plan two stages at a time and then the tsc fan-out alone.",
|
|
19
|
+
],
|
|
20
|
+
removed: [],
|
|
21
|
+
},
|
|
22
|
+
"0.31.0": {
|
|
23
|
+
migrate: [
|
|
24
|
+
"Bump lockstep SDK packages to `^0.31.0`: `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.28.0`; `@jgengine/github` is `0.18.0`.",
|
|
25
|
+
],
|
|
26
|
+
added: [
|
|
27
|
+
"`createAgent` (`@jgengine/core/agents/agent`) runs one resident's whole loop on the world clock: needs decay through `sim/needs`, a utility brain picks among the targets the game offers, an authored day plan says where the agent belongs, and the agent falls back to a declared target when its pick disappears. Mood, action and the far flag snapshot and restore with the needs.",
|
|
28
|
+
"`createMoodReader` / `readMood` (`@jgengine/core/agents/mood`) derive a 0-100 mood and a caller-labelled tier from weighted need satisfaction, plus named sources a queue, a litter patch or a cold snap raises and clears.",
|
|
29
|
+
"`createAgentSchedule` (`@jgengine/core/agents/schedule`) resolves an authored day plan through `sim/routine` and adds a work beat: a stop names the places it patrols, so a janitor is an agent with a route rather than a special case. Off-hours behaviour is a stop without a beat.",
|
|
30
|
+
"`createRelationshipBook` (`@jgengine/core/agents/relationships`) keeps undirected familiarity and opinion per pair. A social action moves both and reports the milestones crossed, friends prefer each other's company, and a bonded pair refuses to split. Pair keys come from `relation/keyedValues` and crossings from `relation/thresholds`.",
|
|
31
|
+
"`GameSave.deleteSlot(slot)` and `SaveStore.deleteSlot(slot)` remove one slot's payload and drop it from the slot index; they resolve `true` when a document was removed and `false` for an unknown slot. Deleting the active slot resets the live state to the initial document. `load()` no longer lists a slot that holds nothing, so a deleted slot cannot return.",
|
|
32
|
+
"`HudPanel` takes `lane` (`@jgengine/react`): blocks naming the same anchor and lane stack without overlap, lanes in one anchor sit side by side, and `resolveHudLanes` resolves the rectangles. A moved panel leaves its lane and keeps its authored placement.",
|
|
33
|
+
"`hudBlockOverlaps` and `formatHudOverlapReport` (`@jgengine/react`) name every pair of rendered HUD blocks whose rectangles intersect, with both ids and the viewport; `HudCanvas` warns with that report in a dev build.",
|
|
34
|
+
"`rollDrop` (`@jgengine/core/loot/drop`) rolls one deterministic item from a seed and `compareToEquipped` turns it into per-stat deltas against worn gear with a weighted verdict a HUD can render.",
|
|
35
|
+
"`createRunHaul` (`@jgengine/core/loot/haul`) gates carried loot by weight through the shared encumbrance rules, banks what the persistent stash fits, and discards the run's haul when the game calls `fail`.",
|
|
36
|
+
"`salvageItem` and `createItemLedger` (`@jgengine/core/loot/salvage`) break an item down for the currency its level and rarity say and claim the instance, so a salvaged item cannot be salvaged, equipped or sold again.",
|
|
37
|
+
"`stockVendor` and `createVendorRotation` (`@jgengine/core/loot/vendor`) fill a `ShopStock` with rolled items priced through the economy wallet, with a run-count restock rule the game authors.",
|
|
38
|
+
"`markerProximity` on `createMissionRuntime` picks the plane a marker proximity test compares in: `\"horizontal\"` (default) measures ground distance and allows the actor a `tolerance` (default 8 m) above or below the marker, `\"spatial\"` is the straight 3D check for worlds with stacked platforms. `MissionMarkerPlane`, `MissionMarkerProximity` and `DEFAULT_MARKER_TOLERANCE` are exported with it.",
|
|
39
|
+
],
|
|
40
|
+
changed: [
|
|
41
|
+
"`evaluatePressure` (`@jgengine/core/ai/director/pressure`) reads pressure as player control: a healthy player further into a run is leaned on harder, a hurt player is eased off, and every effect stays inside the rule's `maxTier`.",
|
|
42
|
+
"Loot bases and affixes take `minLevel`, so `rollLoot` only draws from entries at or below the item level. Loot bases take `mass`; rolled items carry it for carry checks.",
|
|
43
|
+
"`HudCanvas` resolved its host with `canvas.parentElement`, which is `HudThemeRoot`'s `display: contents` element and measures 0x0 — the boundary overlay, the HUD-fit measurement and the new overlap report never ran under the standard HUD root. It now uses the nearest ancestor with a real box.",
|
|
44
|
+
"The HUD boundary check returned early whenever nothing overflowed, so a later report in the same pass could never run.",
|
|
45
|
+
"`reach`, `interact` and the `left-area` fail condition compared a marker in 3D, so a marker authored at `y=0` under rising ground was unreachable. They now read the same plane.",
|
|
46
|
+
],
|
|
47
|
+
removed: [],
|
|
48
|
+
},
|
|
5
49
|
"0.30.0": {
|
|
6
50
|
migrate: [
|
|
7
51
|
"Bump lockstep SDK packages to `^0.30.0`: `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.27.0`; `@jgengine/github` is `0.17.0`.",
|
|
@@ -341,6 +341,8 @@ export interface MissionRuntimeConfig {
|
|
|
341
341
|
readonly missions: readonly MissionDef[];
|
|
342
342
|
/** Marker positions resolved from the authored scene, by marker id. See `missionMarkers`. */
|
|
343
343
|
readonly markers?: Readonly<Record<string, ScenePoint3>>;
|
|
344
|
+
/** How marker proximity is measured for `reach`, `interact` and `left-area`. Default horizontal. */
|
|
345
|
+
readonly markerProximity?: MissionMarkerProximity;
|
|
344
346
|
/** Actor id used for a `pursue` objective that names no `pursuer`. */
|
|
345
347
|
readonly defaultPursuer?: string;
|
|
346
348
|
/** `[0,1)` source for a mission's `rollout`; pass `ctx.rng`. Only read when a mission declares one. */
|
|
@@ -378,8 +380,26 @@ export interface MissionRuntime {
|
|
|
378
380
|
/** Drop the devtools probe registered by `devtoolsName`. Safe to call twice. */
|
|
379
381
|
dispose(): void;
|
|
380
382
|
}
|
|
383
|
+
/** Which plane a marker proximity test compares in. */
|
|
384
|
+
export type MissionMarkerPlane = "horizontal" | "spatial";
|
|
385
|
+
/**
|
|
386
|
+
* How a positional objective compares an actor to its authored marker.
|
|
387
|
+
*
|
|
388
|
+
* Authored markers sit on a flat plan (`y = 0`) while the terrain around them rises, so a straight
|
|
389
|
+
* 3D test can leave a marker unreachable from the ground below it. `"horizontal"` compares the
|
|
390
|
+
* ground distance and allows the actor up to `tolerance` metres off the marker's height;
|
|
391
|
+
* `"spatial"` is the plain 3D check, for a world whose stacked platforms must stay distinct.
|
|
392
|
+
*/
|
|
393
|
+
export interface MissionMarkerProximity {
|
|
394
|
+
/** `"horizontal"` (default) or `"spatial"`. */
|
|
395
|
+
plane?: MissionMarkerPlane;
|
|
396
|
+
/** Horizontal plane only: how far (m) the actor may sit above or below the marker. Default {@link DEFAULT_MARKER_TOLERANCE}. */
|
|
397
|
+
tolerance?: number;
|
|
398
|
+
}
|
|
381
399
|
/** Default proximity radius (m) for a `reach` / `interact` objective. */
|
|
382
400
|
export declare const DEFAULT_REACH_RADIUS = 2.5;
|
|
401
|
+
/** Default vertical allowance (m) for a horizontal marker proximity test. */
|
|
402
|
+
export declare const DEFAULT_MARKER_TOLERANCE = 8;
|
|
383
403
|
/** Default keep-close distance (m) for a `pursue` objective. */
|
|
384
404
|
export declare const DEFAULT_PURSUE_DISTANCE = 3;
|
|
385
405
|
/** A pursuit escapes at this multiple of its keep-close distance when none is declared. */
|
|
@@ -404,6 +424,10 @@ export declare function applyMissionRewards(rewards: MissionRewards, appliers: M
|
|
|
404
424
|
*
|
|
405
425
|
* Cost per tick is bounded by the active objective's own fail list plus one goal test.
|
|
406
426
|
*
|
|
427
|
+
* Marker proximity runs on the plane `markerProximity` picks: horizontal by default, so a marker
|
|
428
|
+
* authored on a flat `y = 0` plan still triggers from ground that rises under it; `spatial` for a
|
|
429
|
+
* world whose stacked platforms must stay apart.
|
|
430
|
+
*
|
|
407
431
|
* @capability mission-runtime missions as a data-driven objective state machine with fail states, timers, branches and rewards
|
|
408
432
|
*/
|
|
409
433
|
export declare function createMissionRuntime(config: MissionRuntimeConfig): MissionRuntime;
|
package/dist/mission/mission.js
CHANGED
|
@@ -23,6 +23,8 @@ export function isCounterObjective(kind) {
|
|
|
23
23
|
}
|
|
24
24
|
/** Default proximity radius (m) for a `reach` / `interact` objective. */
|
|
25
25
|
export const DEFAULT_REACH_RADIUS = 2.5;
|
|
26
|
+
/** Default vertical allowance (m) for a horizontal marker proximity test. */
|
|
27
|
+
export const DEFAULT_MARKER_TOLERANCE = 8;
|
|
26
28
|
/** Default keep-close distance (m) for a `pursue` objective. */
|
|
27
29
|
export const DEFAULT_PURSUE_DISTANCE = 3;
|
|
28
30
|
/** A pursuit escapes at this multiple of its keep-close distance when none is declared. */
|
|
@@ -30,6 +32,21 @@ export const DEFAULT_ESCAPE_MULTIPLE = 3;
|
|
|
30
32
|
function distance(a, b) {
|
|
31
33
|
return Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
|
|
32
34
|
}
|
|
35
|
+
/** Whether `from` is inside the marker's reach radius under the configured plane — no allocation, no sqrt. */
|
|
36
|
+
function markerWithin(from, to, radius, plane, tolerance) {
|
|
37
|
+
const dy = from.y - to.y;
|
|
38
|
+
const limit = radius * radius;
|
|
39
|
+
if (plane === "spatial") {
|
|
40
|
+
const dx = from.x - to.x;
|
|
41
|
+
const dz = from.z - to.z;
|
|
42
|
+
return dx * dx + dy * dy + dz * dz <= limit;
|
|
43
|
+
}
|
|
44
|
+
if (Math.abs(dy) > tolerance)
|
|
45
|
+
return false;
|
|
46
|
+
const dx = from.x - to.x;
|
|
47
|
+
const dz = from.z - to.z;
|
|
48
|
+
return dx * dx + dz * dz <= limit;
|
|
49
|
+
}
|
|
33
50
|
function cloneState(state) {
|
|
34
51
|
return {
|
|
35
52
|
...state,
|
|
@@ -110,6 +127,10 @@ export function applyMissionRewards(rewards, appliers) {
|
|
|
110
127
|
*
|
|
111
128
|
* Cost per tick is bounded by the active objective's own fail list plus one goal test.
|
|
112
129
|
*
|
|
130
|
+
* Marker proximity runs on the plane `markerProximity` picks: horizontal by default, so a marker
|
|
131
|
+
* authored on a flat `y = 0` plan still triggers from ground that rises under it; `spatial` for a
|
|
132
|
+
* world whose stacked platforms must stay apart.
|
|
133
|
+
*
|
|
113
134
|
* @capability mission-runtime missions as a data-driven objective state machine with fail states, timers, branches and rewards
|
|
114
135
|
*/
|
|
115
136
|
export function createMissionRuntime(config) {
|
|
@@ -117,6 +138,8 @@ export function createMissionRuntime(config) {
|
|
|
117
138
|
for (const def of config.missions)
|
|
118
139
|
defs.set(def.id, def);
|
|
119
140
|
const markers = config.markers ?? {};
|
|
141
|
+
const markerPlane = config.markerProximity?.plane ?? "horizontal";
|
|
142
|
+
const markerTolerance = Math.max(0, config.markerProximity?.tolerance ?? DEFAULT_MARKER_TOLERANCE);
|
|
120
143
|
let rng = config.rng;
|
|
121
144
|
let state = null;
|
|
122
145
|
let ticks = 0;
|
|
@@ -222,7 +245,7 @@ export function createMissionRuntime(config) {
|
|
|
222
245
|
const to = objective.marker === undefined ? undefined : markers[objective.marker];
|
|
223
246
|
if (from === undefined || to === undefined)
|
|
224
247
|
return false;
|
|
225
|
-
return
|
|
248
|
+
return markerWithin(from, to, objective.radius ?? DEFAULT_REACH_RADIUS, markerPlane, markerTolerance);
|
|
226
249
|
}
|
|
227
250
|
case "interact": {
|
|
228
251
|
if (objective.actor === undefined || !(input.interact ?? []).includes(objective.actor))
|
|
@@ -231,7 +254,8 @@ export function createMissionRuntime(config) {
|
|
|
231
254
|
if (to === undefined)
|
|
232
255
|
return true;
|
|
233
256
|
const from = positionOf(objective.actor, input);
|
|
234
|
-
return from !== undefined
|
|
257
|
+
return from !== undefined
|
|
258
|
+
&& markerWithin(from, to, objective.radius ?? DEFAULT_REACH_RADIUS, markerPlane, markerTolerance);
|
|
235
259
|
}
|
|
236
260
|
case "survive":
|
|
237
261
|
return state.objectiveElapsed >= (objective.duration ?? 0);
|
|
@@ -306,7 +330,8 @@ export function createMissionRuntime(config) {
|
|
|
306
330
|
const actor = condition.actor ?? objective.actor;
|
|
307
331
|
const from = positionOf(actor, input);
|
|
308
332
|
const to = condition.marker === undefined ? undefined : markers[condition.marker];
|
|
309
|
-
|
|
333
|
+
const radius = condition.radius ?? DEFAULT_REACH_RADIUS;
|
|
334
|
+
if (from !== undefined && to !== undefined && !markerWithin(from, to, radius, markerPlane, markerTolerance)) {
|
|
310
335
|
return { state: condition.to ?? "failed", reason: condition.message ?? `${actor ?? "actor"} left the area` };
|
|
311
336
|
}
|
|
312
337
|
continue;
|
|
@@ -505,6 +505,9 @@ export function createGameContext(options) {
|
|
|
505
505
|
setLoop: (id, params) => events.emit("audio.loopSet", { id, ...params }),
|
|
506
506
|
stopLoop: (id) => events.emit("audio.loopStop", { id }),
|
|
507
507
|
},
|
|
508
|
+
feedback: {
|
|
509
|
+
cue: (cue, caption) => events.emit("feedback.cue", caption === undefined ? { cue } : { cue, caption }),
|
|
510
|
+
},
|
|
508
511
|
feed: {
|
|
509
512
|
bind: (action) => feed.bind(action, events),
|
|
510
513
|
push(action, entry) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AuthoredSimulationRuntime } from "../world/authoredSimulation.js";
|
|
2
2
|
import type { Territory } from "../world/territory.js";
|
|
3
|
+
import type { FeedbackCue } from "../audio/feedbackCues.js";
|
|
3
4
|
import type { NoticeFeed } from "../game/notices.js";
|
|
4
5
|
import type { CurrencyDefinition } from "../economy/currency.js";
|
|
5
6
|
import type { CardPile, CardPileConfig } from "../cards/cardPile.js";
|
|
@@ -436,6 +437,15 @@ export interface GameContextRace {
|
|
|
436
437
|
/** Lazily creates (on first call, `config` required) or returns the existing reactive race for `id` — discrete mutations and eventful `update` calls bump `ctx.version()`, so HUDs stop hand-managing `store.set` (#286.2). */
|
|
437
438
|
state(id: string, config?: RaceStateConfig): RaceState;
|
|
438
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* The game-announced feel seam on `ctx.game`: name a moment the engine has no event for and the
|
|
442
|
+
* shell plays its cue's sound, rumble, camera kick and caption. The reaction table lives in the
|
|
443
|
+
* shell, so a game never encodes what a hit or a wave should sound like.
|
|
444
|
+
*/
|
|
445
|
+
export interface GameFeedback {
|
|
446
|
+
/** Raise `cue` (`"hit" | "hurt" | "kill" | "pickup" | "objective" | "wave" | "deny"`); `caption` replaces the cue's default caption text. Emits `feedback.cue`. */
|
|
447
|
+
cue(cue: FeedbackCue, caption?: string): void;
|
|
448
|
+
}
|
|
439
449
|
/** Reachable audio seam on `ctx.game`: `play`, `music`, and `resume` route through the `audio.play`/`audio.music`/`audio.resume` events the shell's audio engine listens on, so game code triggers sound without importing the shell. Retained `loop`/`setLoop`/`stopLoop` add id-keyed loops with live pitch/gain control over `audio.loopStart`/`audio.loopSet`/`audio.loopStop` (#1051). */
|
|
440
450
|
export interface GameAudio {
|
|
441
451
|
play(sound: string, at?: readonly [number, number, number]): void;
|
|
@@ -500,6 +510,12 @@ export interface GameContext {
|
|
|
500
510
|
*/
|
|
501
511
|
notices: NoticeFeed;
|
|
502
512
|
audio: GameAudio;
|
|
513
|
+
/**
|
|
514
|
+
* Announce a gameplay moment the engine has no event for (a wave starting, a scripted beat).
|
|
515
|
+
* The shell answers with the cue's sound, gamepad pulse, camera kick and caption, so a game
|
|
516
|
+
* never encodes the reaction itself.
|
|
517
|
+
*/
|
|
518
|
+
feedback: GameFeedback;
|
|
503
519
|
/** Play a model's one-shot animation clip bound to `event` in its `animation.oneShots` (e.g. an attack swing) — emits `entity.animation` for the shell to pick up. */
|
|
504
520
|
playEntityAnimation(instanceId: string, event: string): void;
|
|
505
521
|
feed: GameContextFeed;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "jgengine core — pure-TypeScript game framework SDK (npm @jgengine/core). Entity stores, commands, combat, inventory, multiplayer seams. Zero dependencies. Not an ECS. Agent skills ship in the tarball. https://jgengine.com",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jgengine",
|
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.32.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.32.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
289
289
|
|
|
290
290
|
## @jgengine/core/runtime/adapter
|
|
291
291
|
|
|
@@ -410,6 +410,7 @@
|
|
|
410
410
|
- `GameContextRace` (interface): interface GameContextRace { state(id: string, config?: RaceStateConfig): RaceState } — ⚠ undocumented
|
|
411
411
|
- `GameContextTurn` (interface): interface GameContextTurn { loop(id: string, config?: TurnLoopConfig): TurnLoop } — ⚠ undocumented
|
|
412
412
|
- `GameContextWorld` (interface): interface GameContextWorld { ground: TerrainField; groundHeightAt(x: number, z: number): number; solids: WorldSolids } — ⚠ undocumented
|
|
413
|
+
- `GameFeedback` (interface): interface GameFeedback — The game-announced feel seam on `ctx.game`: name a moment the engine has no event for and the shell plays its cue's sound, rumble, camera kick and caption. The reaction table lives in the shell, so a game never encodes what a hit or a wave should sound like.
|
|
413
414
|
- `HitReactionInput` (interface): interface HitReactionInput { from: string; to: string; config: HitReactionConfig | ImpactPresetName; power?: number } — ⚠ undocumented · used by `resolveHitReaction` (@jgengine/core/combat): Resolves hit feel (hitstop, knockback impulse, camera shake) from either a named `impactPresets` event (`resolveHitReaction("explosion", inp…
|
|
414
415
|
- `MoveTowardCommitOptions` (interface): interface MoveTowardCommitOptions extends MoveTowardOptions — Options for {@link SceneEntityContext.moveTowardCommit}: {@link MoveTowardOptions} plus an optional facing turn.
|
|
415
416
|
- `SceneEntityContext` (interface): interface SceneEntityContext { spawn(name: string, options?: SpawnOptions): string; despawn(instanceId: string): boolean; update: EntityStore["update"]; setPose(instanceId: string, pose: EntityPose): boolean; setVelocity: EntityStore["setVelocity"]; setPoseConstraint: … — ⚠ undocumented
|
|
@@ -728,7 +729,7 @@
|
|
|
728
729
|
- `GameLoop` (interface): interface GameLoop<TContext = unknown> — Lifecycle hooks a game implements to drive init, per-tick simulation, and player join/leave.
|
|
729
730
|
- `Hotbar` (function): function Hotbar({ inventoryId, activeSlot, keys, slotSize = 46, itemIcon, style, className, }: { inventoryId: string; activeSlot?: number; keys?: readonly string[]; slotSize?: number; /** Caller-supplied item id → icon registry; return null/undefined to fall back to the default glyph. */ itemIcon?: … — A numbered hotbar bound to an inventory — painted iconed slots (a `GameIcon` glyph over a school-keyed gradient with a count badge, #1035), an active-slot highlight, and a keycap per slot. Place it and pass the `inventoryId`; `activeSlot` highlights the equipped one. Supply `itemIcon` to map your item ids to your own glyphs — the default resolves a `GameIcon` from the item id.
|
|
730
731
|
- `HudCanvas` (function): function HudCanvas({ layout, editChord, compactScale, showDuring, className, style, children, }: { layout: HudLayoutStore; editChord?: HudEditChord | false; /** Zoom applied to the whole HUD on compact displays. Default 0.85. */ compactScale?: number; /** Opt-in play-phase gate: render the HUD only … — Full-viewport HUD surface. Panels declared with `HudPanel` flow into nine anchor regions and stack automatically with a gap — no per-panel pixel offsets, no manual clearance for sibling panels, the touch-control dock (`--jg-hud-dock-clearance`), or device safe areas. On compact displays the whole surface scales down and each panel applies its `compact` behavior.
|
|
731
|
-
- `HudPanel` (function): function HudPanel({ id, anchor = "top-left", order, compact: compactMode = "keep", chip, interactive, inset, locked, showDuring, priority, mobileBehavior, allowOverlapWith, collisionGroup, region = true, width, height, type, className, style, children, }: { id: string; anchor?: HudAnchor; /**
|
|
732
|
+
- `HudPanel` (function): function HudPanel({ id, anchor = "top-left", lane, order, compact: compactMode = "keep", chip, interactive, inset, locked, showDuring, priority, mobileBehavior, allowOverlapWith, collisionGroup, region = true, width, height, type, className, style, children, }: { id: string; anchor?: HudAnchor; /** … — A HUD block that lives in one of the nine anchor regions. Panels sharing a region stack outward from the screen edge in ascending `order`. On fine pointers panels stay draggable through the edit chord; a dragged panel leaves the flow and keeps its custom placement. On compact displays custom placements are ignored and the `compact` behavior applies.
|
|
732
733
|
- `PlayableGame` (type): type PlayableGame = EnginePlayableGame< ComponentType, ComponentType, RenderEntity, RenderObject, ComponentType<ViewmodelProps>, ComponentType<WorldOverlayProps> > & { /** HUD theme the shell applies at the `GameUI` root. Unset uses `defaultHudTheme`; `false` opts out of the default theme and the en… — The concrete React-bound playable a shell `defineGame` returns and `GameHost` mounts — the one PlayableGame games handle.
|
|
733
734
|
- `StatBar` (function): function StatBar({ statId = "health", entityId, tone = "health", label, showValue = true, width = 200, icon, style, className, }: { statId?: string; entityId?: string; tone?: StatTone; label?: string; showValue?: boolean; width?: number; icon?: ReactNode; style?: CSSProperties; className?: string; }… — A polished stat bar (health/mana/stamina/shield/xp) — a rounded, glassy meter with a tone-colored fill and an optional value readout. Reads `statId` off `entityId` (defaults to the local player). Renders nothing until the stat exists.
|
|
734
735
|
- `StoreHandle` (interface): interface StoreHandle<T> — A typed, cast-free handle onto one slot of the reactive game store (`ctx.game.store`). The single type parameter is fixed at definition; `read`/`write`/`update` never widen to `unknown`, so no call site repeats `store.get(key) as T`. Writes flow through the engine store, so they bump `ctx.version()`, serialize into a {@link WorldSnapshot}, and replay under host authority — the same guarantees a hand-rolled `store.get`/`store.set` pair gives up the moment it forks run state into a module-level singleton.
|
|
@@ -44,7 +44,16 @@ tiers, manufacturer stat personalities and a level curve, all caller data.
|
|
|
44
44
|
`validateLootItem` names each defect, `compareLootItems` returns per-stat deltas
|
|
45
45
|
and a weighted score, and `lootWeaponDefinition` (`loot/lootWeapon`) projects a
|
|
46
46
|
rolled item onto a `WeaponDefinition` for `createWeaponSystem`. `createLootRoller`
|
|
47
|
-
counts drops per rarity for a debug probe and snapshots its counters.
|
|
47
|
+
counts drops per rarity for a debug probe and snapshots its counters.
|
|
48
|
+
|
|
49
|
+
Roll a campaign drop with `rollDrop` (`loot/drop`) and show `compareToEquipped`
|
|
50
|
+
against worn gear; carry and bank it with `createRunHaul` (`loot/haul`), which
|
|
51
|
+
weighs the hold through the encumbrance rules and drops the hold on `fail`. Break
|
|
52
|
+
an item down once with `salvageItem` and `createItemLedger` (`loot/salvage`), and
|
|
53
|
+
price a vendor shelf through the wallet with `stockVendor` (`loot/vendor`).
|
|
54
|
+
Bases and affixes take `minLevel`; bases take `mass`, which a roll carries forward.
|
|
55
|
+
|
|
56
|
+
Sample
|
|
48
57
|
catalog, distribution check and generated-weapon proof are in
|
|
49
58
|
[reference-systems.md](reference-systems.md#procedural-gear).
|
|
50
59
|
|
|
@@ -604,6 +604,7 @@
|
|
|
604
604
|
- `EntityAnimationEvent` (interface): interface EntityAnimationEvent — Request that an entity's rig play a one-shot animation clip bound to `event` in its `animation.oneShots` (e.g. an "attack" swing); the shell resolves the clip and plays it once over the locomotion state. With an `animation.graph`, `event` arms the trigger of that name.
|
|
605
605
|
- `EntityDiedEvent` (interface): interface EntityDiedEvent { instanceId: string; catalogId: string; userId?: string; displayName?: string; reason: DeathReason; source?: string; position: [number, number, number]; serverId?: string } — ⚠ undocumented
|
|
606
606
|
- `EntityFloatTextEvent` (interface): interface EntityFloatTextEvent { instanceId?: string; position: [number, number, number]; text: string; kind: string; amount?: number; hitType?: string; element?: string; crit?: boolean; scale?: number } — ⚠ undocumented
|
|
607
|
+
- `FeedbackCueEvent` (interface): interface FeedbackCueEvent — A gameplay moment the game announces itself, for the moments no engine event covers (a wave starting, a scripted beat). The shell reacts with the cue's sound, rumble, camera kick and caption.
|
|
607
608
|
- `FormChangedEvent` (interface): interface FormChangedEvent { instanceId: string; formId: string | null } — ⚠ undocumented
|
|
608
609
|
- `GameEventHandler` (type): type GameEventHandler<TPayload> = (payload: TPayload) => void — ⚠ undocumented
|
|
609
610
|
- `GameEventMap` (interface): interface GameEventMap { "entity.died": EntityDiedEvent; "combat.damage": DamageAppliedEvent; "entity.floatText": EntityFloatTextEvent; "combat.telegraph": CombatTelegraphEvent; "combat.vfx": CombatVfxEvent; "combat.vfxInstance": CombatVfxInstanceEvent; "combat.t… — ⚠ undocumented · used by `createGameEvents`: A typed publish/subscribe bus for gameplay events that systems and HUDs subscribe to.
|
|
@@ -1297,6 +1298,7 @@
|
|
|
1297
1298
|
- `DEFAULT_ESCAPE_MULTIPLE` (const): const DEFAULT_ESCAPE_MULTIPLE: 3 — A pursuit escapes at this multiple of its keep-close distance when none is declared.
|
|
1298
1299
|
- `DEFAULT_FIXED_STAGES` (const): const DEFAULT_FIXED_STAGES: readonly ["input", "movement", "combat", "ai", "activities", "cleanup"] — Default fixed-sim stage order — systems pick a stage; most need only this.
|
|
1299
1300
|
- `DEFAULT_FRAME_STAGES` (const): const DEFAULT_FRAME_STAGES: readonly ["input", "movement", "combat", "ai", "activities", "cleanup", "animation", "camera", "effects"] — Default frame stage order. Gameplay stages mirror the fixed table so once-per-frame systems (`type: "frame"`) can pick `combat`/`ai`/… without falling into the unknown-stage bucket; presentation stages follow.
|
|
1301
|
+
- `DEFAULT_MARKER_TOLERANCE` (const): const DEFAULT_MARKER_TOLERANCE: 8 — Default vertical allowance (m) for a horizontal marker proximity test.
|
|
1300
1302
|
- `DEFAULT_PICKUP_RADIUS` (const): const DEFAULT_PICKUP_RADIUS: 2 — ⚠ undocumented
|
|
1301
1303
|
- `DEFAULT_PING_CATEGORIES` (const): const DEFAULT_PING_CATEGORIES: Record<PingCategory, PingCategoryDef> — Content-agnostic default ping wheel: enemy / loot / location / danger.
|
|
1302
1304
|
- `DEFAULT_PURSUE_DISTANCE` (const): const DEFAULT_PURSUE_DISTANCE: 3 — Default keep-close distance (m) for a `pursue` objective.
|
|
@@ -1445,7 +1447,7 @@
|
|
|
1445
1447
|
- `LootDropProvenance` (interface): interface LootDropProvenance — Why one drop is in the result: the stage/table/entry that produced it, its weights, and the modifiers that shaped it.
|
|
1446
1448
|
- `LootFilterRule` (interface): interface LootFilterRule { id: string; when: LootFilterCondition; hide?: boolean; color?: string; beam?: boolean; label?: string } — ⚠ undocumented · used by `evaluateLootFilter`: First matching rule wins (PoE/Last Epoch block semantics) — later rules never override an earlier match.
|
|
1447
1449
|
- `LootInstalledPart` (interface): interface LootInstalledPart — One part occupying one slot of a rolled item.
|
|
1448
|
-
- `LootIssueCode` (type): type LootIssueCode = | "invalid-level" | "unknown-base" | "unknown-manufacturer" | "manufacturer-base" | "unknown-rarity" | "duplicate-slot" | "unknown-slot" | "missing-slot-part" | "unknown-part" | "part-category" | "unknown-affix" | "duplicate-affix" | "invalid-affix" | "non-
|
|
1450
|
+
- `LootIssueCode` (type): type LootIssueCode = | "invalid-level" | "unknown-base" | "level-gated-base" | "unknown-manufacturer" | "manufacturer-base" | "unknown-rarity" | "duplicate-slot" | "unknown-slot" | "missing-slot-part" | "unknown-part" | "part-category" | "unknown-affix" | "duplicate-affix" | "invalid-affix" | "non-f… — A defect found by {@link validateLootItem}.
|
|
1449
1451
|
- `LootItem` (interface): interface LootItem — One rolled item: the base, maker, rarity, level, composed name, chosen parts, rolled affixes and resolved stats. Plain JSON, so a save stores it and the instance registry can key it.
|
|
1450
1452
|
- `LootManufacturerDef` (interface): interface LootManufacturerDef — A maker with a stat personality: its mark goes in the name, `bias` shifts stats additively, `scale` multiplies them, and favored affix pools weigh more when this maker rolls the item.
|
|
1451
1453
|
- `LootModifier` (interface): interface LootModifier<TCtx = unknown> — A registered, id-tagged loot policy applied to one stage. `plan` transforms eligibility, weights, and roll counts before rolling (luck, difficulty, gating); `drops` post-processes the rolled drops (quantity multipliers, dedupe, caps). Neither hook mutates table definitions, and the id is recorded in provenance so a resolved drop can be traced back to the policies that shaped it.
|
|
@@ -1493,6 +1495,8 @@
|
|
|
1493
1495
|
- `MissionFailCondition` (interface): interface MissionFailCondition — A condition that fails the mission while the objective is active. `to` names a {@link MissionFailDef}; with no `to` the mission fails with the generic `"failed"` state.
|
|
1494
1496
|
- `MissionFailDef` (interface): interface MissionFailDef — A named fail state an objective, timer or branch can land in.
|
|
1495
1497
|
- `MissionIssue` (interface): interface MissionIssue — A located mission-catalog problem; `path` is a JSON Pointer into the supplied catalog.
|
|
1498
|
+
- `MissionMarkerPlane` (type): type MissionMarkerPlane = "horizontal" | "spatial" — Which plane a marker proximity test compares in.
|
|
1499
|
+
- `MissionMarkerProximity` (interface): interface MissionMarkerProximity — How a positional objective compares an actor to its authored marker.
|
|
1496
1500
|
- `MissionObjectiveDef` (interface): interface MissionObjectiveDef — One objective. Every field is content; the machine reads only these.
|
|
1497
1501
|
- `MissionObjectiveKind` (type): type MissionObjectiveKind = "reach" | "interact" | "survive" | "pursue" | "kill" | "collect" — The primitive goals an objective can have. Escort, chase and defend are not kinds — they are compositions of these plus fail conditions and timers (see `compositions.ts`).
|
|
1498
1502
|
- `MissionRewardAppliers` (interface): interface MissionRewardAppliers — Appliers a mission reward routes through — the same shape quests use, plus a loot-table hook.
|
|
@@ -2178,7 +2182,7 @@
|
|
|
2178
2182
|
|
|
2179
2183
|
## @jgengine/core/item/affix
|
|
2180
2184
|
|
|
2181
|
-
- `AffixDef` (interface): interface AffixDef { id: string; stat: string; op?: AffixOp; roll: number | [number, number]; weight: number; namePart?: {position: "prefix" | "suffix"; text: string} } — ⚠ undocumented
|
|
2185
|
+
- `AffixDef` (interface): interface AffixDef { id: string; stat: string; op?: AffixOp; roll: number | [number, number]; weight: number; minLevel?: number; namePart?: {position: "prefix" | "suffix"; text: string} } — ⚠ undocumented
|
|
2182
2186
|
- `AffixOp` (type): type AffixOp = "add" | "mul" — ⚠ undocumented
|
|
2183
2187
|
- `AffixPool` (interface): interface AffixPool { id: string; affixes: readonly AffixDef[] } — ⚠ undocumented
|
|
2184
2188
|
- `AffixRoller` (interface): interface AffixRoller { rollRarity(rng: () => number): RarityTier; roll(base: ItemBaseDef, rarityId: string, rng: () => number): RolledItem; rollRandom(base: ItemBaseDef, rng: () => number): RolledItem } — ⚠ undocumented · used by `createAffixRoller`: Rolls randomized gear from a base item: a weighted rarity tier scales base stats and picks prefix/suffix affixes that name and modify the it…
|
|
@@ -2309,6 +2313,25 @@
|
|
|
2309
2313
|
- `createWeaponStats` (function): function createWeaponStats(resolveEntry: (itemId: string) => WeaponEntry | null | undefined): WeaponStats — Resolve per-weapon stat values — damage, fire rate, spread — for combat math.
|
|
2310
2314
|
- `getWeaponStat` (function): function getWeaponStat(entry: WeaponEntry | null | undefined, stat: string): number | null — ⚠ undocumented
|
|
2311
2315
|
|
|
2316
|
+
## @jgengine/core/loot/drop
|
|
2317
|
+
|
|
2318
|
+
- `EquippedComparison` (interface): interface EquippedComparison — What a HUD renders when a drop lands: per-stat deltas against the worn item and one verdict.
|
|
2319
|
+
- `EquippedStatDelta` (interface): interface EquippedStatDelta extends LootStatDelta — A per-stat delta from the equipped item to the candidate; positive `delta` means the candidate is larger.
|
|
2320
|
+
- `ItemDrop` (interface): interface ItemDrop extends LootItem — A rolled item with a stable instance id, so a stash, ledger and vendor stock can name it.
|
|
2321
|
+
- `RollDropOptions` (interface): interface RollDropOptions extends LootRollOptions — {@link rollDrop} options: the loot roll plus the instance id (defaults to `drop:<seed>`).
|
|
2322
|
+
- `compareToEquipped` (function): function compareToEquipped(candidate: ItemDrop, equipped: ItemDrop | null, options: LootComparisonOptions = {}): EquippedComparison — Compare a dropped item against what is equipped, with one delta per stat and a weighted verdict. The delta is `candidate - equipped`; `better` flips for stats the caller lists in `lowerIsBetter`, so reload time improving does not read as worse. A missing equipped item compares against zeros.
|
|
2323
|
+
- `itemMass` (function): function itemMass(item: Pick<LootItem, "mass">): number — Carried mass of a rolled item. Older saves may predate `mass`; those default to `1`.
|
|
2324
|
+
- `rollDrop` (function): function rollDrop(catalog: LootCatalog, seed: string | number, options: RollDropOptions = {}): ItemDrop — Roll one item from a seed. Same seed, catalog and options produce the same id, name, affixes, stats and mass on every run; level gates which bases and affixes are in the pool.
|
|
2325
|
+
|
|
2326
|
+
## @jgengine/core/loot/haul
|
|
2327
|
+
|
|
2328
|
+
- `CarryOutcome` (type): type CarryOutcome = | { ok: true; mass: number; remaining: number } | { ok: false; reason: "over-capacity" | "no-space" | "invalid-count"; mass: number; remaining: number } — What a carry attempt returns: the new load, or the rule that refused it.
|
|
2329
|
+
- `HaulTransfer` (interface): interface HaulTransfer — Stacks moved between a run and its stash, or lost when the run fails.
|
|
2330
|
+
- `RunHaul` (interface): interface RunHaul — A run's carried loot and the stash that outlives it.
|
|
2331
|
+
- `RunHaulConfig` (interface): interface RunHaulConfig<TId extends string> — What a run haul needs: two inventories, a weight rule and an optional save cell.
|
|
2332
|
+
- `RunHaulState` (interface): interface RunHaulState — Detached run and stash contents, for a save or a debug readout.
|
|
2333
|
+
- `createRunHaul` (function): function createRunHaul<TId extends string>(config: RunHaulConfig<TId>): RunHaul — A run's haul beside a persistent stash. Carrying is gated by the shared encumbrance rules; `bank` moves what the stash fits and saves; `fail` throws the run's haul away and leaves the stash as it was. The game decides when a run succeeds or fails and calls one of them.
|
|
2334
|
+
|
|
2312
2335
|
## @jgengine/core/loot/lootGen
|
|
2313
2336
|
|
|
2314
2337
|
- `LootBaseDef` (interface): interface LootBaseDef extends ItemBaseDef — A base family (frame, housing, chassis — caller nouns) with its slots, stats and affix pools.
|
|
@@ -2316,7 +2339,7 @@
|
|
|
2316
2339
|
- `LootComparison` (interface): interface LootComparison — How two items compare: every stat delta plus one weighted score for each.
|
|
2317
2340
|
- `LootComparisonOptions` (interface): interface LootComparisonOptions — Caller policy for the comparison score.
|
|
2318
2341
|
- `LootInstalledPart` (interface): interface LootInstalledPart — One part occupying one slot of a rolled item.
|
|
2319
|
-
- `LootIssueCode` (type): type LootIssueCode = | "invalid-level" | "unknown-base" | "unknown-manufacturer" | "manufacturer-base" | "unknown-rarity" | "duplicate-slot" | "unknown-slot" | "missing-slot-part" | "unknown-part" | "part-category" | "unknown-affix" | "duplicate-affix" | "invalid-affix" | "non-
|
|
2342
|
+
- `LootIssueCode` (type): type LootIssueCode = | "invalid-level" | "unknown-base" | "level-gated-base" | "unknown-manufacturer" | "manufacturer-base" | "unknown-rarity" | "duplicate-slot" | "unknown-slot" | "missing-slot-part" | "unknown-part" | "part-category" | "unknown-affix" | "duplicate-affix" | "invalid-affix" | "non-f… — A defect found by {@link validateLootItem}.
|
|
2320
2343
|
- `LootItem` (interface): interface LootItem — One rolled item: the base, maker, rarity, level, composed name, chosen parts, rolled affixes and resolved stats. Plain JSON, so a save stores it and the instance registry can key it.
|
|
2321
2344
|
- `LootManufacturerDef` (interface): interface LootManufacturerDef — A maker with a stat personality: its mark goes in the name, `bias` shifts stats additively, `scale` multiplies them, and favored affix pools weigh more when this maker rolls the item.
|
|
2322
2345
|
- `LootPartDef` (interface): interface LootPartDef extends PartDef — A part a base can mount: its stats, a pick weight, and an optional word for the item name.
|
|
@@ -2342,6 +2365,30 @@
|
|
|
2342
2365
|
- `LootWeaponStatMap` (interface): interface LootWeaponStatMap — Which rolled stat feeds which part of a {@link WeaponDefinition}. Every field is a stat id from the item; an omitted field keeps the template's value, so a catalog only maps what it rolls.
|
|
2343
2366
|
- `lootWeaponDefinition` (function): function lootWeaponDefinition(item: LootItem, projection: LootWeaponProjection): WeaponDefinition — Project a rolled item onto a plain weapon definition: copy the template, then write the mapped stats into rate, damage, magazine, spread, range and projectile speed. The result is a {@link WeaponDefinition}, so `assertWeaponDefinition` and `createWeaponSystem` accept it unchanged.
|
|
2344
2367
|
|
|
2368
|
+
## @jgengine/core/loot/salvage
|
|
2369
|
+
|
|
2370
|
+
- `ItemDropLike` (interface): interface ItemDropLike — The fields salvage reads; an `ItemDrop` from `./drop` satisfies it.
|
|
2371
|
+
- `ItemLedger` (interface): interface ItemLedger — One record of what happened to rolled item instances. Every path that removes an item - salvage, sale, granting a duplicate - claims it here first, so a second claim returns false and the item cannot be equipped, sold or broken down twice.
|
|
2372
|
+
- `ItemLedgerState` (type): type ItemLedgerState = Readonly<Record<string, Exclude<ItemLifecycle, "owned">>> — Items that have left play, keyed by instance id; anything absent is still `owned`.
|
|
2373
|
+
- `ItemLifecycle` (type): type ItemLifecycle = "owned" | "salvaged" | "sold" — Where an item instance stands: still usable, broken down, or sold.
|
|
2374
|
+
- `SalvageResult` (type): type SalvageResult = | { ok: true; id: string; yield: SalvageYield } | { ok: false; reason: "already-spent" | "invalid-item" } — Salvage outcome: the yield, or the reason the instance could not be broken down.
|
|
2375
|
+
- `SalvageTable` (interface): interface SalvageTable — What a broken-down item is worth: a game-authored currency, rate and per-rarity multiplier.
|
|
2376
|
+
- `SalvageYield` (interface): interface SalvageYield — The currency and amount one salvage yields. `currency` is the game's key, never interpreted.
|
|
2377
|
+
- `createItemLedger` (function): function createItemLedger(initial: ItemLedgerState = {}): ItemLedger — Build an item ledger. Ids are consumed once: the first successful {@link ItemLedger.spend} wins, every later call fails, which is what makes salvage and sale mutually exclusive.
|
|
2378
|
+
- `salvageItem` (function): function salvageItem(item: ItemDropLike, table: SalvageTable, ledger: ItemLedger): SalvageResult — Break one item down through its ledger. The ledger is claimed before the yield is computed, so a salvaged item cannot be salvaged, equipped or sold again; pass the game's own wallet update from `yield`.
|
|
2379
|
+
- `salvageValue` (function): function salvageValue(item: Pick<ItemDropLike, "level" | "rarity">, table: SalvageTable): SalvageYield — The yield an item of this level and rarity breaks down into. The formula is `round(perLevel x level x rarityMultiplier)`, floored by `minimum`, so the same item always yields the same amount and a higher level never yields less.
|
|
2380
|
+
|
|
2381
|
+
## @jgengine/core/loot/vendor
|
|
2382
|
+
|
|
2383
|
+
- `StockVendorOptions` (interface): interface StockVendorOptions — What stocking a vendor reads: the roll catalog, a seeded rng and the pricing policy.
|
|
2384
|
+
- `VendorOffer` (interface): interface VendorOffer — One stocked item and the shop entry that prices it.
|
|
2385
|
+
- `VendorPriceRule` (interface): interface VendorPriceRule — How a vendor price is derived from an item: a base, a per-level rate and a rarity multiplier.
|
|
2386
|
+
- `VendorRotation` (interface): interface VendorRotation — A run-count refresh rule; the game owns how many runs between restocks.
|
|
2387
|
+
- `VendorRotationState` (interface): interface VendorRotationState — Where a vendor shelf stands: the run number it was last stocked on.
|
|
2388
|
+
- `createVendorRotation` (function): function createVendorRotation(everyRuns: number, start = 0): VendorRotation — A restock rule the game authors as "every N runs". It only answers when a refresh is due; the game calls {@link stockVendor} and then `mark`. No timer, so a paused or reloaded save keeps the same shelf until the run count moves.
|
|
2389
|
+
- `itemPrice` (function): function itemPrice(item: Pick<LootItem, "level" | "rarity">, rule: VendorPriceRule): { buy: number; sell: number | null } — The price of one item under a `VendorPriceRule`: `round(base + perLevel x level)` times the rarity multiplier, never below 1. Sell-back is `floor(price x sellFactor)` when the rule sets one.
|
|
2390
|
+
- `stockVendor` (function): function stockVendor(shop: ShopStock, options: StockVendorOptions): VendorOffer[] — Roll a vendor's shelf and add it to an existing {@link ShopStock}. Entries added by a previous call with the same `idPrefix` are removed first, so a refresh replaces the shelf instead of throwing on duplicate ids. Buying, selling and wallet charging stay in the economy owner.
|
|
2391
|
+
|
|
2345
2392
|
## @jgengine/core/mission/compositions
|
|
2346
2393
|
|
|
2347
2394
|
- `ChaseMissionOptions` (interface): interface ChaseMissionOptions extends ComposedMissionOptions — Options for {@link chaseMission}: a target kept close until it is caught.
|
|
@@ -2355,6 +2402,7 @@
|
|
|
2355
2402
|
## @jgengine/core/mission/mission
|
|
2356
2403
|
|
|
2357
2404
|
- `DEFAULT_ESCAPE_MULTIPLE` (const): const DEFAULT_ESCAPE_MULTIPLE: 3 — A pursuit escapes at this multiple of its keep-close distance when none is declared.
|
|
2405
|
+
- `DEFAULT_MARKER_TOLERANCE` (const): const DEFAULT_MARKER_TOLERANCE: 8 — Default vertical allowance (m) for a horizontal marker proximity test.
|
|
2358
2406
|
- `DEFAULT_PURSUE_DISTANCE` (const): const DEFAULT_PURSUE_DISTANCE: 3 — Default keep-close distance (m) for a `pursue` objective.
|
|
2359
2407
|
- `DEFAULT_REACH_RADIUS` (const): const DEFAULT_REACH_RADIUS: 2.5 — Default proximity radius (m) for a `reach` / `interact` objective.
|
|
2360
2408
|
- `MISSION_COUNTER_KINDS` (const): const MISSION_COUNTER_KINDS: readonly MissionObjectiveKind[] — Objective kinds that are satisfied only by a counter — a mission made of these alone is a lint error.
|
|
@@ -2367,6 +2415,8 @@
|
|
|
2367
2415
|
- `MissionFailCondition` (interface): interface MissionFailCondition — A condition that fails the mission while the objective is active. `to` names a {@link MissionFailDef}; with no `to` the mission fails with the generic `"failed"` state.
|
|
2368
2416
|
- `MissionFailDef` (interface): interface MissionFailDef — A named fail state an objective, timer or branch can land in.
|
|
2369
2417
|
- `MissionIssue` (interface): interface MissionIssue — A located mission-catalog problem; `path` is a JSON Pointer into the supplied catalog.
|
|
2418
|
+
- `MissionMarkerPlane` (type): type MissionMarkerPlane = "horizontal" | "spatial" — Which plane a marker proximity test compares in.
|
|
2419
|
+
- `MissionMarkerProximity` (interface): interface MissionMarkerProximity — How a positional objective compares an actor to its authored marker.
|
|
2370
2420
|
- `MissionObjectiveDef` (interface): interface MissionObjectiveDef — One objective. Every field is content; the machine reads only these.
|
|
2371
2421
|
- `MissionObjectiveKind` (type): type MissionObjectiveKind = "reach" | "interact" | "survive" | "pursue" | "kill" | "collect" — The primitive goals an objective can have. Escort, chase and defend are not kinds — they are compositions of these plus fail conditions and timers (see `compositions.ts`).
|
|
2372
2422
|
- `MissionRewardAppliers` (interface): interface MissionRewardAppliers — Appliers a mission reward routes through — the same shape quests use, plus a loot-table hook.
|
|
@@ -244,6 +244,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
244
244
|
|
|
245
245
|
- `splitStack` (function) · `import { splitStack } from "@jgengine/core/inventory/inventoryModel"`
|
|
246
246
|
|
|
247
|
+
## item-comparison — show per-stat deltas and a weighted verdict for a drop against equipped gear
|
|
248
|
+
|
|
249
|
+
- `compareToEquipped` (function) · `import { compareToEquipped } from "@jgengine/core/loot/drop"`
|
|
250
|
+
|
|
247
251
|
## item-generation — compose constraints, weighted pools, transforms, and provenance into a deterministic generated item
|
|
248
252
|
|
|
249
253
|
- `generate` (function) · `import { generate } from "@jgengine/core/gameplay"`
|
|
@@ -265,10 +269,46 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
265
269
|
- `createItemInstanceRegistry` (function) · `import { createItemInstanceRegistry } from "@jgengine/core/gameplay"`
|
|
266
270
|
- `proceduralLootEntry` (function) · `import { proceduralLootEntry } from "@jgengine/core/gameplay"`
|
|
267
271
|
|
|
272
|
+
## item-ledger — track which rolled item instances are still owned, salvaged or sold
|
|
273
|
+
|
|
274
|
+
- `createItemLedger` (function) · `import { createItemLedger } from "@jgengine/core/loot/salvage"`
|
|
275
|
+
|
|
276
|
+
## item-roll — roll one deterministic, level-gated item from a seed
|
|
277
|
+
|
|
278
|
+
- `rollDrop` (function) · `import { rollDrop } from "@jgengine/core/loot/drop"`
|
|
279
|
+
|
|
280
|
+
## item-run-haul — carry loot with a weight gate, bank it into a persistent stash, or lose it when the run fails
|
|
281
|
+
|
|
282
|
+
- `createRunHaul` (function) · `import { createRunHaul } from "@jgengine/core/loot/haul"`
|
|
283
|
+
|
|
284
|
+
## item-salvage — break an item down for the currency its level and rarity say, once
|
|
285
|
+
|
|
286
|
+
- `salvageItem` (function) · `import { salvageItem } from "@jgengine/core/loot/salvage"`
|
|
287
|
+
|
|
288
|
+
## item-salvage-value — compute the currency an item of a level and rarity breaks down into
|
|
289
|
+
|
|
290
|
+
- `salvageValue` (function) · `import { salvageValue } from "@jgengine/core/loot/salvage"`
|
|
291
|
+
|
|
268
292
|
## item-use-composition — compose pluggable, ordered item use-behaviors from serializable references
|
|
269
293
|
|
|
270
294
|
- `createUseBehaviorRegistry` (function) · `import { createUseBehaviorRegistry } from "@jgengine/core/gameplay"`
|
|
271
295
|
|
|
296
|
+
## item-vendor — stock a vendor with rolled items priced through the shared economy
|
|
297
|
+
|
|
298
|
+
- `stockVendor` (function) · `import { stockVendor } from "@jgengine/core/loot/vendor"`
|
|
299
|
+
|
|
300
|
+
## item-vendor-price — price an item from its level and rarity through the economy's currency
|
|
301
|
+
|
|
302
|
+
- `itemPrice` (function) · `import { itemPrice } from "@jgengine/core/loot/vendor"`
|
|
303
|
+
|
|
304
|
+
## item-vendor-rotation — decide when a vendor's shelf is due for a restock
|
|
305
|
+
|
|
306
|
+
- `createVendorRotation` (function) · `import { createVendorRotation } from "@jgengine/core/loot/vendor"`
|
|
307
|
+
|
|
308
|
+
## item-weight — read a rolled item's mass for a carry check
|
|
309
|
+
|
|
310
|
+
- `itemMass` (function) · `import { itemMass } from "@jgengine/core/loot/drop"`
|
|
311
|
+
|
|
272
312
|
## key-rebinding — conflict-aware key-remap session over the action-binding model — capture/reassign, conflict detection, reset-to-defaults, persist overrides, snapshot/restore
|
|
273
313
|
|
|
274
314
|
- `createRebindSession` (function) · `import { createRebindSession } from "@jgengine/core/gameplay"`
|
|
@@ -455,11 +455,11 @@
|
|
|
455
455
|
|
|
456
456
|
## @jgengine/core/audio/audioFalloff
|
|
457
457
|
|
|
458
|
-
- `AudioBusDef` (interface): interface AudioBusDef { id: AudioBusId; gain?: number } — ⚠ undocumented · used by `
|
|
458
|
+
- `AudioBusDef` (interface): interface AudioBusDef { id: AudioBusId; gain?: number } — ⚠ undocumented · used by `withFeedbackCueSounds` (@jgengine/core/audio/feedbackCues): Merge the default feedback reactions into a game's sound catalog.
|
|
459
459
|
- `AudioBusId` (type): type AudioBusId = string — ⚠ undocumented
|
|
460
460
|
- `AudioFalloffConfig` (interface): interface AudioFalloffConfig { minDistance?: number; maxDistance?: number; curve?: FalloffCurve } — ⚠ undocumented
|
|
461
461
|
- `FalloffCurve` (type): type FalloffCurve = "linear" | "inverse" | "none" — ⚠ undocumented
|
|
462
|
-
- `SoundDef` (interface): interface SoundDef { id: string; url?: string; synth?: SynthPatch; bus: AudioBusId; gain?: number; loop?: boolean; maxVoices?: number; priority?: number; positional?: boolean; falloff?: AudioFalloffConfig; doppler?: number; spatial?: {panning: "hrtf" | "equal… — ⚠ undocumented · used by `
|
|
462
|
+
- `SoundDef` (interface): interface SoundDef { id: string; url?: string; synth?: SynthPatch; bus: AudioBusId; gain?: number; loop?: boolean; maxVoices?: number; priority?: number; positional?: boolean; falloff?: AudioFalloffConfig; doppler?: number; spatial?: {panning: "hrtf" | "equal… — ⚠ undocumented · used by `DEFAULT_FEEDBACK_CUE_SOUNDS` (@jgengine/core/audio/feedbackCues): Short procedural reactions - no samples to load, so every game has impact feedback without authoring audio.
|
|
463
463
|
- `computeFalloffGain` (function): function computeFalloffGain(distance: number, config: AudioFalloffConfig = {}): number — ⚠ undocumented
|
|
464
464
|
- `distance3` (function): function distance3(a: { x: number; y: number; z: number }, b: { x: number; y: number; z: number }): number — ⚠ undocumented
|
|
465
465
|
- `resolveEmitterGain` (function): function resolveEmitterGain(distance: number, sound: Pick<SoundDef, "gain" | "positional" | "falloff">, busGain: number): number — ⚠ undocumented
|
|
@@ -481,6 +481,16 @@
|
|
|
481
481
|
- `EngineLayersState` (interface): interface EngineLayersState — Serializable {@link EngineLayers} state.
|
|
482
482
|
- `createEngineLayers` (function): function createEngineLayers(initial: EngineLayersConfig): EngineLayers — Layered engine sound: each set of rpm-keyed samples is equal-power crossfaded between the two samples bracketing the current rpm and pitched by `rpm / layer.rpm`; the on- and off-load sets are equal-power crossfaded by smoothed `engineLoad`. A config with only one load set plays it at every load. `play` drives `ctx.game.audio` directly, so a game needs no per-layer glue.
|
|
483
483
|
|
|
484
|
+
## @jgengine/core/audio/feedbackCues
|
|
485
|
+
|
|
486
|
+
- `DEFAULT_FEEDBACK_CUE_SOUNDS` (const): const DEFAULT_FEEDBACK_CUE_SOUNDS: Readonly<Record<FeedbackCue, SoundDef>> — Short procedural reactions - no samples to load, so every game has impact feedback without authoring audio. Loud enough to read, quiet enough to sit under a weapon's own sound.
|
|
487
|
+
- `FEEDBACK_CUES` (const): const FEEDBACK_CUES: readonly FeedbackCue[] — Every {@link FeedbackCue}, in a stable order.
|
|
488
|
+
- `FEEDBACK_CUE_BUS` (const): const FEEDBACK_CUE_BUS: "sfx" — Bus the default feedback cues mix through; the settings menu shows its volume and mute.
|
|
489
|
+
- `FeedbackCue` (type): type FeedbackCue = "hit" | "hurt" | "kill" | "pickup" | "objective" | "wave" | "deny" — A gameplay moment the engine reacts to: taking or dealing a hit, a kill, a pickup, an objective, a wave starting, or a refused action. The game names the moment; the engine owns the reaction.
|
|
490
|
+
- `FeedbackCueOverrides` (type): type FeedbackCueOverrides = false | Partial<Record<FeedbackCue, SoundDef | false>> — Per-cue override: a replacement sound, or `false` to silence that cue. `false` for the whole map silences all.
|
|
491
|
+
- `feedbackCueSoundId` (function): function feedbackCueSoundId(cue: FeedbackCue): string — Catalog id a cue plays under: `feel.hit`, `feel.kill`, ... A game sound with that id replaces the default.
|
|
492
|
+
- `withFeedbackCueSounds` (function): function withFeedbackCueSounds(sounds: Readonly<Record<string, SoundDef>> | undefined, buses: Readonly<Record<string, AudioBusDef>> | undefined, overrides?: FeedbackCueOverrides): { sounds: Record<string, SoundDef>; buses: Record<string, AudioBusDef> | undefined } — Merge the default feedback reactions into a game's sound catalog. A game sound already named `feel.<cue>` wins, then `overrides`, then the default. Adds the `sfx` bus (gain 0.9) when the game did not declare one, so the impact layer has its own volume and mute row.
|
|
493
|
+
|
|
484
494
|
## @jgengine/core/audio/music
|
|
485
495
|
|
|
486
496
|
- `MusicInstrument` (type): type MusicInstrument = | "strings" | "flute" | "harp" | "horn" | "choir" | "bell" | "timpani" | "bass" | "stacc" | "pad" | "lute" | "dulcimer" | "frameDrum" | "warDrum" | "reed" | "pipe" | "squareLead" | "woodBlock" | "tinyBell" | "piano" | "shaker" | "brassStab" | "cymSwell" | "oboe" — Named synthesised instrument. Each maps to a voice in the shell's instrument library (`@jgengine/shell/audio/musicVoices`); an unknown name falls back to a plain sine voice so a theme is never silent.
|
|
@@ -2358,7 +2368,7 @@
|
|
|
2358
2368
|
- `AssetSpace` (interface): interface AssetSpace — Catalog-level asset-space metadata: how a model is authored relative to the engine, owned by the catalog entry rather than re-derived per game. Every field is optional so an unmeasured asset stays valid; {@link resolveFacingRotationY}/{@link assetUnitScale}/{@link resolveAnchorOffset} supply the documented defaults.
|
|
2359
2369
|
- `AssetSpaceIssue` (interface): interface AssetSpaceIssue — One problem found by {@link validateAssetSpace}: which metadata field and why it is invalid.
|
|
2360
2370
|
- `AttackMoveOrderPayload` (interface): interface AttackMoveOrderPayload — Payload for an attack-move: advance toward `point`, engaging hostiles encountered en route.
|
|
2361
|
-
- `AudioBusDef` (interface): interface AudioBusDef { id: AudioBusId; gain?: number } — ⚠ undocumented · used by `
|
|
2371
|
+
- `AudioBusDef` (interface): interface AudioBusDef { id: AudioBusId; gain?: number } — ⚠ undocumented · used by `withFeedbackCueSounds` (@jgengine/core/audio/feedbackCues): Merge the default feedback reactions into a game's sound catalog.
|
|
2362
2372
|
- `AudioFalloffConfig` (interface): interface AudioFalloffConfig { minDistance?: number; maxDistance?: number; curve?: FalloffCurve } — ⚠ undocumented
|
|
2363
2373
|
- `AuthoredNavLink` (interface): interface AuthoredNavLink — A connection the scene author places between two spots — a door through a wall, a ladder to a deck, a jump across a gap. Authored as world points, not polygon ids, so it survives a re-bake: {@link NavRuntime.applyMesh} resolves each end onto the nearest walkable polygon.
|
|
2364
2374
|
- `AuthoredTrigger` (interface): interface AuthoredTrigger — One resolved trigger binding from a document object.
|
|
@@ -2847,7 +2857,7 @@
|
|
|
2847
2857
|
- `SnapMode` (type): type SnapMode = "grid" | "free" | "surface" — ⚠ undocumented
|
|
2848
2858
|
- `SnowEnvironmentDescriptor` (type): type SnowEnvironmentDescriptor = { kind: "snow" } & Required< Pick<SnowEnvironmentConfig, "area" | "density" | "speed" | "flakeSize" | "drift" | "wind" | "color" | "opacity"> > — ⚠ undocumented · used by `snow`: Declares a snowfall weather effect for `environment()` — area, density, drift, wind, and flake opacity.
|
|
2849
2859
|
- `SoilRules` (interface): interface SoilRules — Fully-defaulted soil params parsed from a volume's `meta`.
|
|
2850
|
-
- `SoundDef` (interface): interface SoundDef { id: string; url?: string; synth?: SynthPatch; bus: AudioBusId; gain?: number; loop?: boolean; maxVoices?: number; priority?: number; positional?: boolean; falloff?: AudioFalloffConfig; doppler?: number; spatial?: {panning: "hrtf" | "equal… — ⚠ undocumented · used by `
|
|
2860
|
+
- `SoundDef` (interface): interface SoundDef { id: string; url?: string; synth?: SynthPatch; bus: AudioBusId; gain?: number; loop?: boolean; maxVoices?: number; priority?: number; positional?: boolean; falloff?: AudioFalloffConfig; doppler?: number; spatial?: {panning: "hrtf" | "equal… — ⚠ undocumented · used by `DEFAULT_FEEDBACK_CUE_SOUNDS` (@jgengine/core/audio/feedbackCues): Short procedural reactions - no samples to load, so every game has impact feedback without authoring audio.
|
|
2851
2861
|
- `SpatialGrid` (class): class SpatialGrid — A uniform-grid broad-phase over the x/z plane, separate from the rigid-body sim, for cheap same-tick proximity across hundreds–thousands of simple movers (swarm enemies). Rebuild each tick from the caller's own position arrays, then `queryCircle` (enemies hitting the player / an AoE) or `forEachPair` (mutual separation). Both are precise: no false negatives, no false positives beyond the exact distance test.
|
|
2852
2862
|
- `SpawnDirectorConfig` (interface): interface SpawnDirectorConfig { waves: readonly WaveManifest[]; maxAlive?: number; escalationPerSecond?: number; alertBudgetPerSecond?: number; alertDecayPerSecond?: number; playerBudgetPerSecond?: number; maxSpawnsPerTick?: number; loop?: boolean; seed?: number; spawnP… — ⚠ undocumented
|
|
2853
2863
|
- `SpawnDirectorState` (interface): interface SpawnDirectorState { wave: number; elapsed: number; waveElapsed: number; budget: number; alert: number; spawnedThisWave: number; spawnedTotal: number; rng: RandomSeed; done: boolean } — ⚠ undocumented
|
|
@@ -370,6 +370,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
370
370
|
|
|
371
371
|
- `createFastTravelNetwork` (function) · `import { createFastTravelNetwork } from "@jgengine/core/world"`
|
|
372
372
|
|
|
373
|
+
## feedback-cue-sounds — default procedural hit/hurt/kill/pickup/objective/wave/deny reactions on an "sfx" bus, overridable per game
|
|
374
|
+
|
|
375
|
+
- `withFeedbackCueSounds` (function) · `import { withFeedbackCueSounds } from "@jgengine/core/audio/feedbackCues"`
|
|
376
|
+
|
|
373
377
|
## feedback-mixer — map sim telemetry to camera, audio and haptic parameters through curves, smoothing and threshold events
|
|
374
378
|
|
|
375
379
|
- `createFeedbackMixer` (function) · `import { createFeedbackMixer } from "@jgengine/core/vfx/feedbackMixer"`
|