@jgengine/core 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/dist/ai/policeResponse.d.ts +253 -0
  3. package/dist/ai/policeResponse.js +421 -0
  4. package/dist/anim/actionLayer.d.ts +47 -0
  5. package/dist/anim/actionLayer.js +63 -0
  6. package/dist/anim/ikSolver.d.ts +14 -0
  7. package/dist/anim/ikSolver.js +37 -0
  8. package/dist/anim/locomotionGraph.d.ts +58 -3
  9. package/dist/anim/locomotionGraph.js +142 -15
  10. package/dist/anim/ragdollRig.d.ts +53 -0
  11. package/dist/anim/ragdollRig.js +132 -0
  12. package/dist/anim/skeletonStandard.d.ts +140 -0
  13. package/dist/anim/skeletonStandard.js +326 -0
  14. package/dist/combat/damageRuntime.d.ts +14 -1
  15. package/dist/combat/damageRuntime.js +62 -4
  16. package/dist/combat/death.js +7 -3
  17. package/dist/combat/deathReason.d.ts +7 -1
  18. package/dist/combat/effects.d.ts +25 -0
  19. package/dist/combat/effects.js +16 -0
  20. package/dist/combat/statusRuntime.d.ts +2 -0
  21. package/dist/combat/statusRuntime.js +1 -0
  22. package/dist/economy/loanBook.d.ts +182 -0
  23. package/dist/economy/loanBook.js +303 -0
  24. package/dist/economy/market.d.ts +161 -0
  25. package/dist/economy/market.js +248 -0
  26. package/dist/economy/transactionLedger.d.ts +162 -0
  27. package/dist/economy/transactionLedger.js +301 -0
  28. package/dist/editor/creatorStorage.js +3 -1
  29. package/dist/editor/simulation.d.ts +5 -2
  30. package/dist/editor/simulation.js +29 -19
  31. package/dist/game/cameraConfig.d.ts +14 -0
  32. package/dist/game/clipRoles.d.ts +5 -4
  33. package/dist/game/clipRoles.js +34 -4
  34. package/dist/game/events.d.ts +27 -0
  35. package/dist/game/gameSave.d.ts +142 -0
  36. package/dist/game/gameSave.js +174 -0
  37. package/dist/game/playableGame.d.ts +74 -6
  38. package/dist/game/saveSlotIndex.d.ts +54 -0
  39. package/dist/game/saveSlotIndex.js +71 -0
  40. package/dist/game/saveSlots.d.ts +2 -0
  41. package/dist/game/saveSlots.js +1 -1
  42. package/dist/game/saveStore.d.ts +8 -0
  43. package/dist/game/saveStore.js +16 -0
  44. package/dist/gameplay.d.ts +17 -2
  45. package/dist/gameplay.js +15 -0
  46. package/dist/inventory/carryLoad.d.ts +48 -0
  47. package/dist/inventory/carryLoad.js +44 -0
  48. package/dist/inventory/equipment.d.ts +95 -0
  49. package/dist/inventory/equipment.js +169 -0
  50. package/dist/inventory/inventoryModel.d.ts +15 -6
  51. package/dist/inventory/inventoryModel.js +54 -5
  52. package/dist/inventory/shapedGrid.d.ts +26 -0
  53. package/dist/inventory/shapedGrid.js +47 -0
  54. package/dist/loot/lootGen.d.ts +186 -0
  55. package/dist/loot/lootGen.js +437 -0
  56. package/dist/loot/lootWeapon.d.ts +35 -0
  57. package/dist/loot/lootWeapon.js +70 -0
  58. package/dist/meta/changelog.d.ts +1 -1
  59. package/dist/meta/changelog.js +156 -1
  60. package/dist/mission/compositions.d.ts +85 -0
  61. package/dist/mission/compositions.js +130 -0
  62. package/dist/mission/mission.d.ts +435 -0
  63. package/dist/mission/mission.js +770 -0
  64. package/dist/mission/triggers.d.ts +61 -0
  65. package/dist/mission/triggers.js +108 -0
  66. package/dist/nav/laneGraph.d.ts +178 -0
  67. package/dist/nav/laneGraph.js +671 -0
  68. package/dist/nav/traffic.d.ts +222 -0
  69. package/dist/nav/traffic.js +918 -0
  70. package/dist/physics/kinematicVehicle.d.ts +36 -0
  71. package/dist/physics/kinematicVehicle.js +15 -1
  72. package/dist/physics/vehicleDamage.d.ts +136 -0
  73. package/dist/physics/vehicleDamage.js +214 -0
  74. package/dist/physics/vehicleParts.d.ts +62 -0
  75. package/dist/physics/vehicleParts.js +65 -0
  76. package/dist/progression/gearScore.d.ts +56 -0
  77. package/dist/progression/gearScore.js +59 -0
  78. package/dist/progression/skillTree.d.ts +129 -0
  79. package/dist/progression/skillTree.js +265 -0
  80. package/dist/progression/xpCurve.d.ts +60 -0
  81. package/dist/progression/xpCurve.js +141 -0
  82. package/dist/runtime/context/combat.js +11 -0
  83. package/dist/scene/builtinSceneKinds.js +2 -0
  84. package/dist/settings/accessibilitySettings.d.ts +81 -0
  85. package/dist/settings/accessibilitySettings.js +152 -0
  86. package/dist/settings/graphicsProfile.d.ts +3 -0
  87. package/dist/settings/graphicsProfile.js +4 -0
  88. package/dist/settings/settingsModel.d.ts +1 -1
  89. package/dist/settings/settingsModel.js +1 -0
  90. package/dist/sim/nearAgents.d.ts +8 -1
  91. package/dist/sim/nearAgents.js +34 -17
  92. package/dist/sim/offScreen.d.ts +2 -1
  93. package/dist/sim/offScreen.js +14 -3
  94. package/dist/sim/population.d.ts +147 -0
  95. package/dist/sim/population.js +182 -0
  96. package/dist/sim/queue.d.ts +90 -0
  97. package/dist/sim/queue.js +171 -0
  98. package/dist/sim/rides.d.ts +287 -0
  99. package/dist/sim/rides.js +471 -0
  100. package/dist/sim/routine.js +19 -11
  101. package/dist/ui/loadingTips.d.ts +41 -0
  102. package/dist/ui/loadingTips.js +64 -0
  103. package/dist/ui/menuNav.d.ts +79 -0
  104. package/dist/ui/menuNav.js +147 -0
  105. package/dist/ui/menuTransition.d.ts +50 -0
  106. package/dist/ui/menuTransition.js +67 -0
  107. package/dist/ui.d.ts +2 -0
  108. package/dist/ui.js +2 -0
  109. package/dist/vfx/effectLibrary.d.ts +98 -0
  110. package/dist/vfx/effectLibrary.js +181 -0
  111. package/dist/vfx/particleBudget.d.ts +40 -0
  112. package/dist/vfx/particleBudget.js +39 -0
  113. package/dist/vfx/particleDirector.d.ts +16 -0
  114. package/dist/vfx/particleDirector.js +18 -0
  115. package/dist/world/authoredSimulation.js +7 -1
  116. package/dist/world/captureCamera.d.ts +30 -0
  117. package/dist/world/captureCamera.js +19 -0
  118. package/dist/world/decalKind.d.ts +48 -0
  119. package/dist/world/decalKind.js +86 -0
  120. package/dist/world/features.d.ts +18 -5
  121. package/dist/world/features.js +1 -0
  122. package/dist/world/grassKind.d.ts +5 -0
  123. package/dist/world/grassKind.js +3 -0
  124. package/dist/world/terraform.d.ts +8 -0
  125. package/dist/world/terrain.d.ts +1 -0
  126. package/dist/world/terrain.js +2 -0
  127. package/dist/world.d.ts +7 -2
  128. package/dist/world.js +5 -0
  129. package/package.json +1 -1
  130. package/skills/jgengine/api.md +2 -2
  131. package/skills/jgengine-combat/SKILL.md +9 -1
  132. package/skills/jgengine-combat/api.md +5 -4
  133. package/skills/jgengine-combat/reference.md +4 -0
  134. package/skills/jgengine-gameplay/SKILL.md +86 -0
  135. package/skills/jgengine-gameplay/api.md +485 -16
  136. package/skills/jgengine-gameplay/capabilities.md +131 -4
  137. package/skills/jgengine-gameplay/reference-systems.md +118 -0
  138. package/skills/jgengine-world/SKILL.md +8 -0
  139. package/skills/jgengine-world/api.md +243 -11
  140. package/skills/jgengine-world/capabilities.md +61 -1
  141. package/skills/jgengine-world/recipes/character-animation.md +25 -2
  142. package/skills/jgengine-world/recipes/vehicle-feel.md +5 -0
  143. package/skills/jgengine-world/reference.md +1 -1
@@ -194,6 +194,37 @@
194
194
  - `PostListingResult` (type): type PostListingResult = | { status: "ok"; listing: Listing } | { status: "rejected"; reason: PostListingReason } — Result of {@link ListingBook.post}.
195
195
  - `createListingBook` (function): function createListingBook(config: ListingBookConfig): ListingBook — A player-driven listing marketplace: post/cancel/buy against a shared book with a house cut on every sale, an expiry sweep that pulls unsold goods out of circulation, and a per-seller collection box holding sale proceeds and returned items until claimed. Buyer/seller wallet and inventory movement is the caller's job (mirrors `game/trade`'s split) — this primitive owns only the listing lifecycle and the escrowed collection-box bookkeeping behind it.
196
196
 
197
+ ## @jgengine/core/economy/loanBook
198
+
199
+ - `LoanAdvanceResult` (interface): interface LoanAdvanceResult — What one {@link LoanBook.advance} settled, and what it could not.
200
+ - `LoanBook` (interface): interface LoanBook — The loans a business owes: schedules, interest, arrears and default, posted to a ledger.
201
+ - `LoanBookOptions` (interface): interface LoanBookOptions — Options for {@link createLoanBook}.
202
+ - `LoanBookState` (interface): interface LoanBookState — Serializable loan-book state.
203
+ - `LoanBookStats` (interface): interface LoanBookStats — Counters of a loan book, for a probe or a summary line.
204
+ - `LoanPayment` (interface): interface LoanPayment — One scheduled repayment, in integer minor units.
205
+ - `LoanRecord` (interface): interface LoanRecord — One live loan: its plan plus how far it has been paid.
206
+ - `LoanRepayment` (interface): interface LoanRepayment — Outcome of an early repayment.
207
+ - `LoanSchedule` (interface): interface LoanSchedule — A full repayment plan: every payment, its interest/principal split and the running balance.
208
+ - `LoanState` (interface): interface LoanState — Saved progress of one loan; the schedule is rebuilt from its terms on restore.
209
+ - `LoanTerms` (interface): interface LoanTerms — Money in this module is an integer count of **minor units** — cents when the currency has two decimals, whole coins when it has none. Principal, interest, payments and balances are safe integers, so a schedule adds up exactly.
210
+ - `buildLoanSchedule` (function): function buildLoanSchedule(terms: LoanTerms): LoanSchedule — Build a loan's repayment schedule: one entry per period with its due time, interest/principal split and the balance left, in integer minor units. An amortized loan levels the payment over the outstanding balance (`P·i / (1 − (1+i)^−n)`); a flat loan charges interest on the original principal. Either way the final payment absorbs the rounding, so the principal parts sum to the principal exactly and the balance ends at zero. Deterministic and pure.
211
+ - `createLoanBook` (function): function createLoanBook(options: LoanBookOptions): LoanBook — The loans a business owes, over a {@link TransactionLedger}: each draw posts the principal to the borrower's account, each settled payment posts its interest (`"loan-interest"`) and principal (`"loan-principal"`) as separate reason-tagged entries, and an early repayment posts `"loan-repayment"`. A payment that comes due with no funds is left in arrears and counted once per advance; after `maxMissed` the loan is defaulted and stops being advanced. State is plain data — `snapshot()` stores terms and progress, and the schedule is rebuilt from the terms.
212
+
213
+ ## @jgengine/core/economy/market
214
+
215
+ - `Market` (interface): interface Market — A live market: prices from supply and demand over finite stock, with serializable state.
216
+ - `MarketConfig` (interface): interface MarketConfig — Options for {@link createMarket}: the goods and the price/demand policy they share.
217
+ - `MarketGoodDef` (interface): interface MarketGoodDef — Money in this module is an integer count of **minor units** — cents when the currency has two decimals, whole coins when it has none. Every price, cost and total is a safe integer, so a simulation never accumulates float drift; the caller converts at the display boundary.
218
+ - `MarketGoodState` (interface): interface MarketGoodState — The mutable half of a good: what a save stores, with the definition kept as authored data.
219
+ - `MarketPriceInput` (interface): interface MarketPriceInput — Everything a price curve reads: the good's resolved policy plus its live stock and demand.
220
+ - `MarketQuote` (interface): interface MarketQuote — A non-mutating read of what a sale would cost and how it would move the price.
221
+ - `MarketSale` (interface): interface MarketSale — A served sale: what the buyer paid per unit and in total, in minor units.
222
+ - `MarketSaleRejection` (interface): interface MarketSaleRejection — Why a sale could not be served.
223
+ - `MarketSaleResult` (type): type MarketSaleResult = MarketSale | MarketSaleRejection — A sale outcome: the price the buyers paid, or why it could not be served.
224
+ - `MarketSnapshot` (interface): interface MarketSnapshot — Serializable market state: the mutable half of every good plus the running counters.
225
+ - `MarketStats` (interface): interface MarketStats — Counters and stocks of a market, for a probe or a summary line.
226
+ - `createMarket` (function): function createMarket(config: MarketConfig = {}): Market — A market whose price comes from supply and demand over finite stock, as plain serializable state.
227
+
197
228
  ## @jgengine/core/economy/priceHistory
198
229
 
199
230
  - `PriceHistory` (interface): interface PriceHistory — A bounded rolling record of completed sales per item. See {@link createPriceHistory}.
@@ -261,6 +292,22 @@
261
292
  - `TechState` (type): type TechState = UnlockState — ⚠ undocumented
262
293
  - `TechTree` (interface): interface TechTree { has(userId: string, id: string): boolean; canUnlock(userId: string, id: string): TechCheck; unlock(userId: string, id: string): TechCheck; available(userId: string): TechNodeDef[]; recipes(userId: string): string[]; list(userId: string): … — ⚠ undocumented
263
294
 
295
+ ## @jgengine/core/economy/transactionLedger
296
+
297
+ - `ChargeCursor` (interface): interface ChargeCursor — Mutable per-charge progress, kept apart from the {@link RecurringCharge} definition.
298
+ - `LedgerAdvanceResult` (interface): interface LedgerAdvanceResult — What one {@link TransactionLedger.chargeDue} settled, and what it could not.
299
+ - `LedgerEntry` (interface): interface LedgerEntry — Money in this module is an integer count of **minor units** — cents when the currency has two decimals, whole coins when it has none. Every posted amount and every balance is a safe integer, so a long simulation never accumulates float drift. `currency` is a free string the game owns.
300
+ - `LedgerEntryFilter` (interface): interface LedgerEntryFilter — A filter over the journal.
301
+ - `LedgerPostInput` (interface): interface LedgerPostInput — A post to attempt.
302
+ - `LedgerPostResult` (type): type LedgerPostResult = | { ok: true; entry: LedgerEntry } | { ok: false; reason: "unknown-account" | "invalid-amount" | "invalid-reason" | "insufficient-funds" } — Outcome of {@link TransactionLedger.post}. A rejection leaves every balance untouched.
303
+ - `LedgerState` (interface): interface LedgerState — Fully serializable ledger state: clock, balances, journal, totals, charges and cursors.
304
+ - `LedgerStats` (interface): interface LedgerStats — Counters of a ledger, for a probe or a summary line.
305
+ - `LedgerTotals` (interface): interface LedgerTotals — What one reason code did to the books, kept across entry trimming.
306
+ - `RecurringCharge` (interface): interface RecurringCharge — A recurring charge — upkeep, rent, a subscription — settled by {@link TransactionLedger.chargeDue}.
307
+ - `TransactionLedger` (interface): interface TransactionLedger — The books: balances with a reason-tagged journal, and recurring charges on a cadence.
308
+ - `TransactionLedgerOptions` (interface): interface TransactionLedgerOptions — Options for {@link createTransactionLedger}.
309
+ - `createTransactionLedger` (function): function createTransactionLedger(options: TransactionLedgerOptions = {}): TransactionLedger — The books a business keeps: per-account, per-currency balances in integer minor units with a journal that records every movement, the balance it produced and the reason it happened. {@link TransactionLedger.post} is the one money path — sales, purchases, upkeep and loan payments all go through it, so nothing moves without a reason on the record. {@link TransactionLedger.chargeDue} settles recurring charges (upkeep, rent) on their own cadence, bounded per advance; a cycle with no funds is counted as missed instead of overdrawing. Journal memory is bounded by `maxEntries`, with `totals()` keeping the per-reason summary beyond it.
310
+
264
311
  ## @jgengine/core/economy/unlockPoints
265
312
 
266
313
  - `SpendOptions` (interface): interface SpendOptions — Options for {@link UnlockPoints.spend}.
@@ -326,7 +373,8 @@
326
373
  - `ChaseCameraConfig` (interface): interface ChaseCameraConfig — Speed-reactive vehicle chase rig (#27) — speed→FOV, spring arm, procedural shake, interior views.
327
374
  - `ChaseView` (type): type ChaseView = "chase" | "cockpit" | "hood" | "rear" — Fixed interior view for the chase rig (#27).
328
375
  - `CinematicCameraConfig` (interface): interface CinematicCameraConfig — Scripted keyframe / path player (#29). When set it overrides the active rig.
329
- - `FirstPersonCameraConfig` (interface): interface FirstPersonCameraConfig { eyeHeight?: number; sensitivity?: number; maxPitch?: number; reticle?: boolean; viewmodel?: boolean } — ⚠ undocumented
376
+ - `FirstPersonCameraConfig` (interface): interface FirstPersonCameraConfig { eyeHeight?: number; sensitivity?: number; maxPitch?: number; reticle?: boolean; viewmodel?: boolean; collision?: false | FirstPersonCollisionConfig } — ⚠ undocumented
377
+ - `FirstPersonCollisionConfig` (interface): interface FirstPersonCollisionConfig — Clearance tuning for the first-person camera's collision pull-in.
330
378
  - `GameCameraConfig` (interface): interface GameCameraConfig — Camera tuning for the shell's rig stack: pick the rig via `rig`, then tune it through its matching config block. All fields optional — the default is the third-person orbit rig.
331
379
  - `InspectionCameraConfig` (interface): interface InspectionCameraConfig — Model-viewer / inspection rig (#207.7) — orbit + pan + anchored zoom around a fixed point, never reads player input.
332
380
  - `InspectionZoomAnchor` (type): type InspectionZoomAnchor = "target" | "cursor" | "center" — How scroll-zoom re-anchors the view for the inspection rig (#207.7): - `target` — dolly toward the orbit target (classic OrbitControls behavior). - `cursor` — dolly toward the point under the pointer. - `center` — dolly toward the viewport center; equivalent to `target` for an OrbitControls-driven rig, since the camera always faces `target` and that point already projects to the exact center of the viewport.
@@ -373,11 +421,11 @@
373
421
 
374
422
  ## @jgengine/core/game/clipRoles
375
423
 
376
- - `ClipRole` (type): type ClipRole = | "idle" | "walk" | "run" | "attack" | "hit" | "death" | "jump" | "spawn" | "interact" | "cheer" — Semantic animation roles a GLB clip name can map to. Rigged packs name the same motion differently (`Idle` / `Walking_A` / `Armature|Run`); roles are the pack-agnostic vocabulary the engine defaults from.
424
+ - `ClipRole` (type): type ClipRole = | "idle" | "walk" | "run" | "strafe" | "backward" | "attack" | "hit" | "death" | "jump" | "spawn" | "interact" | "cheer" — Semantic animation roles a GLB clip name can map to. Rigged packs name the same motion differently (`Idle` / `Walking_A` / `Armature|Run`); roles are the pack-agnostic vocabulary the engine defaults from.
377
425
  - `ClipRoleTable` (interface): interface ClipRoleTable — Keyword table driving {@link classifyClip}: lowercase keywords matched against a clip name's tokens (exact token match beats prefix match). Extend or replace per game/pack — the table is plain data, no genre assumptions.
378
426
  - `DEFAULT_CLIP_ROLE_TABLE` (const): const DEFAULT_CLIP_ROLE_TABLE: ClipRoleTable — Default vocabulary covering KayKit, Quaternius, and Mixamo-style clip naming.
379
427
  - `classifyClip` (function): function classifyClip(name: string, table: ClipRoleTable = DEFAULT_CLIP_ROLE_TABLE): ClipRole | null — Classify a single GLB clip name into a {@link ClipRole}, or `null` when nothing matches (or an exclusion token like `pose` disqualifies it). Deterministic: exact token matches beat prefix matches; remaining ties resolve by fixed role priority (death > hit > attack > … > idle).
380
- - `defaultAnimationForClips` (function): function defaultAnimationForClips(clips: readonly string[], table: ClipRoleTable = DEFAULT_CLIP_ROLE_TABLE): ModelAnimationConfig | undefined — Build the default `ModelConfig.animation` for a rigged asset from its catalog clip names: speed-driven idle/walk/run states plus hit/death/attack one-shots where identifiable. Returns `undefined` when no idle clip is identifiable — an ambiguous rig renders its bind pose rather than guessing. Walk falls back to idle when the pack has no walk clip.
428
+ - `defaultAnimationForClips` (function): function defaultAnimationForClips(clips: readonly string[], table: ClipRoleTable = DEFAULT_CLIP_ROLE_TABLE): ModelAnimationConfig | undefined — Build the default `ModelConfig.animation` for a rigged asset from its catalog clip names: speed-driven idle/walk/run states plus hit/death/attack one-shots where identifiable, and the lateral/backward states when the pack names them (KayKit's `Running_Strafe_Left`, `Walking_Backwards`). Returns `undefined` when no idle clip is identifiable - an ambiguous rig renders its bind pose rather than guessing. Walk falls back to idle when the pack has no walk clip.
381
429
  - `resolveAnimationConfig` (function): function resolveAnimationConfig(animation: ModelAnimationConfig | "auto" | "none" | undefined, clips: readonly string[] | undefined, table: ClipRoleTable = DEFAULT_CLIP_ROLE_TABLE): ModelAnimationConfig | undefined — Collapse `ModelConfig.animation`'s widened union to a concrete playback config: explicit configs pass through, `"none"`/absent render the bind pose, and `"auto"` derives states and one-shots from the model's clip names via {@link defaultAnimationForClips}. The shell calls this with the loaded GLB's actual clip names, so `"auto"` works on any rigged model — catalog-resolved ids are stamped `"auto"` automatically. An object with `auto: true` derives the same defaults, then replaces each explicitly supplied field whole, including empty maps.
382
430
  - `rolesFromClips` (function): function rolesFromClips(clips: readonly string[], table: ClipRoleTable = DEFAULT_CLIP_ROLE_TABLE): Partial<Record<ClipRole, string[]>> — Group a GLB's clip names by role. Each role's variants are sorted simplest-first — fewest name tokens, then lexicographic — so the canonical variant (`Idle` over `2H_Melee_Idle`, KayKit `Walking_A` over `Walking_B`) is always first: stable output for the same clip set regardless of source order.
383
431
 
@@ -504,14 +552,15 @@
504
552
  - `CombatVfxEvent` (interface): interface CombatVfxEvent — A transient sprite-particle effect the shell renders once and expires — one burst of `kind`, tinted `color`, anchored at `from` (and `to` for travel/beam effects).
505
553
  - `CommandRefusedEvent` (interface): interface CommandRefusedEvent — A command run through `ctx.game.commands` (or a bound host) was refused. `reason` is the machine code; `message` is the player-facing text the command's `validate` returned, when it gave one.
506
554
  - `CosmeticsChangedEvent` (interface): interface CosmeticsChangedEvent { userId: string; slots: Record<string, string> } — ⚠ undocumented
507
- - `DeathReason` (type): type DeathReason = | { kind: "player_kill"; killerUserId: string; via?: { item?: string } } | { kind: "environment"; source: string } | { kind: "self"; source: string } — Why an entity died — who or what gets credit, for drop/command rules and the `entity.died` event.
555
+ - `DamageAppliedEvent` (interface): interface DamageAppliedEvent — One application of damage to one target: what hit it, for how much, and where its health stands. `target` is always the thing that took the blow, so a hero and a keep hit in the same frame raise two separate events and a HUD reads only the one whose `target` it owns.
556
+ - `DeathReason` (type): type DeathReason = | { kind: "player_kill"; killerUserId: string; source?: string; via?: { item?: string } } | { kind: "environment"; source: string; attacker?: string } | { kind: "self"; source: string } — Why an entity died — who or what gets credit, for drop/command rules and the `entity.died` event. `source` names what dealt it: a player kill's killer instance id, or the hazard/item of an environment death; `attacker` carries the non-player entity's instance id when one dealt it.
508
557
  - `EmotePlayedEvent` (interface): interface EmotePlayedEvent { from: string; emoteId: string; at: readonly [number, number, number]; recipients: readonly string[] } — ⚠ undocumented
509
558
  - `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.
510
- - `EntityDiedEvent` (interface): interface EntityDiedEvent { instanceId: string; catalogId: string; userId?: string; displayName?: string; reason: DeathReason; position: [number, number, number]; serverId?: string } — ⚠ undocumented
559
+ - `EntityDiedEvent` (interface): interface EntityDiedEvent { instanceId: string; catalogId: string; userId?: string; displayName?: string; reason: DeathReason; source?: string; position: [number, number, number]; serverId?: string } — ⚠ undocumented
511
560
  - `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
512
561
  - `FormChangedEvent` (interface): interface FormChangedEvent { instanceId: string; formId: string | null } — ⚠ undocumented
513
562
  - `GameEventHandler` (type): type GameEventHandler<TPayload> = (payload: TPayload) => void — ⚠ undocumented
514
- - `GameEventMap` (interface): interface GameEventMap { "entity.died": EntityDiedEvent; "entity.floatText": EntityFloatTextEvent; "combat.telegraph": CombatTelegraphEvent; "combat.vfx": CombatVfxEvent; "combat.vfxInstance": CombatVfxInstanceEvent; "combat.telegraphCancelled": CombatTelegraphCa… — ⚠ undocumented · used by `createGameEvents`: A typed publish/subscribe bus for gameplay events that systems and HUDs subscribe to.
563
+ - `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.
515
564
  - `GameEvents` (interface): interface GameEvents<TMap extends GameEventMap = GameEventMap> { on<TName extends keyof TMap>(name: TName, handler: GameEventHandler<TMap[TName]>): () => void; subscribe<TName extends keyof TMap>(name: TName, handler: GameEventHandler<TMap[TName]>): () => void; emit<TName extends keyof TMap>(name: TNam… — ⚠ undocumented · used by `createGameEvents`: A typed publish/subscribe bus for gameplay events that systems and HUDs subscribe to.
516
565
  - `InventoryAddedEvent` (interface): interface InventoryAddedEvent { userId: string; item: string; count: number; source?: string } — ⚠ undocumented
517
566
  - `LootDrop` (interface): interface LootDrop — One granted reward: an inventory `item` id or a `currency` id, plus how many. Named because every consumer of `loot.granted` needs it — a toast renderer, a loot log, a session summary — and each one that re-declares the shape inline is a cast the compiler cannot check.
@@ -556,6 +605,18 @@
556
605
  - `isPlaying` (function): function isPlaying(ctx: GameContext): boolean — ⚠ undocumented
557
606
  - `setGamePhase` (function): function setGamePhase(ctx: GameContext, phase: GamePhase): void — Set the current phase. Publishes it to `ctx.game.store` (React reads it via `useGamePhase`) and gates the shell's on-screen touch controls in one call — `playing` shows them, every other phase hides them. This is the whole "main menu shouldn't show touch controls" wiring: call it once per phase transition and the dock follows.
558
607
 
608
+ ## @jgengine/core/game/gameSave
609
+
610
+ - `GAME_SAVE_VERSION` (const): const GAME_SAVE_VERSION: 1 — Current save-format version of {@link GameSaveDocument}. Bump it with {@link GameSaveConfig.migrate} when the shape changes.
611
+ - `GameSave` (interface): interface GameSave — A pluggable-backend game save: one versioned document holding a section per system, with named slots, caller-policy autosave, migration and a state digest. `capture()` builds the document from the live systems; `save()`/`touch()` write it (directly or on the autosave debounce); `load()` reads a slot and applies every section back through its system's `restore`. The shared RNG's cursor rides along, so the seed survives the save and a reloaded run continues the same sequence.
612
+ - `GameSaveConfig` (interface): interface GameSaveConfig — How a {@link createGameSave} is wired. Storage is the narrow injected {@link SaveBackend}, never `localStorage` welded in.
613
+ - `GameSaveDocument` (interface): interface GameSaveDocument — The one save document: a format `version`, the game and clock metadata a save-select menu shows, the world's shared RNG cursor, and one section per registered system. There is deliberately no second per-system format — every system's state lives under `systems[<name>]`.
614
+ - `GameSaveRng` (interface): interface GameSaveRng — The world's shared injected RNG: its `seed` and the live `stream` whose cursor is persisted.
615
+ - `GameSaveRngState` (interface): interface GameSaveRngState — The persisted cursor of the world's shared injected RNG, plus the seed that produced it.
616
+ - `GameSaveSystem` (interface): interface GameSaveSystem<T = unknown> — One stateful system inside a game save: a stable `name` (its section key in the document) and the `snapshot()`/`restore()` pair every Lane B module exposes. The name is the contract — a document written by one build restores into the next build's system with the same name. `reset` runs when a loaded slot has no section for this system (a fresh slot), so switching to an empty slot starts a new game instead of leaving the previous one's state behind; a system without it is left as-is.
617
+ - `createGameSave` (function): function createGameSave(config: GameSaveConfig): GameSave — Create a {@link GameSave}. Register every stateful system once; `capture()` writes one versioned document with a section per system, `save()`/`touch()` persist it through the injected backend (autosave cadence is the caller's), `load()` restores every section and the shared RNG cursor, and `digest()`/`digests()` give a determinism test a hash over the whole state.
618
+ - `gameSaveDigest` (function): function gameSaveDigest(document: GameSaveDocument): string — A stable hash over the state-bearing part of a document — every system section, the world clock and the shared RNG cursor — excluding the wall-clock `savedAt`, which is not state.
619
+
559
620
  ## @jgengine/core/game/keyValueStore
560
621
 
561
622
  - `KeyValueStorage` (interface): interface KeyValueStorage — Structural, DOM-free storage backend: the browser `localStorage` satisfies it, as does a test stub or `null`. The one storage seam core primitives target so persistence code never needs the DOM `Storage` lib.
@@ -763,7 +824,8 @@
763
824
  - `CinematicCameraConfig` (interface): interface CinematicCameraConfig — Scripted keyframe / path player (#29). When set it overrides the active rig.
764
825
  - `DirectionalLightingConfig` (interface): interface DirectionalLightingConfig { color?: string; intensity?: number; position: readonly [number, number, number]; castShadow?: boolean; shadowMapSize?: number; shadowCameraSize?: number; cascades?: number; shadowMaxFar?: number; shadowBias?: number; shadowNormalBias?: nu… — ⚠ undocumented · used by `FALLBACK_KEY_LIGHT` (@jgengine/core/render/lightingBalance): Soft key added when authored lighting has no directional light: high, off to one side, shadowless.
765
826
  - `EntitySpriteConfig` (interface): interface EntitySpriteConfig { url: string; width: number; height: number; y: number; clip?: {atlas: SpriteAtlas; animation: string} } — ⚠ undocumented
766
- - `FirstPersonCameraConfig` (interface): interface FirstPersonCameraConfig { eyeHeight?: number; sensitivity?: number; maxPitch?: number; reticle?: boolean; viewmodel?: boolean } — ⚠ undocumented
827
+ - `FirstPersonCameraConfig` (interface): interface FirstPersonCameraConfig { eyeHeight?: number; sensitivity?: number; maxPitch?: number; reticle?: boolean; viewmodel?: boolean; collision?: false | FirstPersonCollisionConfig } — ⚠ undocumented
828
+ - `FirstPersonCollisionConfig` (interface): interface FirstPersonCollisionConfig — Clearance tuning for the first-person camera's collision pull-in.
767
829
  - `FlightConfig` (interface): interface FlightConfig — Free-flight families moved into the walk controller so one seam covers ground + air — creative/spectator/noclip/hover are weightless, aircraft, rotorcraft and rockets use `physics/aircraftDynamics`.
768
830
  - `GameCameraConfig` (interface): interface GameCameraConfig — Camera tuning for the shell's rig stack: pick the rig via `rig`, then tune it through its matching config block. All fields optional — the default is the third-person orbit rig.
769
831
  - `GameCaptureConfig` (interface): interface GameCaptureConfig — How a game makes itself screenshottable: the commands that reach live play or a named screen, the framings worth re-capturing, and the progress metrics a bot playtest samples. Declaring these once is what lets a capture host reproduce a view instead of re-deriving it by hand every run.
@@ -776,12 +838,15 @@
776
838
  - `ModelAnimationConfig` (interface): interface ModelAnimationConfig — Rig playback for a `ModelConfig`'s GLTF animation clips — looping idles, one-shots, and held poses.
777
839
  - `ModelAnimationStates` (interface): interface ModelAnimationStates — Movement-state clip set for `ModelAnimationConfig.states`: the shell reads the entity's live speed each frame and crossfades between these clips, so a walking mob animates without any game-side driver.
778
840
  - `ModelAttachment` (interface): interface ModelAttachment — Parents a prop/weapon model to a named bone or node on the host model's rig — a sword on `handslot.r`, a spellbook offhand — following the bone's animated transform each frame.
779
- - `ModelConfig` (interface): interface ModelConfig { url: string; scale?: number; targetHeight?: number; y?: number; anchor?: "center" | "origin"; dims?: ModelDims; collisionMesh?: CollisionMeshData; material?: ModelMaterialOverride; surfaceDetail?: boolean; materialAssets?: readonly Materi… — ⚠ undocumented · used by `PartMotionRig` (@jgengine/shell/render/PartMotion): Procedural motion rig for a rig-less part-composed character (`ModelPart.role` — see `@jgengine/core/game/partAnimation`).
841
+ - `ModelConfig` (interface): interface ModelConfig { url: string; scale?: number; targetHeight?: number; y?: number; anchor?: "center" | "origin"; dims?: ModelDims; collisionMesh?: CollisionMeshData; material?: ModelMaterialOverride; surfaceDetail?: boolean; wind?: ModelWindConfig | false; … — ⚠ undocumented · used by `PartMotionRig` (@jgengine/shell/render/PartMotion): Procedural motion rig for a rig-less part-composed character (`ModelPart.role` — see `@jgengine/core/game/partAnimation`).
780
842
  - `ModelIkConfig` (interface): interface ModelIkConfig — Bone names and limits for {@link ModelConfig.ik}.
843
+ - `ModelLodConfig` (interface): interface ModelLodConfig — Distance tiers for one character model. Distances are metres from the camera; every field is data so a game can set its own thresholds per model and retune them without touching the engine.
844
+ - `ModelLookAtConfig` (interface): interface ModelLookAtConfig — {@link ModelIkConfig.lookAt}: the bone that turns, and what it turns toward.
781
845
  - `ModelMaterialMaps` (interface): interface ModelMaterialMaps extends Partial<Record<MaterialTextureRole, string>> — Real PBR map URLs (e.g. `buildMaterialCatalog(...).resolve(id)!.maps` from `@jgengine/assets`) layered onto a model's material — the seam for texturing an otherwise-flat/untextured GLB. Any supported MaterialTextureRole may be omitted to keep the model's own map. Physical roles use the renderer's physical material; map URLs and response parameters remain caller-owned.
782
846
  - `ModelMaterialOverride` (interface): interface ModelMaterialOverride extends MaterialSurfaceParameters — Per-entity PBR material override (#151.3) applied to every standard or physical material in the model's cloned scene graph.
783
847
  - `ModelPart` (interface): interface ModelPart — Static child model stacked at a fixed local offset under its parent's transform — no bone/rig resolution, unlike `ModelAttachment`. Assembles a compound entity (e.g. a modular castle wall + tower + roof) from several single-mesh kit pieces.
784
848
  - `ModelRimLight` (interface): interface ModelRimLight — Fresnel rim-light term added on top of a model's lighting — see {@link ModelMaterialOverride.rim}.
849
+ - `ModelWindConfig` (interface): interface ModelWindConfig — Wind sway for a foliage model; see {@link ModelConfig.wind}.
785
850
  - `MovementCommitFrame` (interface): interface MovementCommitFrame — One frame's movement resolution handed to `PlayerMovementConfig.beforeCommit`.
786
851
  - `MovementFeelConfig` (interface): interface MovementFeelConfig — Feel knobs for the built-in walk controller; see {@link PlayerMovementConfig.feel}.
787
852
  - `NoticePresentationConfig` (interface): interface NoticePresentationConfig extends NoticeFeedConfig — How the shell presents `ctx.game.notices`; every field is optional.
@@ -914,6 +979,16 @@
914
979
  - `createRunDraft` (function): function createRunDraft<TStat extends string = string, TData = unknown>(config: RunDraftConfig<TStat, TData>): RunDraft<TStat, TData> — A roguelike run built from stacking drafted modifier picks that reshape the run.
915
980
  - `createRunModifierStack` (function): function createRunModifierStack<TStat extends string = string, TData = unknown>(offers: readonly RunModifierOffer<TStat, TData>[]): RunModifierStack<TStat, TData> — ⚠ undocumented
916
981
 
982
+ ## @jgengine/core/game/saveSlotIndex
983
+
984
+ - `SaveSlotMetaOptions` (interface): interface SaveSlotMetaOptions — Bridging the versioned save document (`createGameSave`) to the save-select index (`createSaveSlots`): the document already carries `savedAt`, `time` and `playtime`, so a menu built on the index can show real saves instead of hand-maintained metadata.
985
+ - `SaveSlotSource` (interface): interface SaveSlotSource — The source a slot list is refreshed from — a `GameSave`, or a narrower reader.
986
+ - `SaveSlotsRefresh` (interface): interface SaveSlotsRefresh — The result of a refresh: what the menu should show now.
987
+ - `formatSavePlaytime` (function): function formatSavePlaytime(seconds: number): string — "4h 12m" / "12m" / "< 1m" — the playtime chip every save menu wants.
988
+ - `gameSaveSlotSource` (function): function gameSaveSlotSource(save: GameSave): SaveSlotSource — Adapt a {@link GameSave} as a {@link SaveSlotSource}. Kept separate so a host can supply its own reader (a cloud endpoint listing saves) with the same shape.
989
+ - `refreshSaveSlots` (function): function refreshSaveSlots(source: SaveSlotSource, model: SaveSlots, options: SaveSlotMetaOptions = {}): Promise<SaveSlotsRefresh> — Read every written slot's document and write the display metadata into a `SaveSlots` index — the glue between B5.3's save document and a save-select menu. Slots the index already knows that have no document are cleared, so a deleted save stops showing a stale Continue.
990
+ - `saveSlotMeta` (function): function saveSlotMeta(slot: string, document: GameSaveDocument, options: SaveSlotMetaOptions = {}): SaveSlotMeta — Project one save document onto the index's display metadata: name, last-saved time and a playtime chip (plus any caller `metaOf` fields). Pure, so a menu can build its list from documents a host fetched for it.
991
+
917
992
  ## @jgengine/core/game/saveSlots
918
993
 
919
994
  - `SaveSlotMeta` (interface): interface SaveSlotMeta — One entry in a {@link SaveSlots} index: a save/profile slot's *display* metadata, not its payload. The real save data lives in a `createSaveStore` slot; this record is only what a save-select menu renders — an id, an optional player-facing `name`, whether the slot is `empty`, when it was last written (`savedAt`), and a free-string `meta` bag the game fills with whatever the menu should show (level, playtime, location, chapter, a thumbnail ref, …). `meta` keys are opaque to this model — it never reads or branches on them.
@@ -1142,9 +1217,11 @@
1142
1217
  - `CatchUpPolicy` (type): type CatchUpPolicy = "each" | "sum" | "skip" — How cycles that came due between two {@link advanceLedger} calls are settled: - `"each"` — replay every missed cycle as its own transaction (bounded by the limits below); - `"sum"` — collapse the missed cycles into one transaction of the combined amount; - `"skip"` — apply only the most recent cycle and discard the rest (idle income that does not bank).
1143
1218
  - `Cell` (type): type Cell = readonly [number, number] — ⚠ undocumented · used by `canPlace` (@jgengine/core/inventory/shapedGrid): Check bounds and overlap for a discrete footprint, optionally ignoring the item being moved.
1144
1219
  - `CellGrid` (interface): interface CellGrid<T> — A fixed-size 2D board of cells, row-major from the top-left; `null` is an empty cell. Plain data, so it serializes and restores as-is.
1220
+ - `ChargeCursor` (interface): interface ChargeCursor — Mutable per-charge progress, kept apart from the {@link RecurringCharge} definition.
1145
1221
  - `ChargeOptions` (interface): interface ChargeOptions — Options for {@link charge}/{@link chargeAll}: opt one call into overdraft debt via `overdraft`.
1146
1222
  - `ChargeResult` (type): type ChargeResult = { status: "ok"; state: WalletState } | { status: "rejected"; reason: "insufficient-funds" } — Outcome of a {@link charge}/{@link chargeAll} attempt: `status: "ok"` carries the debited {@link WalletState}, while `status: "rejected"` leaves the wallet untouched and reports why (currently only `"insufficient-funds"`). Discriminate on `status` before reading `state`.
1147
1223
  - `ChaseCameraConfig` (interface): interface ChaseCameraConfig — Speed-reactive vehicle chase rig (#27) — speed→FOV, spring arm, procedural shake, interior views.
1224
+ - `ChaseMissionOptions` (interface): interface ChaseMissionOptions extends ComposedMissionOptions — Options for {@link chaseMission}: a target kept close until it is caught.
1148
1225
  - `Chat` (interface): interface Chat { register(def: ChatChannelDef): void; channels(): ChatChannelDef[]; send(fromUserId: string, body: string): ChatSendResult; send(fromUserId: string, channelId: string, body: string): ChatSendResult; recent(options?: {limit?: number; channe… — ⚠ undocumented
1149
1226
  - `ChatMessage` (interface): interface ChatMessage { id: string; channelId: string; fromUserId: string; body: string; at: number } — ⚠ undocumented · used by `createConvexChatTransport` (@jgengine/convex): Wires a game's Convex chat functions into the engine's ChatTransport contract: one live query per subscribed channel (the channel's recent h…
1150
1227
  - `ChatRateLimit` (interface): interface ChatRateLimit { count: number; perMs: number } — ⚠ undocumented
@@ -1160,6 +1237,7 @@
1160
1237
  - `CombatVfxInstanceEvent` (interface): interface CombatVfxInstanceEvent — The lifecycle op a {@link VfxInstanceStore} emits to its renderer sink. `upsert`/`update` carry the full merged {@link VfxInstanceState} (the renderer applies it directly, no merge); `stop` carries the id plus a fade duration. This is the payload of the `combat.vfxInstance` game event when the store is wired to the event bus.
1161
1238
  - `CompatibilityRule` (type): type CompatibilityRule = RequireRule | ForbidRule — A cross-slot compatibility rule constraining which part/tag/family combinations a modular item may hold.
1162
1239
  - `CompiledSystemSchedule` (interface): interface CompiledSystemSchedule — Deterministic compiled schedule: stage buckets, multi-subscribe channels, dependency validation. Order never depends on import order — only stage tables + explicit before/after constraints.
1240
+ - `ComposedMissionOptions` (interface): interface ComposedMissionOptions — Options shared by every composed mission.
1163
1241
  - `ComposedUse` (interface): interface ComposedUse<TWorld> — A resolved, ordered composition of behaviors for one item. Dispatch is transactional: `apply` commits the folded world only if every behavior in the chain succeeds, otherwise it returns the original world and state plus the first error.
1164
1242
  - `CompositionResult` (type): type CompositionResult<TWorld> = | { status: "ok"; composed: ComposedUse<TWorld> } | { status: "error"; reason: "unknown-behavior"; id: string } | { status: "error"; reason: "duplicate-behavior"; id: string } | { status: "error"; reason: "missing-capability"; id: string; capability: string } | { sta… — The outcome of composing an item's behavior refs: either a ready {@link ComposedUse} or a structured error naming the offending behavior.
1165
1243
  - `ConstraintViolation` (interface): interface ConstraintViolation — One failed {@link CompatibilityRule}, carrying the rule id, its kind, and a human-readable message for UI or generator diagnostics.
@@ -1170,14 +1248,23 @@
1170
1248
  - `DEFAULT_CHAT_BODY_LENGTH` (const): const DEFAULT_CHAT_BODY_LENGTH: 240 — ⚠ undocumented
1171
1249
  - `DEFAULT_CHAT_HISTORY_LIMIT` (const): const DEFAULT_CHAT_HISTORY_LIMIT: 100 — ⚠ undocumented
1172
1250
  - `DEFAULT_CHAT_RATE_LIMIT` (const): const DEFAULT_CHAT_RATE_LIMIT: ChatRateLimit — ⚠ undocumented
1251
+ - `DEFAULT_ESCAPE_MULTIPLE` (const): const DEFAULT_ESCAPE_MULTIPLE: 3 — A pursuit escapes at this multiple of its keep-close distance when none is declared.
1173
1252
  - `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.
1174
1253
  - `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.
1175
1254
  - `DEFAULT_PICKUP_RADIUS` (const): const DEFAULT_PICKUP_RADIUS: 2 — ⚠ undocumented
1176
1255
  - `DEFAULT_PING_CATEGORIES` (const): const DEFAULT_PING_CATEGORIES: Record<PingCategory, PingCategoryDef> — Content-agnostic default ping wheel: enemy / loot / location / danger.
1256
+ - `DEFAULT_PURSUE_DISTANCE` (const): const DEFAULT_PURSUE_DISTANCE: 3 — Default keep-close distance (m) for a `pursue` objective.
1257
+ - `DEFAULT_QUEUE_SEED_SERVICE_SECONDS` (const): const DEFAULT_QUEUE_SEED_SERVICE_SECONDS: 8 — Seconds one guest occupies the head of the line before any boarding interval has been measured. A bootstrap only: the estimate switches to the queue's own measured throughput as soon as two boardings have been observed.
1258
+ - `DEFAULT_QUEUE_WINDOW` (const): const DEFAULT_QUEUE_WINDOW: 8 — Boarding events the throughput window keeps.
1259
+ - `DEFAULT_REACH_RADIUS` (const): const DEFAULT_REACH_RADIUS: 2.5 — Default proximity radius (m) for a `reach` / `interact` objective.
1260
+ - `DEFAULT_RIDE_BREAKDOWN` (const): const DEFAULT_RIDE_BREAKDOWN: RideBreakdown — A ride's breakdown curve a park starts from when it has none and retunes only part of one.
1261
+ - `DEFAULT_RIDE_FUN_CURVE` (const): const DEFAULT_RIDE_FUN_CURVE: ResponseCurve — How empty a bored guest's fun need has to be before a ride starts to pull, by default.
1262
+ - `DEFAULT_RIDE_RATING_RANGE` (const): const DEFAULT_RIDE_RATING_RANGE: RideRatingRange — A 0–10 rating scale, the usual shape of a ride catalog. Override it when a park rates differently.
1177
1263
  - `DEFAULT_TOUCH_STYLE` (const): const DEFAULT_TOUCH_STYLE: TouchStyle — Skin used when neither the game nor the player picks one.
1178
1264
  - `DecayMeterSet` (interface): interface DecayMeterSet — Set of named survival meters (hunger/thirst/…) that drain and refill over game time.
1179
1265
  - `DecayMeterValues` (type): type DecayMeterValues = Record<string, number> — Plain-data meter values: `meter id → current value`. This is the whole serialized form — it drops straight into a `defineGame` state record and round-trips through save/load and multiplayer sync with no closure to rebuild.
1180
1266
  - `DecayModifier` (type): type DecayModifier = number | Record<string, number> — Rate multiplier for {@link decayMeters}: one scalar applied to every meter (a member's metabolism, a game-mode harshness dial) or a per-meter record (cold biome → warmth only). `1` / omitted leaves the base rates unscaled.
1267
+ - `DefendMissionOptions` (interface): interface DefendMissionOptions extends ComposedMissionOptions — Options for {@link defendMission}: hold a position, scored by what is left standing.
1181
1268
  - `DeliveryEntry` (interface): interface DeliveryEntry { id: string; userId: string; inventoryId: string; items: readonly ItemStack[]; deliverAt: number } — ⚠ undocumented
1182
1269
  - `DeliveryQueue` (interface): interface DeliveryQueue { schedule(entry: ScheduledDelivery & {id?: string}): DeliveryEntry; due(now: number): readonly DeliveryEntry[]; claimDue(now: number): readonly DeliveryEntry[]; pending(userId?: string): readonly DeliveryEntry[]; cancel(id: string): boolea… — ⚠ undocumented
1183
1270
  - `DialogueGraph` (interface): interface DialogueGraph — A serializable branching conversation: a start node id and the nodes it can reach.
@@ -1194,9 +1281,10 @@
1194
1281
  - `EffectRef` (interface): interface EffectRef — A reference to an effect the game knows how to apply — id plus JSON-safe params, no closures.
1195
1282
  - `EnqueueOptions` (interface): interface EnqueueOptions — Optional per-enqueue overrides.
1196
1283
  - `EnqueueResult` (type): type EnqueueResult<TSpec, TReserve = undefined> = | { readonly ok: true; readonly state: WorkQueueState<TSpec, TReserve>; readonly job: Job<TSpec, TReserve> } | { readonly ok: false; readonly state: WorkQueueState<TSpec, TReserve>; readonly reason: string } — Outcome of {@link enqueue}. On rejection the state is returned unchanged.
1197
- - `EntityDiedEvent` (interface): interface EntityDiedEvent { instanceId: string; catalogId: string; userId?: string; displayName?: string; reason: DeathReason; position: [number, number, number]; serverId?: string } — ⚠ undocumented
1284
+ - `EntityDiedEvent` (interface): interface EntityDiedEvent { instanceId: string; catalogId: string; userId?: string; displayName?: string; reason: DeathReason; source?: string; position: [number, number, number]; serverId?: string } — ⚠ undocumented
1198
1285
  - `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
1199
1286
  - `EntitySpriteConfig` (interface): interface EntitySpriteConfig { url: string; width: number; height: number; y: number; clip?: {atlas: SpriteAtlas; animation: string} } — ⚠ undocumented
1287
+ - `EscortMissionOptions` (interface): interface EscortMissionOptions extends ComposedMissionOptions — Options for {@link escortMission}: a protected actor that must reach a destination alive.
1200
1288
  - `EventTicker` (interface): interface EventTicker — A live, observable event/kill-feed ticker.
1201
1289
  - `EventTickerEntry` (interface): interface EventTickerEntry extends TimedFeedEntry — A stored ticker entry — an {@link EventTickerInput} stamped with an id and clock time.
1202
1290
  - `EventTickerInput` (interface): interface EventTickerInput — One event pushed onto the ticker. `kind` and `icon` are free strings the game owns and the model never interprets — they only ride through to the renderer so a game can color, icon, and group each entry however it likes ("kill", "assist", "info", …).
@@ -1212,19 +1300,30 @@
1212
1300
  - `FeedEntry` (interface): interface FeedEntry<T = unknown> { at: number; data: T } — ⚠ undocumented · used by `ToastStack` (@jgengine/react): Render `ctx.game.feed`'s entries for `action` as a newest-first toast stack — the feed-backed sibling of `@jgengine/core/game/toasts`' `crea…
1213
1301
  - `FeedWindow` (interface): interface FeedWindow — Bounds for {@link appendFeed} / {@link pruneFeed}: newest-`limit` cap and/or `ttl` age window.
1214
1302
  - `FiringBlock` (type): type FiringBlock = "predicate" | "no-target" | "cooldown" | "rate-limit" | "no-charges" | "stack-ignored" — Reason a firing did not produce an effect — surfaced for debug inspection, never thrown.
1215
- - `FirstPersonCameraConfig` (interface): interface FirstPersonCameraConfig { eyeHeight?: number; sensitivity?: number; maxPitch?: number; reticle?: boolean; viewmodel?: boolean } — ⚠ undocumented
1303
+ - `FirstPersonCameraConfig` (interface): interface FirstPersonCameraConfig { eyeHeight?: number; sensitivity?: number; maxPitch?: number; reticle?: boolean; viewmodel?: boolean; collision?: false | FirstPersonCollisionConfig } — ⚠ undocumented
1304
+ - `FirstPersonCollisionConfig` (interface): interface FirstPersonCollisionConfig — Clearance tuning for the first-person camera's collision pull-in.
1216
1305
  - `ForbidRule` (interface): interface ForbidRule — A rule that forbids a combination — e.g. "an incendiary barrel cannot pair with a cryo core". A forbid rule can never become satisfiable by adding more parts, so it is the check a backtracking generator runs on each candidate.
1217
1306
  - `FriendEntry` (interface): interface FriendEntry { userId: string; online: boolean } — ⚠ undocumented
1218
1307
  - `FriendRequestEntry` (interface): interface FriendRequestEntry { requestId: string; fromUserId: string } — ⚠ undocumented
1219
1308
  - `Friends` (interface): interface Friends { canRequest(fromUserId: string, toUserId: string): {reason: string} | null; request(fromUserId: string, toUserId: string): {requestId: string} | {reason: string}; accept(userId: string, requestId: string): {reason: string} | null; decline(… — ⚠ undocumented
1309
+ - `GAME_SAVE_VERSION` (const): const GAME_SAVE_VERSION: 1 — Current save-format version of {@link GameSaveDocument}. Bump it with {@link GameSaveConfig.migrate} when the shape changes.
1220
1310
  - `GameCameraConfig` (interface): interface GameCameraConfig — Camera tuning for the shell's rig stack: pick the rig via `rig`, then tune it through its matching config block. All fields optional — the default is the third-person orbit rig.
1221
1311
  - `GameDefinition` (interface): interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> — Fully-resolved game description produced by {@link defineGameDefinition} — assets, scene, and opted-in subsystems.
1222
1312
  - `GameDefinitionConfig` (type): type GameDefinitionConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> = Omit<GameDefinition<TAssetRef, TMultiplayer>, "scene" | "assets" | "lifecycle"> & { assets?: AssetCatalog<TAssetRef>; /** * The game's run-phase story — a {@link LifecycleConfig} or the `"always-live… — Input to {@link defineGameDefinition} — a `GameDefinition` with `scene` derived and `assets` optional.
1223
- - `GameEventMap` (interface): interface GameEventMap { "entity.died": EntityDiedEvent; "entity.floatText": EntityFloatTextEvent; "combat.telegraph": CombatTelegraphEvent; "combat.vfx": CombatVfxEvent; "combat.vfxInstance": CombatVfxInstanceEvent; "combat.telegraphCancelled": CombatTelegraphCa… — ⚠ undocumented · used by `createGameEvents`: A typed publish/subscribe bus for gameplay events that systems and HUDs subscribe to.
1313
+ - `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.
1224
1314
  - `GameEvents` (interface): interface GameEvents<TMap extends GameEventMap = GameEventMap> { on<TName extends keyof TMap>(name: TName, handler: GameEventHandler<TMap[TName]>): () => void; subscribe<TName extends keyof TMap>(name: TName, handler: GameEventHandler<TMap[TName]>): () => void; emit<TName extends keyof TMap>(name: TNam… — ⚠ undocumented · used by `createGameEvents`: A typed publish/subscribe bus for gameplay events that systems and HUDs subscribe to.
1225
1315
  - `GameLifecycle` (type): type GameLifecycle<TState = unknown> = LifecycleConfig<TState> | "always-live" — How a game declares its run-phase story to {@link defineGameDefinition}. Every game states this explicitly so the shell never has to guess — a silent game used to default to `"playing"`, which painted the touch dock over title/menu/results screens (#1337, Harbor Heat #1329).
1226
1316
  - `GameLoop` (interface): interface GameLoop<TContext = unknown> — Lifecycle hooks a game implements to drive init, per-tick simulation, and player join/leave.
1227
1317
  - `GamePhase` (type): type GamePhase = "menu" | "playing" | "paused" | "ended" — Canonical run phase every game moves through. `menu` (title/main menu), `playing` (live), `paused` (mid-run pause), `ended` (win/lose/results). Touch controls are shown only while `playing`; menus and results never paint the touch dock over themselves.
1318
+ - `GameSave` (interface): interface GameSave — A pluggable-backend game save: one versioned document holding a section per system, with named slots, caller-policy autosave, migration and a state digest. `capture()` builds the document from the live systems; `save()`/`touch()` write it (directly or on the autosave debounce); `load()` reads a slot and applies every section back through its system's `restore`. The shared RNG's cursor rides along, so the seed survives the save and a reloaded run continues the same sequence.
1319
+ - `GameSaveConfig` (interface): interface GameSaveConfig — How a {@link createGameSave} is wired. Storage is the narrow injected {@link SaveBackend}, never `localStorage` welded in.
1320
+ - `GameSaveDocument` (interface): interface GameSaveDocument — The one save document: a format `version`, the game and clock metadata a save-select menu shows, the world's shared RNG cursor, and one section per registered system. There is deliberately no second per-system format — every system's state lives under `systems[<name>]`.
1321
+ - `GameSaveRng` (interface): interface GameSaveRng — The world's shared injected RNG: its `seed` and the live `stream` whose cursor is persisted.
1322
+ - `GameSaveRngState` (interface): interface GameSaveRngState — The persisted cursor of the world's shared injected RNG, plus the seed that produced it.
1323
+ - `GameSaveSystem` (interface): interface GameSaveSystem<T = unknown> — One stateful system inside a game save: a stable `name` (its section key in the document) and the `snapshot()`/`restore()` pair every Lane B module exposes. The name is the contract — a document written by one build restores into the next build's system with the same name. `reset` runs when a loaded slot has no section for this system (a fresh slot), so switching to an empty slot starts a new game instead of leaving the previous one's state behind; a system without it is left as-is.
1324
+ - `GearScore` (interface): interface GearScore — A scored loadout: the total, each slot's share, each stat's signed contribution, and how complete it is.
1325
+ - `GearScoreOptions` (interface): interface GearScoreOptions extends Pick<LootComparisonOptions, "weights" | "lowerIsBetter"> — Caller policy for a loadout score: stat weights, which stats read better small, and what level, rarity and an empty slot are worth.
1326
+ - `GearSlotItem` (interface): interface GearSlotItem — The least a worn item must expose to be scored: its stat block, plus optional level and rarity for the gear-score terms.
1228
1327
  - `GenChoiceRecord` (interface): interface GenChoiceRecord — Provenance for one resolved step: which option stuck, its effective weight, how many options were eligible after constraints, and how many eligible options backtracking rejected before this one. A skipped optional step is recorded with an empty `optionId`.
1229
1328
  - `GenChoices` (interface): interface GenChoices — Read-only view of the choices resolved so far, threaded into a later step's pool, constraint, weight function, or transform so a decision can depend on earlier picks (dependent choice).
1230
1329
  - `GenDraft` (interface): interface GenDraft — The resolved-but-unvalidated draft passed to each {@link GenValidator}: the step choices, their values, and the transformed fields. Returning false from any validator rerolls the whole generation up to the attempt budget.
@@ -1245,7 +1344,7 @@
1245
1344
  - `InstalledPart` (interface): interface InstalledPart { slotId: string; part: PartDef } — ⚠ undocumented · used by `identityOf`: Assemble an {@link ItemIdentity} from a family, tags, and installed parts.
1246
1345
  - `InventoryDeclaration` (interface): interface InventoryDeclaration — Shape of one named inventory a game declares — slot count, accepted item types, HUD binding.
1247
1346
  - `InventorySlot` (type): type InventorySlot = { itemId: string; count: number } | null — ⚠ undocumented
1248
- - `InventoryState` (interface): interface InventoryState { slots: InventorySlot[] } — ⚠ undocumented · used by `countItem` (@jgengine/core/inventory/inventoryModel): Total quantity of `itemId` summed across every stack in the inventory.
1347
+ - `InventoryState` (interface): interface InventoryState { slots: InventorySlot[] } — ⚠ undocumented · used by `carriedEntries` (@jgengine/core/inventory/carryLoad): Every filled stack in the given inventories as load entries, in slot order.
1249
1348
  - `ItemIdentity` (interface): interface ItemIdentity — The declarative identity of a built modular item: a caller-named family, its provenance tags, and the parts occupying its slots. This is the "what an item is" layer above the raw stat rollup in ./modularItem — a plain, serializable value with no game noun baked in ("gun", "manufacturer", "potion" are all caller data in `family`/`tags`).
1250
1349
  - `ItemProvenance` (interface): interface ItemProvenance — The serializable record of how an item was generated: its family, tags, per-slot part selection, applied set-bonus ids, and the deterministic seed. Enough for UI provenance display and byte-exact regeneration.
1251
1350
  - `ItemUseHandler` (interface): interface ItemUseHandler<TState> { can?(state: TState, input: ItemUseInput): ItemUseRejection | null; apply(state: TState, input: ItemUseInput): ItemUseResult<TState> } — ⚠ undocumented
@@ -1263,8 +1362,16 @@
1263
1362
  - `LayerSelection` (type): type LayerSelection = readonly string[] — A serializable, ordered reference to layers by stable id — what a save file or session setup stores.
1264
1363
  - `LeaderboardRow` (interface): interface LeaderboardRow { stat: string; scope: LeaderboardScope; serverId?: string; userId: string; value: number } — ⚠ undocumented · used by `createLeaderboard`: Ranked score tracking across global, server, and per-profile scopes, with top-N queries and per-profile lookups.
1265
1364
  - `LeaderboardScope` (type): type LeaderboardScope = "global" | "server" | "profile" — ⚠ undocumented
1365
+ - `LedgerAdvanceResult` (interface): interface LedgerAdvanceResult — What one {@link TransactionLedger.chargeDue} settled, and what it could not.
1366
+ - `LedgerEntry` (interface): interface LedgerEntry — Money in this module is an integer count of **minor units** — cents when the currency has two decimals, whole coins when it has none. Every posted amount and every balance is a safe integer, so a long simulation never accumulates float drift. `currency` is a free string the game owns.
1367
+ - `LedgerEntryFilter` (interface): interface LedgerEntryFilter — A filter over the journal.
1266
1368
  - `LedgerEvent` (interface): interface LedgerEvent — Lifecycle signal emitted by {@link advanceLedger}.
1267
1369
  - `LedgerEventKind` (type): type LedgerEventKind = "started" | "fired" | "skipped" | "depleted" | "ended" — The kinds of lifecycle signal {@link advanceLedger} can emit for a rule.
1370
+ - `LedgerPostInput` (interface): interface LedgerPostInput — A post to attempt.
1371
+ - `LedgerPostResult` (type): type LedgerPostResult = | { ok: true; entry: LedgerEntry } | { ok: false; reason: "unknown-account" | "invalid-amount" | "invalid-reason" | "insufficient-funds" } — Outcome of {@link TransactionLedger.post}. A rejection leaves every balance untouched.
1372
+ - `LedgerState` (interface): interface LedgerState — Fully serializable ledger state: clock, balances, journal, totals, charges and cursors.
1373
+ - `LedgerStats` (interface): interface LedgerStats — Counters of a ledger, for a probe or a summary line.
1374
+ - `LedgerTotals` (interface): interface LedgerTotals — What one reason code did to the books, kept across entry trimming.
1268
1375
  - `LevelProgress` (interface): interface LevelProgress — Result of resolving a level+xp pair: the settled `level`, leftover `xp` into the current level, the `xpMax` threshold for the next level, and how many `levelsGained` this resolution produced.
1269
1376
  - `LevelSequence` (interface): interface LevelSequence<TLevelConfig> { current(): CurrentLevel<TLevelConfig> | null; status(): LevelSequenceStatus; start(): void; clear(stars?: LevelStars): void; fail(): "retry" | "failed"; retry(): boolean; advance(): boolean; select(level: string): boolean; isUnlocked(leve… — ⚠ undocumented · used by `createLevelSequence`: A pure, deterministic level campaign: an ordered list of levels, each with its own opaque config, played through a `start` → (`clear` → `adv…
1270
1377
  - `LevelingConfig` (interface): interface LevelingConfig — Configuration for a {@link leveling} track: the `xpForLevel` {@link Curve}, the `maxLevel` cap, and optional `startLevel`, stat ids (`xpStat`/`levelStat`, default `"xp"`/`"level"`), and `thresholdMode` — `"perLevel"` (each level costs its own curve value) or `"cumulative"` (curve gives the total xp to reach a level).
@@ -1273,23 +1380,90 @@
1273
1380
  - `LightingConfig` (interface): interface LightingConfig — Declarative lighting replacing the shell's hardcoded ambient/directional default (#207.5); mounts regardless of world kind, only when supplied.
1274
1381
  - `Listing` (interface): interface Listing — One active post in a {@link ListingBook}: an item stack a seller offered at a fixed price until it expires.
1275
1382
  - `LoadoutDef` (interface): interface LoadoutDef { inventories?: Record<string, LoadoutItemEntry[]>; stats?: Record<string, {current: number; max?: number; min?: number}>; economy?: Record<string, number>; unlocks?: string[] } — ⚠ undocumented
1383
+ - `LoanAdvanceResult` (interface): interface LoanAdvanceResult — What one {@link LoanBook.advance} settled, and what it could not.
1384
+ - `LoanBook` (interface): interface LoanBook — The loans a business owes: schedules, interest, arrears and default, posted to a ledger.
1385
+ - `LoanBookOptions` (interface): interface LoanBookOptions — Options for {@link createLoanBook}.
1386
+ - `LoanBookState` (interface): interface LoanBookState — Serializable loan-book state.
1387
+ - `LoanBookStats` (interface): interface LoanBookStats — Counters of a loan book, for a probe or a summary line.
1388
+ - `LoanPayment` (interface): interface LoanPayment — One scheduled repayment, in integer minor units.
1389
+ - `LoanRecord` (interface): interface LoanRecord — One live loan: its plan plus how far it has been paid.
1390
+ - `LoanRepayment` (interface): interface LoanRepayment — Outcome of an early repayment.
1391
+ - `LoanSchedule` (interface): interface LoanSchedule — A full repayment plan: every payment, its interest/principal split and the running balance.
1392
+ - `LoanState` (interface): interface LoanState — Saved progress of one loan; the schedule is rebuilt from its terms on restore.
1393
+ - `LoanTerms` (interface): interface LoanTerms — Money in this module is an integer count of **minor units** — cents when the currency has two decimals, whole coins when it has none. Principal, interest, payments and balances are safe integers, so a schedule adds up exactly.
1276
1394
  - `LockOnCameraConfig` (interface): interface LockOnCameraConfig — Lock-on / strafe rig (#26) — yaw bound to player→target, move axis becomes strafe.
1395
+ - `LootBaseDef` (interface): interface LootBaseDef extends ItemBaseDef — A base family (frame, housing, chassis — caller nouns) with its slots, stats and affix pools.
1396
+ - `LootCatalog` (interface): interface LootCatalog — Everything a roll reads: bases, parts, affixes, rarity odds, makers and the level curve.
1397
+ - `LootComparison` (interface): interface LootComparison — How two items compare: every stat delta plus one weighted score for each.
1398
+ - `LootComparisonOptions` (interface): interface LootComparisonOptions — Caller policy for the comparison score.
1277
1399
  - `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.
1278
1400
  - `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.
1401
+ - `LootInstalledPart` (interface): interface LootInstalledPart — One part occupying one slot of a rolled item.
1402
+ - `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-finite-stat" | "empty-… — A defect found by {@link validateLootItem}.
1403
+ - `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.
1404
+ - `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.
1279
1405
  - `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.
1406
+ - `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.
1280
1407
  - `LootPipeline` (interface): interface LootPipeline<TCtx = unknown> — A resolved, reusable loot pipeline. Call {@link LootPipeline.resolve} with a per-drop context and RNG.
1281
1408
  - `LootPipelineDef` (interface): interface LootPipelineDef<TCtx = unknown> — A named, ordered loot-resolution pipeline: the stages to run plus result-shaping policy (duplicate stacking, a total drop cap). Serializable except for its stage gate/modifier functions, mirroring how {@link LootTableDef} entries may carry a `generate` function.
1282
1409
  - `LootPipelineDeps` (interface): interface LootPipelineDeps — Dependencies a pipeline needs at build time — chiefly how to turn a table id into its definition.
1283
1410
  - `LootPlanEntry` (interface): interface LootPlanEntry — One eligible entry inside a {@link LootRollPlan}: the original table entry plus its live eligibility and effective weight/chance. Modifiers rewrite these fields; the source `entry` object is never mutated, so table definitions stay shared and reusable.
1284
1411
  - `LootResolution` (interface): interface LootResolution — The fully serializable outcome of a resolution: the drops, their provenance, per-stage traces, and the replay seed.
1285
1412
  - `LootResolveContext` (interface): interface LootResolveContext<TCtx = unknown> — The caller-owned context, injected RNG, and optional seed threaded through a single resolution.
1413
+ - `LootRollOptions` (interface): interface LootRollOptions — Pin any part of a roll; omitted fields are drawn from the catalog.
1286
1414
  - `LootRollPlan` (interface): interface LootRollPlan — The mutable roll plan a stage derives from its table before rolling. Modifiers transform this — gating entries, reweighting them, or changing the roll count — instead of touching table defs.
1415
+ - `LootRoller` (interface): interface LootRoller — A roll counter over one catalog: every roll goes through {@link rollLoot}, and the handle keeps counters (a distribution readout in the debug overlay) that snapshot and restore as plain data.
1416
+ - `LootRollerOptions` (interface): interface LootRollerOptions — Options for {@link createLootRoller}.
1417
+ - `LootRollerState` (interface): interface LootRollerState — Saved counters of a {@link LootRoller}.
1287
1418
  - `LootStage` (interface): interface LootStage<TCtx = unknown> — One ordered step in a {@link LootPipelineDef}: a source pool (a table id or inline table), an optional context gate, a fold mode, and the modifiers that reshape its roll. Stages compose the genre concepts — world pool, dedicated pool, luck, pity — without any of them living in core.
1288
1419
  - `LootStageKind` (type): type LootStageKind = "contribute" | "fallback" | "replace" — How a stage folds its rolled drops into the accumulating result: `"contribute"` appends, `"fallback"` rolls only when nothing has dropped yet, `"replace"` discards prior drops and overrides with its own (quest/boss overrides).
1289
1420
  - `LootStageStatus` (type): type LootStageStatus = "rolled" | "skipped" | "empty" | "replaced" | "fell-through" — The disposition of a stage after resolution — did it roll, get gated, fall through, override, or find nothing.
1290
1421
  - `LootStageTrace` (interface): interface LootStageTrace — Per-stage record of what happened during resolution, for debugging and replay auditing.
1422
+ - `LootStatDelta` (interface): interface LootStatDelta — A per-stat comparison entry; `delta` is `b - a`, so positive means the second item is larger.
1291
1423
  - `LootTableDef` (interface): interface LootTableDef — A named, validated loot table — its roll count, weighted-vs-independent mode, and candidate entries.
1292
- - `ModelConfig` (interface): interface ModelConfig { url: string; scale?: number; targetHeight?: number; y?: number; anchor?: "center" | "origin"; dims?: ModelDims; collisionMesh?: CollisionMeshData; material?: ModelMaterialOverride; surfaceDetail?: boolean; materialAssets?: readonly Materi… — ⚠ undocumented · used by `PartMotionRig` (@jgengine/shell/render/PartMotion): Procedural motion rig for a rig-less part-composed character (`ModelPart.role` — see `@jgengine/core/game/partAnimation`).
1424
+ - `LootValidationIssue` (interface): interface LootValidationIssue — One reason a rolled item is not legal against its catalog, with the offending value named.
1425
+ - `LootWeaponProjection` (interface): interface LootWeaponProjection — A base weapon definition plus the stat map a rolled item overrides.
1426
+ - `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.
1427
+ - `MAX_LOOT_AFFIXES` (const): const MAX_LOOT_AFFIXES: 8 — Cap on affixes one item may roll, so a bad catalog cannot make a drop cost unbounded work.
1428
+ - `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.
1429
+ - `MISSION_TRIGGER_ACTIONS` (const): const MISSION_TRIGGER_ACTIONS: readonly TriggerActionDefinition[] — The mission actions a game registers so the editor inspector can assign them to a marker or volume. Params land flat on the object's `meta` beside `on`/`action`, like any authored trigger.
1430
+ - `Market` (interface): interface Market — A live market: prices from supply and demand over finite stock, with serializable state.
1431
+ - `MarketConfig` (interface): interface MarketConfig — Options for {@link createMarket}: the goods and the price/demand policy they share.
1432
+ - `MarketGoodDef` (interface): interface MarketGoodDef — Money in this module is an integer count of **minor units** — cents when the currency has two decimals, whole coins when it has none. Every price, cost and total is a safe integer, so a simulation never accumulates float drift; the caller converts at the display boundary.
1433
+ - `MarketGoodState` (interface): interface MarketGoodState — The mutable half of a good: what a save stores, with the definition kept as authored data.
1434
+ - `MarketPriceInput` (interface): interface MarketPriceInput — Everything a price curve reads: the good's resolved policy plus its live stock and demand.
1435
+ - `MarketQuote` (interface): interface MarketQuote — A non-mutating read of what a sale would cost and how it would move the price.
1436
+ - `MarketSale` (interface): interface MarketSale — A served sale: what the buyer paid per unit and in total, in minor units.
1437
+ - `MarketSaleRejection` (interface): interface MarketSaleRejection — Why a sale could not be served.
1438
+ - `MarketSaleResult` (type): type MarketSaleResult = MarketSale | MarketSaleRejection — A sale outcome: the price the buyers paid, or why it could not be served.
1439
+ - `MarketSnapshot` (interface): interface MarketSnapshot — Serializable market state: the mutable half of every good plus the running counters.
1440
+ - `MarketStats` (interface): interface MarketStats — Counters and stocks of a market, for a probe or a summary line.
1441
+ - `MissionActorFact` (interface): interface MissionActorFact — A live actor the machine tracks: whether it is alive and, when needed, where it is.
1442
+ - `MissionBranch` (interface): interface MissionBranch — One outgoing edge of an objective: the first branch whose `when` matches wins over `next`.
1443
+ - `MissionDef` (interface): interface MissionDef — A whole mission: the objective graph plus its fail states and rewards.
1444
+ - `MissionEvent` (type): type MissionEvent = | { kind: "started"; missionId: string; mission: MissionDef } | { kind: "objective"; missionId: string; objectiveId: string; label: string } | { kind: "branch"; missionId: string; from: string; to: string; label?: string } | { kind: "timeout"; missionId: string; objectiveId: stri… — Emitted as the machine advances.
1445
+ - `MissionEventFilter` (interface): interface MissionEventFilter — Which world events an objective counts or a fail condition reacts to. Absent fields do not filter.
1446
+ - `MissionFactCondition` (interface): interface MissionFactCondition — A readable fact test a branch or a `fact` fail condition reads. All comparators are optional.
1447
+ - `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.
1448
+ - `MissionFailDef` (interface): interface MissionFailDef — A named fail state an objective, timer or branch can land in.
1449
+ - `MissionIssue` (interface): interface MissionIssue — A located mission-catalog problem; `path` is a JSON Pointer into the supplied catalog.
1450
+ - `MissionObjectiveDef` (interface): interface MissionObjectiveDef — One objective. Every field is content; the machine reads only these.
1451
+ - `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`).
1452
+ - `MissionRewardAppliers` (interface): interface MissionRewardAppliers — Appliers a mission reward routes through — the same shape quests use, plus a loot-table hook.
1453
+ - `MissionRewards` (interface): interface MissionRewards extends QuestRewards — What a mission pays. Economy/XP/items/unlocks flow through the quest appliers; loot names tables.
1454
+ - `MissionRuntime` (interface): interface MissionRuntime — The mission state machine over one active mission.
1455
+ - `MissionRuntimeConfig` (interface): interface MissionRuntimeConfig — Everything a mission runtime needs.
1456
+ - `MissionRuntimeSnapshot` (interface): interface MissionRuntimeSnapshot — Serializable runtime state for a save.
1457
+ - `MissionState` (interface): interface MissionState — Serializable mission state — one active mission's position in its graph.
1458
+ - `MissionStats` (interface): interface MissionStats — Counters a devtools probe or a test reads.
1459
+ - `MissionStatus` (type): type MissionStatus = "active" | "completed" | "failed" — Where a mission is in its life.
1460
+ - `MissionTickInput` (interface): interface MissionTickInput — Everything the machine reads for one tick. All optional; a mission only touches what it declares.
1461
+ - `MissionTriggerBridge` (interface): interface MissionTriggerBridge — Watches a document's authored mission triggers against moving actors.
1462
+ - `MissionValidationOptions` (interface): interface MissionValidationOptions — Declarations a mission catalog is validated against.
1463
+ - `MissionView` (interface): interface MissionView — Pooled per-frame readout a HUD tracker renders.
1464
+ - `MissionWorldEvent` (interface): interface MissionWorldEvent — One thing that happened in the world this tick, reported by the game. A constrained objective counts matching events and an `event` fail condition reacts to them, so the machine judges the world instead of the game polling its own counters.
1465
+ - `MissionWorldEventKind` (type): type MissionWorldEventKind = "kill" | "collect" | "deliver" — What a reported world event is.
1466
+ - `ModelConfig` (interface): interface ModelConfig { url: string; scale?: number; targetHeight?: number; y?: number; anchor?: "center" | "origin"; dims?: ModelDims; collisionMesh?: CollisionMeshData; material?: ModelMaterialOverride; surfaceDetail?: boolean; wind?: ModelWindConfig | false; … — ⚠ undocumented · used by `PartMotionRig` (@jgengine/shell/render/PartMotion): Procedural motion rig for a rig-less part-composed character (`ModelPart.role` — see `@jgengine/core/game/partAnimation`).
1293
1467
  - `ModelMaterialMaps` (interface): interface ModelMaterialMaps extends Partial<Record<MaterialTextureRole, string>> — Real PBR map URLs (e.g. `buildMaterialCatalog(...).resolve(id)!.maps` from `@jgengine/assets`) layered onto a model's material — the seam for texturing an otherwise-flat/untextured GLB. Any supported MaterialTextureRole may be omitted to keep the model's own map. Physical roles use the renderer's physical material; map URLs and response parameters remain caller-owned.
1294
1468
  - `ModelMaterialOverride` (interface): interface ModelMaterialOverride extends MaterialSurfaceParameters — Per-entity PBR material override (#151.3) applied to every standard or physical material in the model's cloned scene graph.
1295
1469
  - `ModularItemDef` (interface): interface ModularItemDef { id: string; baseStats: Record<string, number>; slots: readonly MountSlotDef[] } — ⚠ undocumented
@@ -1359,6 +1533,8 @@
1359
1533
  - `QuestDef` (interface): interface QuestDef { id: string; title: string; description?: string; giver?: string; turnIn?: string; requires?: string[]; objectives: QuestObjective[]; rewards?: QuestRewards } — ⚠ undocumented · used by `describeTrackedQuest`: Join a quest's static {@link QuestDef} with a player's live {@link QuestInstance} into a flat, renderer-free view a HUD tracker draws (title…
1360
1534
  - `QuestInstance` (interface): interface QuestInstance { questId: string; status: QuestStatus; objectives: QuestObjectiveProgress[] } — ⚠ undocumented · used by `describeTrackedQuest`: Join a quest's static {@link QuestDef} with a player's live {@link QuestInstance} into a flat, renderer-free view a HUD tracker draws (title…
1361
1535
  - `QuestRewards` (interface): interface QuestRewards { xp?: {amount: number}; economy?: Record<string, number>; items?: {item: string; count: number; inventory: string}[]; unlocks?: string[]; quests?: string[] } — ⚠ undocumented · used by `applyQuestRewards` (@jgengine/core/game/quest): Apply items before XP, currency, and unlocks.
1536
+ - `QueueEntry` (interface): interface QueueEntry — One guest waiting their turn, and when they joined the line.
1537
+ - `QueueWait` (interface): interface QueueWait — A wait estimate read off the queue's own measured throughput.
1362
1538
  - `RaceState` (class): class RaceState — Race state machine (issue #87). Drive it each tick with `update(now, positions)` — `now` is game time (`ctx.time`), `positions` maps each racer to a world point tested against the ordered checkpoint volumes. It emits `checkpoint.hit` / `lap.completed` / `position.changed` / `race.finished`, keeps cumulative split times for PB deltas, resolves a pluggable win condition (first-past-post, round-cut, derby last-standing), and `resetToCheckpoint` hands back a respawn pose at the racer's last checkpoint. `removeRacer` drops a racer mid-race and `reset` returns the whole instance to its pre-race state for reuse.
1363
1539
  - `RankLeaderboardOptions` (interface): interface RankLeaderboardOptions — Options for {@link rankLeaderboard}. All optional.
1364
1540
  - `RankableRow` (interface): interface RankableRow — A single input score to rank. Accepts the raw shape produced by {@link LeaderboardRow} (a `LeaderboardRow` is assignable to this) as well as a minimal `{ userId, value }` pair with an optional display `label` the game owns.
@@ -1375,12 +1551,33 @@
1375
1551
  - `RebindSessionSnapshot` (interface): interface RebindSessionSnapshot — Serializable session state for save/restore.
1376
1552
  - `RecipeDef` (interface): interface RecipeDef { id: string; inputs: readonly RecipeItem[]; outputs: readonly RecipeItem[]; seconds?: number; station?: string; stationRange?: number; requires?: readonly string[]; category?: string } — ⚠ undocumented · used by `validateRecipeCatalog` (@jgengine/core/crafting/recipeCatalog): Validate native recipes before registration: ids, counts, timing and caller-catalog references.
1377
1553
  - `RecipeItem` (interface): interface RecipeItem { itemId: string; count: number } — ⚠ undocumented
1554
+ - `RecurringCharge` (interface): interface RecurringCharge — A recurring charge — upkeep, rent, a subscription — settled by {@link TransactionLedger.chargeDue}.
1378
1555
  - `RequireRule` (interface): interface RequireRule — A rule that requires a second condition to hold whenever the first matches — e.g. "a scoped rifle requires a stock". Checked at completeness time, not during incremental placement, because the `then` side may be satisfied by a part chosen later in a generation pass.
1379
1556
  - `ResourceCost` (type): type ResourceCost = Readonly<Record<string, number>> — Resource costs keyed by currency/resource id.
1380
1557
  - `ResourceLedger` (interface): interface ResourceLedger — Fully serializable scheduled-transaction state: clock, balances, rule definitions, and cursors.
1381
1558
  - `ResourcePolicy` (type): type ResourcePolicy = ( txn: ResourceTransaction, ctx: PolicyContext, ) => readonly ResourceTransaction[] — A composable transform over one transaction. Return the transactions to keep: `[]` rejects it, `[txn]` passes/transforms it, and multiple entries split it.
1382
1559
  - `ResourceTransaction` (interface): interface ResourceTransaction — One resource movement produced by a due cycle, before it is applied to balances.
1383
1560
  - `RetainedVfxKind` (type): type RetainedVfxKind = string — The archetype of a retained (long-lived, updatable) VFX effect — an open string, not a closed union, so a renderer registers new kinds (beam, tether, zone, target line, looping emitter) without a central branch. `"beam"` is the first shipped retained renderer.
1561
+ - `RideBreakdown` (interface): interface RideBreakdown — Breakdown curve and money for one ride. All data, so a park tunes its own economy.
1562
+ - `RideChoicePolicy` (interface): interface RideChoicePolicy — Policy a park might vary when it turns taste and ride data into utility options.
1563
+ - `RideChooser` (interface): interface RideChooser — One guest's ride choice over one park: taste becomes utility options, each ride is costed by its own line's wait, and the pick comes from the shared utility brain — so the same brain that picks food picks rides, and a guest's queue tolerance is a weight rather than a rule.
1564
+ - `RideChooserConfig` (interface): interface RideChooserConfig — Everything a ride chooser needs: the park, the guest's taste, and the guest's needs.
1565
+ - `RideChooserSnapshot` (interface): interface RideChooserSnapshot — Serializable chooser state: the guest's taste and the brain's held pick.
1566
+ - `RideDefinition` (interface): interface RideDefinition — Everything a ride is: its ratings, its cycle and its wear. Content, not code.
1567
+ - `RidePhase` (type): type RidePhase = "idle" | "loading" | "riding" | "unloading" | "broken" — Where a ride is in its cycle.
1568
+ - `RideQueueView` (interface): interface RideQueueView — A ride's line as a game or the devtools overlay reads it.
1569
+ - `RideRatingRange` (interface): interface RideRatingRange — The rating scale a park's taste curves are normalized against.
1570
+ - `RideRatings` (interface): interface RideRatings — What a ride is worth to a guest. All three come from the ride's own data.
1571
+ - `RideRetune` (interface): interface RideRetune — In-play dials a ride accepts from {@link RideSystem.retune}.
1572
+ - `RideServiceResult` (interface): interface RideServiceResult — What a service cost the caller.
1573
+ - `RideStats` (interface): interface RideStats — Counters a devtools probe or test reads.
1574
+ - `RideSystem` (interface): interface RideSystem — The park's rides: each runs its own load → ride → unload cycle at its own capacity, takes guests off its own line, wears out against its own breakdown curve, and reports its own repairs and services. The clock is internal and only `tick` moves it, so a replay, a save and a test read the same cycle.
1575
+ - `RideSystemConfig` (interface): interface RideSystemConfig — Everything a ride system needs.
1576
+ - `RideSystemSnapshot` (interface): interface RideSystemSnapshot — Serializable park state: the clock, each ride's cycle position, wear and line.
1577
+ - `RideTaste` (interface): interface RideTaste — What one guest wants from a ride: the intensity and nausea they are comfortable with, and how long they will stand in line. All three are the guest's own data.
1578
+ - `RideTickSink` (interface): interface RideTickSink — Where a ride's boardings, alightings and breakdowns are announced. Every hook is optional.
1579
+ - `RideView` (interface): interface RideView — One ride's live state.
1580
+ - `RideWaitSource` (interface): interface RideWaitSource — The live queue a ride-choice candidate is costed against. {@link RideSystem} satisfies this.
1384
1581
  - `Ring` (interface): interface Ring — ⚠ undocumented
1385
1582
  - `RingConfig` (interface): interface RingConfig { center: RingPoint; phases: readonly RingPhase[] } — ⚠ undocumented
1386
1583
  - `RingPhase` (interface): interface RingPhase { startTime: number; shrinkDuration: number; fromRadius: number; toRadius: number; damagePerSecond: number; center?: RingPoint } — ⚠ undocumented
@@ -1405,6 +1602,7 @@
1405
1602
  - `RunDraft` (interface): interface RunDraft<TStat extends string = string, TData = unknown> { present(count: number, rng?: () => number): RunModifierOffer<TStat, TData>[]; choose(offerId: string): boolean; stack(): RunModifierStack<TStat, TData> } — ⚠ undocumented · used by `createRunDraft`: A roguelike run built from stacking drafted modifier picks that reshape the run.
1406
1603
  - `RunModifierOffer` (interface): interface RunModifierOffer<TStat extends string = string, TData = unknown> { id: string; weight: number; maxStacks?: number; label?: string; stats?: StatModifierSet<TStat>; data?: TData } — ⚠ undocumented
1407
1604
  - `SaleRecord` (interface): interface SaleRecord — One completed sale recorded into a {@link PriceHistory}.
1605
+ - `SaveAutoConfig` (interface): interface SaveAutoConfig — Autosave cadence: debounce writes `debounceMs` after the last edit, but never wait longer than `maxWaitMs` while edits keep coming (`0` removes the ceiling). `autosave: true` uses `{ debounceMs: 1000, maxWaitMs: 15000 }`.
1408
1606
  - `SaveBackend` (interface): interface SaveBackend — The one async storage seam a save store persists through. Every backend satisfies this same three-method shape — the browser's `localStorage` (offline), an in-memory map (tests/SSR), or a database/Convex/HTTP endpoint (cloud) — so a game switches offline saves for cloud saves by swapping the backend and changing nothing else. Keys are opaque namespaced strings; values are already-serialized strings, so a backend never needs to know the save shape.
1409
1607
  - `SaveSlotMeta` (interface): interface SaveSlotMeta — One entry in a {@link SaveSlots} index: a save/profile slot's *display* metadata, not its payload. The real save data lives in a `createSaveStore` slot; this record is only what a save-select menu renders — an id, an optional player-facing `name`, whether the slot is `empty`, when it was last written (`savedAt`), and a free-string `meta` bag the game fills with whatever the menu should show (level, playtime, location, chapter, a thumbnail ref, …). `meta` keys are opaque to this model — it never reads or branches on them.
1410
1608
  - `SaveSlotWrite` (interface): interface SaveSlotWrite — What a {@link SaveSlots.write} stamps onto a slot — a name and/or a fresh `meta` bag.
@@ -1413,6 +1611,7 @@
1413
1611
  - `SaveSlotsSnapshot` (interface): interface SaveSlotsSnapshot — Serializable state of a {@link SaveSlots} index, for save/restore.
1414
1612
  - `SaveStatus` (type): type SaveStatus = "idle" | "loading" | "saving" | "saved" | "error" — Lifecycle of the last save/load — drive a "Saving…"/"Saved" indicator or a loading gate off it. `"error"` includes rejected reads, writes, removals and slot metadata updates; metadata failure can follow a successful payload write.
1415
1613
  - `SaveStore` (interface): interface SaveStore<T> — A pluggable-backend game save with autosave, named slots, and versioned migration. `value()`/`patch()` hold the live state; `load()` hydrates it from the backend; `save()` (or autosave) writes it back. Backend failures surface as `"error"` status and through `onError` — a save never throws into a tick.
1614
+ - `SaveTimers` (interface): interface SaveTimers — The timer seam autosave schedules through — defaults to the ambient `setTimeout`/`clearTimeout`; inject fakes in tests.
1416
1615
  - `ScheduledDelivery` (interface): interface ScheduledDelivery { userId: string; inventoryId: string; items: readonly ItemStack[]; deliverAt: number } — ⚠ undocumented
1417
1616
  - `ScheduledRule` (interface): interface ScheduledRule — A serializable recurring resource flow. Every field is plain JSON so the whole {@link ResourceLedger} round-trips through `structuredClone`/`JSON`. Dynamic, context-aware behaviour (providers, tax curves, affordability caps) lives in the runtime {@link ResourcePolicy} pipeline, never in this data.
1418
1617
  - `SerializedBehaviorState` (type): type SerializedBehaviorState = Record<string, BehaviorState> — A serialized snapshot of every behavior's state on one item, keyed by behavior id. Round-trips through JSON so it can live in a saved game.
@@ -1429,6 +1628,16 @@
1429
1628
  - `ShopStockSnapshot` (interface): interface ShopStockSnapshot — Serializable state of a shop's stock, for save/restore.
1430
1629
  - `ShoulderCameraConfig` (interface): interface ShoulderCameraConfig — Over-the-shoulder combat rig (#25) — offset, ADS, shoulder swap, decoupled reticle.
1431
1630
  - `SideScrollCameraConfig` (interface): interface SideScrollCameraConfig — Fixed lateral 2.5D follow (side-on platformer cam): the camera sits perpendicular to the travel axis, tracks the followed entity, and never reads player look input.
1631
+ - `SkillActiveDef` (interface): interface SkillActiveDef — An action a node or synergy unlocks, shaped to bind straight into `createAbilityKit`'s slot config.
1632
+ - `SkillNodeDef` (interface): interface SkillNodeDef<TStat extends string = string> — One authored tree node. Every field except `id` is optional data, so a game states its own tree: what a rank costs, what must come first, the passive it folds in and the actions it opens.
1633
+ - `SkillRequirement` (interface): interface SkillRequirement — One node that must be at or above `rank` for a node or synergy to open; `rank` defaults to `1`.
1634
+ - `SkillResolution` (interface): interface SkillResolution<TStat extends string = string> — The whole outcome of a build: merged passives, unlocked actions, live synergies and point accounting.
1635
+ - `SkillSpendReason` (type): type SkillSpendReason = | "unknown-node" | "max-rank" | "no-points" | "requires" | "branch-points" | "excluded" — Why a spend was refused; each reason is reachable from authored data, so a UI can say which rule bit.
1636
+ - `SkillSpendResult` (type): type SkillSpendResult = | { ok: true; spent: number; pointsAvailable: number } | { ok: false; reason: SkillSpendReason; detail: string } — The outcome of one spend or legality check: what it costs and the balance after it, or the rule that stopped it.
1637
+ - `SkillSynergyDef` (interface): interface SkillSynergyDef<TStat extends string = string> — A payoff that exists only when two or more nodes are ranked together. This is what rewards breadth over dumping every point into one branch.
1638
+ - `SkillTree` (interface): interface SkillTree<TStat extends string = string> — A live skill tree over one authored config, with respec, snapshot/restore and an optional devtools probe.
1639
+ - `SkillTreeConfig` (interface): interface SkillTreeConfig<TStat extends string = string> — Everything {@link createSkillTree} reads: nodes, optional synergies, and starting points.
1640
+ - `SkillTreeState` (interface): interface SkillTreeState — Saved tree state: unspent points and the rank of every ranked node.
1432
1641
  - `SlotGrid` (type): type SlotGrid<T> = readonly Slot<T>[] — ⚠ undocumented
1433
1642
  - `Social` (interface): interface Social { friends: Friends; party: Party; presence: {get(userId: string): PresenceInfo}; emotes: Emotes; worldInvites: WorldInvites; snapshot(): SocialSnapshot; hydrate(data: SocialSnapshot): void } — ⚠ undocumented · used by `createSocial`: Emotes and lightweight social interactions between nearby players.
1434
1643
  - `SocialDeps` (interface): interface SocialDeps { events: GameEvents; presence?: (userId: string) => PresenceInfo; now?: () => number; emotes?: EmotesDeps; worldInviteTtlMs?: number } — ⚠ undocumented · used by `createSocial`: Emotes and lightweight social interactions between nearby players.
@@ -1486,6 +1695,8 @@
1486
1695
  - `TrackedObjectiveView` (interface): interface TrackedObjectiveView — One objective as a tracker/HUD reads it — label + progress toward its count.
1487
1696
  - `TrackedQuestView` (interface): interface TrackedQuestView — A quest as a tracker/HUD reads it — title, status, and per-objective progress.
1488
1697
  - `TrainableUnitDef` (interface): interface TrainableUnitDef — A unit the producer can train.
1698
+ - `TransactionLedger` (interface): interface TransactionLedger — The books: balances with a reason-tagged journal, and recurring charges on a cadence.
1699
+ - `TransactionLedgerOptions` (interface): interface TransactionLedgerOptions — Options for {@link createTransactionLedger}.
1489
1700
  - `TransformApi` (interface): interface TransformApi — Mutation surface a {@link GenTransform} uses to derive numeric fields after choices resolve. Every `set`/`add`/`mul` is captured as a {@link GenFieldRecord}, and `rng` is the same injected stream the choices drew from, so rolled derivations stay deterministic and explainable.
1490
1701
  - `TriggeredRule` (interface): interface TriggeredRule — A declarative subscription from an event to an effect. Everything here is serializable content — the runtime reads it, it never embeds behavior. `effect` names an effect the game resolves; core only routes and gates.
1491
1702
  - `TriggeredRuleEngine` (interface): interface TriggeredRuleEngine — A running set of triggered rules with gating, timed lifetimes, stacking, cleanup, and save/load.
@@ -1510,7 +1721,19 @@
1510
1721
  - `VfxInstanceStoreOptions` (interface): interface VfxInstanceStoreOptions — Options for {@link createVfxInstanceStore}.
1511
1722
  - `VfxKind` (type): type VfxKind = "projectile" | "beam" | "nova" | "glow" | "spark" — The visual archetype of a spell/ability effect burst: a traveling bolt, a connecting beam, an expanding ground nova, a soft aura glow, or a scattering impact spark.
1512
1723
  - `VfxRef` (type): type VfxRef = string | readonly [number, number, number] — An endpoint of a retained VFX instance: either an entity instance id (a renderer resolves and follows its live pose each frame) or a fixed `[x, y, z]` world point. Kept serializable so the effect replicates as plain data.
1724
+ - `Visitor` (interface): interface Visitor — One visitor on site: the id its body, its needs and its money are keyed by.
1725
+ - `VisitorFlow` (interface): interface VisitorFlow — What a flow policy reads to decide how many arrive and when one has had enough.
1726
+ - `VisitorPolicy` (interface): interface VisitorPolicy — The game's flow policy: what draws visitors in, how many fit, and when one has had enough.
1727
+ - `VisitorPolicyPatch` (interface): interface VisitorPolicyPatch — Flow policy fields a scenario can change in play.
1728
+ - `VisitorPopulation` (interface): interface VisitorPopulation — The crowd a scene reports. Every visitor is a member with a body the game's world made, so the on-site figure is the member count and the daily figure is the sum of admissions the world accepted — there is no second tally to drift from what is on the ground. Arrivals, capacity and leaving are the game's policy; ids, day rollover and counting are this seam's.
1729
+ - `VisitorPopulationOptions` (interface): interface VisitorPopulationOptions — Options for {@link createVisitorPopulation}.
1730
+ - `VisitorPopulationSnapshot` (interface): interface VisitorPopulationSnapshot — Serializable state. No on-site count is stored: it is the restored visitor list.
1731
+ - `VisitorPopulationStats` (interface): interface VisitorPopulationStats — Counters a HUD, a probe or a test reads. The on-site figure is the visitor list's length.
1732
+ - `VisitorWorld` (interface): interface VisitorWorld — The game's world behind every counted visitor: it creates and removes the body.
1513
1733
  - `WORLD_ITEM_ENTITY_NAME` (const): const WORLD_ITEM_ENTITY_NAME: "world_item" — Scene-entity catalog name every dropped-item instance spawns under (see the three buckets: worldItem is an entity, never an inventory item or object).
1734
+ - `WaitQueue` (interface): interface WaitQueue — A real line: guests join the back, leave the head, and the wait a new arrival faces comes from how fast this queue has actually been boarding — not from a configured constant. A broken ride stops boarding, the line grows, and the estimate grows with it.
1735
+ - `WaitQueueOptions` (interface): interface WaitQueueOptions — Tunables for {@link createWaitQueue}, every one retunable through a restore.
1736
+ - `WaitQueueSnapshot` (interface): interface WaitQueueSnapshot — Serializable line state: who is waiting, and the boardings the estimate is measured from.
1514
1737
  - `WorkQueueConfig` (interface): interface WorkQueueConfig<TSpec, TReserve = undefined, TOutput = undefined> — Injected policy for a queue. Never serialized — pass the same config to every {@link enqueue}/{@link tick}/{@link cancelJob} call for a given queue.
1515
1738
  - `WorkQueueEvent` (type): type WorkQueueEvent<TSpec, TReserve = undefined, TOutput = undefined> = | { readonly type: "started"; readonly job: Job<TSpec, TReserve> } | { readonly type: "completed"; readonly job: Job<TSpec, TReserve>; readonly output: TOutput } — Typed lifecycle event emitted from {@link tick}.
1516
1739
  - `WorkQueueState` (interface): interface WorkQueueState<TSpec, TReserve = undefined> — Serializable queue state. Holds only non-terminal jobs, so it stays bounded.
@@ -1519,6 +1742,10 @@
1519
1742
  - `WorldItemRecord` (interface): interface WorldItemRecord { instanceId: string; itemId: string; rarity: string; baseType: string; count: number; affixTier?: number; source?: string; droppedAt: number } — ⚠ undocumented · used by `useNearestWorldItem` (@jgengine/react): Nearest ground item within `radius` of the local player — drives a pickup prompt/highlight.
1520
1743
  - `WorldItemRenderConfig` (interface): interface WorldItemRenderConfig { rarityStyle?: Record<string, RarityStyle>; filter?: readonly LootFilterRule[]; pickupRadius?: number; beamHeight?: number; autoPickup?: boolean | {radius?: number} } — ⚠ undocumented · used by `WorldItems` (@jgengine/shell/world/WorldItems): Rarity→beam/color/label render binding + loot-filter overlay (#32/#33) for every dropped `worldItem`.
1521
1744
  - `WorldOverlayProps` (interface): interface WorldOverlayProps — Props handed to a `WorldOverlay` component (#542): explicit `ctx` access so canvas-layer VFX read live engine state directly, without an extra hook or a module-global workaround.
1745
+ - `XpCurve` (interface): interface XpCurve — A resolved, validated {@link XpCurveDef}: thresholds, lifetime totals, a stated rule, and a leveling track bound to the same numbers.
1746
+ - `XpCurveDef` (interface): interface XpCurveDef — Authorable XP curve data. Thresholds are generated from one growth rule, so a game states its curve and its cap instead of hand-writing a table it cannot rebalance, and every consumer of the same data agrees on the numbers.
1747
+ - `XpGrowthRule` (type): type XpGrowthRule = "flat" | "linear" | "power" | "geometric" | "table" — Named rule a level threshold grows by. `flat` repeats `step`; `linear` adds `growthRate` per level; `power` uses `level ^ growthRate`; `geometric` multiplies by `growthRate`; `table` reads `table`.
1748
+ - `XpLevelStep` (interface): interface XpLevelStep — One row of a resolved curve: the threshold at `level`, and the lifetime XP it sits at.
1522
1749
  - `activeJobs` (function): function activeJobs<TSpec, TReserve>(state: WorkQueueState<TSpec, TReserve>): Job<TSpec, TReserve>[] — Jobs currently progressing.
1523
1750
  - `activeSetBonuses` (function): function activeSetBonuses(identity: ItemIdentity, bonuses: readonly SetBonus[]): SetBonus[] — Select the set bonuses whose membership count meets their threshold, in the order they were declared.
1524
1751
  - `activeTouchControlsMode` (function): function activeTouchControlsMode(ctx: GameContext): string | null — The active touch control mode, `null` when the base config applies.
@@ -1531,10 +1758,15 @@
1531
1758
  - `appendFeed` (function): function appendFeed<T extends TimedFeedEntry>(list: readonly T[], entry: T, options?: FeedWindow): T[] — Append `entry` to a flat, serializable feed list, then bound it by age (`ttl`, relative to the appended entry's `at`) and/or count (`limit`, newest kept). Works on the game's own flat entry shape — anything with an `at` stamp — so `{ id, text, tone, at }`-style notice lists and event logs drop into serialized state with no `{ at, data }` envelope. Returns a new array.
1532
1759
  - `appendToast` (function): function appendToast<T>(toasts: readonly Toast<T>[], toast: Toast<T>, cap: number): readonly Toast<T>[] — Append `toast`, keeping only the newest `cap` entries.
1533
1760
  - `applyBindingOverrides` (function): function applyBindingOverrides<TAction extends string, TCode extends string>(input: ActionCodesMap<TAction, TCode>, overrides: BindingOverrides): ActionCodesMap<TAction, TCode> — Merge player rebinds over a game's authored `input` map. Only actions the game already declares can be overridden; unknown override keys are ignored so a stale localStorage entry can't inject phantom actions.
1761
+ - `applyMissionRewards` (function): function applyMissionRewards(rewards: MissionRewards, appliers: MissionRewardAppliers): { reason: string } | null — Apply a mission's rewards through the same appliers quests use, then roll any named loot tables. Economy, XP, items and unlocks go through {@link applyQuestRewards}; loot is a table id the game's loot registry resolves, so a mission never grows its own reward path.
1534
1762
  - `applySetBonuses` (function): function applySetBonuses(stats: Record<string, number>, bonuses: readonly SetBonus[]): Record<string, number> — Fold a set of active bonuses' additive stats onto a stat map, returning a new map (the input is not mutated).
1535
1763
  - `applyWear` (function): function applyWear(state: DurabilityState, amount: number): DurabilityState — Apply wear to an item, tracking breakage and repair eligibility.
1764
+ - `assertLootCatalog` (function): function assertLootCatalog(catalog: LootCatalog): void — Check a catalog before rolling; throws `RangeError` naming the offending entry.
1765
+ - `assertSkillTreeConfig` (function): function assertSkillTreeConfig<TStat extends string>(config: SkillTreeConfig<TStat>): void — Check tree data before play; throws `RangeError` naming the offending node, requirement or synergy.
1766
+ - `assertXpCurve` (function): function assertXpCurve(def: XpCurveDef): void — Check curve data before use; throws `RangeError` naming the offending field.
1536
1767
  - `balance` (function): function balance(state: WalletState, currency: string | CurrencyDefinition): number — ⚠ undocumented
1537
1768
  - `balanceOf` (function): function balanceOf(ledger: ResourceLedger, account: string, currency: string): number — Read a single balance; unknown account/currency pairs read as `0`.
1769
+ - `buildLoanSchedule` (function): function buildLoanSchedule(terms: LoanTerms): LoanSchedule — Build a loan's repayment schedule: one entry per period with its due time, interest/principal split and the balance left, in integer minor units. An amortized loan levels the payment over the outstanding balance (`P·i / (1 − (1+i)^−n)`); a flat loan charges interest on the original principal. Either way the final payment absorbs the rounding, so the principal parts sum to the principal exactly and the balance ends at zero. Deterministic and pure.
1538
1770
  - `canAfford` (function): function canAfford(state: WalletState, costs: Readonly<Record<string, number>>): boolean — True when every currency in `costs` has at least that much balance (a pure, non-mutating check).
1539
1771
  - `canCraft` (function): function canCraft(state: InventoryState, layout: InventoryLayout, traits: ItemTraits, recipe: RecipeDef, context: CraftContext = {}): CraftCheck — ⚠ undocumented
1540
1772
  - `cancelJob` (function): function cancelJob<TSpec, TReserve, TOutput>(state: WorkQueueState<TSpec, TReserve>, config: WorkQueueConfig<TSpec, TReserve, TOutput>, id: JobId): CancelResult<TSpec, TReserve> — Cancel a job and compute its refund. Removes the job from the queue and returns a refund payload (full reservation by default, or `config.refund` applied to the job's progress for partial/no refund). Applying the refund is the caller's job.
@@ -1544,8 +1776,11 @@
1544
1776
  - `captureProvenance` (function): function captureProvenance(identity: ItemIdentity, activeBonuses: readonly SetBonus[], seed?: number): ItemProvenance — Capture the provenance of a generated item — family, tags, per-slot parts, active bonus ids, and optional seed — as a JSON-safe record.
1545
1777
  - `charge` (function): function charge(state: WalletState, currency: string | CurrencyDefinition, amount: number, options?: ChargeOptions): ChargeResult — Deduct `amount`, rejecting when it would leave the balance negative unless `options.overdraft` opts into carrying debt (`true` unlimited, `{ max }` capped) — the strict same-tick affordability check stays the default with `options` omitted.
1546
1778
  - `chargeAll` (function): function chargeAll(state: WalletState, costs: Readonly<Record<string, number>>, options?: ChargeOptions): ChargeResult — ⚠ undocumented
1779
+ - `chaseMission` (function): function chaseMission(options: ChaseMissionOptions): MissionDef — A target kept close until it is caught: a `pursue` objective with an escape distance and grace, and an optional `reach` objective to close the chase. The pursuit is the same primitive a bodyguard detail uses; only the numbers differ.
1547
1780
  - `clampValue` (function): function clampValue(value: number, bounds?: NumericBounds): number — Clamp a scalar to `bounds` (identity when `bounds` is omitted). Pure — touches no record.
1548
1781
  - `clearBindingOverride` (function): function clearBindingOverride(gameId: string, action: string, storage: Pick<WebStorageLike, "getItem" | "setItem" | "removeItem"> | null | undefined = defaultStorage()): BindingOverrides — ⚠ undocumented
1782
+ - `compareGearSets` (function): function compareGearSets(a: Readonly<Record<string, GearSlotItem | null | undefined>>, b: Readonly<Record<string, GearSlotItem | null | undefined>>, options: GearScoreOptions = {}): { a: GearScore; b: GearScore; delta: number; better: "a" | "b" | "equal" } — Compare two loadouts; negative means `a` scores lower.
1783
+ - `compareLootItems` (function): function compareLootItems(a: LootItem, b: LootItem, options: LootComparisonOptions = {}): LootComparison — Compare two rolled items: every stat delta plus one weighted score for each, with caller-named weights and which stats read better when small.
1549
1784
  - `compileSystemSchedule` (function): function compileSystemSchedule(systems: readonly SystemDefinition[], options?: CompileSystemScheduleOptions): CompiledSystemSchedule — Compile system definitions into a deterministic schedule. Validates unique ids, `dependsOn`, and before/after cycles.
1550
1785
  - `composeGameLoop` (function): function composeGameLoop(systems: readonly SystemDefinition[] | undefined, loop: GameLoop<GameContext> | undefined, options?: ComposeGameLoopOptions): GameLoop<GameContext> — Merge a system list with an optional classic `GameLoop` into one loop the shell/runners drive. Systems install on first `onInit`; classic hooks still run for incremental migration.
1551
1786
  - `computeEffectiveStats` (function): function computeEffectiveStats(def: ModularItemDef, installed: readonly InstalledPart[]): Record<string, number> — ⚠ undocumented
@@ -1572,6 +1807,7 @@
1572
1807
  - `createGameDialogue` (function): function createGameDialogue(store: DialogueStore): GameDialogue — Build a {@link GameDialogue} over one keyed-store slot. Writes flow through the reactive store, so opening or closing bumps `ctx.version()` and a `useOpenDialogueId` selector re-renders.
1573
1808
  - `createGameEvents` (function): function createGameEvents<TMap extends GameEventMap = GameEventMap>(): GameEvents<TMap> — A typed publish/subscribe bus for gameplay events that systems and HUDs subscribe to.
1574
1809
  - `createGameFeed` (function): function createGameFeed(options?: GameFeedOptions): GameFeed — A rolling per-action feed of recent gameplay events, bindable to the event bus — the HUD ticker and killfeed history.
1810
+ - `createGameSave` (function): function createGameSave(config: GameSaveConfig): GameSave — Create a {@link GameSave}. Register every stateful system once; `capture()` writes one versioned document with a section per system, `save()`/`touch()` persist it through the injected backend (autosave cadence is the caller's), `load()` restores every section and the shared RNG cursor, and `digest()`/`digests()` give a determinism test a hash over the whole state.
1575
1811
  - `createGestureSurfaceTracker` (function): function createGestureSurfaceTracker(bindings: TouchGestureBindings, tuning: GestureSurfaceTuning = DEFAULT_GESTURE_TUNING): GestureSurfaceTracker — ⚠ undocumented
1576
1812
  - `createIntentBoard` (function): function createIntentBoard<TKind extends string = string>(): IntentBoard<TKind> — ⚠ undocumented
1577
1813
  - `createItemInstanceRegistry` (function): function createItemInstanceRegistry<TDef>(prefix = "item", options: ItemInstanceRegistryOptions<TDef> = {}): ItemInstanceRegistry<TDef> — Builds an {@link ItemInstanceRegistry}; generated ids are `"<prefix>:<baseId>:<n>"`, unique per registry instance.
@@ -1583,8 +1819,13 @@
1583
1819
  - `createLevelSequence` (function): function createLevelSequence<TLevelConfig>(config: LevelSequenceConfig<TLevelConfig>): LevelSequence<TLevelConfig> — A pure, deterministic level campaign: an ordered list of levels, each with its own opaque config, played through a `start` → (`clear` → `advance`)* → `complete` happy path, with `fail`/`retry` handling per-level attempts. Mirrors the reducer style of `game/race.ts` and `ai/spawnDirector.ts` — no I/O, no timers, just state transitions driven by the caller.
1584
1820
  - `createListingBook` (function): function createListingBook(config: ListingBookConfig): ListingBook — A player-driven listing marketplace: post/cancel/buy against a shared book with a house cut on every sale, an expiry sweep that pulls unsold goods out of circulation, and a per-seller collection box holding sale proceeds and returned items until claimed. Buyer/seller wallet and inventory movement is the caller's job (mirrors `game/trade`'s split) — this primitive owns only the listing lifecycle and the escrowed collection-box bookkeeping behind it.
1585
1821
  - `createLoadouts` (function): function createLoadouts(deps: LoadoutDeps): Loadouts — Save, name, and swap equipment loadouts.
1822
+ - `createLoanBook` (function): function createLoanBook(options: LoanBookOptions): LoanBook — The loans a business owes, over a {@link TransactionLedger}: each draw posts the principal to the borrower's account, each settled payment posts its interest (`"loan-interest"`) and principal (`"loan-principal"`) as separate reason-tagged entries, and an early repayment posts `"loan-repayment"`. A payment that comes due with no funds is left in arrears and counted once per advance; after `maxMissed` the loan is defaulted and stops being advanced. State is plain data — `snapshot()` stores terms and progress, and the schedule is rebuilt from the terms.
1586
1823
  - `createLootPipeline` (function): function createLootPipeline<TCtx = unknown>(def: LootPipelineDef<TCtx>, deps: LootPipelineDeps = {}): LootPipeline<TCtx> — Build a composable loot-resolution pipeline: ordered source pools, context gates, fallbacks when a pool yields nothing, roll modifiers (luck, quantity, difficulty) applied as registered policies, and full provenance for every drop (stage, table, entry, original vs effective weights, modifier ids, seed). Rolling consumes the injected RNG in the same order as the base loot table, so a seeded resolution is deterministic and server-authoritatively replayable. Genre concepts (world, dedicated, boss, luck, rarity, pity) stay out of core and ship as stage/modifier compositions.
1587
1824
  - `createLootRegistry` (function): function createLootRegistry(options: LootRegistryOptions = {}): LootRegistry — Register named loot tables and roll weighted randomized drops from them.
1825
+ - `createLootRoller` (function): function createLootRoller(catalog: LootCatalog, options: LootRollerOptions = {}): LootRoller — Wrap a catalog in a roll counter: same results as {@link rollLoot}, plus counts by rarity for a debug overlay, snapshot/restore of those counts, and an optional devtools probe.
1826
+ - `createMarket` (function): function createMarket(config: MarketConfig = {}): Market — A market whose price comes from supply and demand over finite stock, as plain serializable state.
1827
+ - `createMissionRuntime` (function): function createMissionRuntime(config: MissionRuntimeConfig): MissionRuntime — Run missions as a state machine. One mission is active at a time; `tick` advances it against the world the caller supplies, moving through objectives on completion, branching on facts, and landing in a named fail state on a fail condition, an expiry or a `minSurvivors` breach. Completion pays rewards through {@link applyMissionRewards} appliers the caller wires, and a mission's `next` chains the follow-up. Escort, chase and defend are compositions of the same objective kinds (`compositions.ts`), never three bespoke systems.
1828
+ - `createMissionTriggerBridge` (function): function createMissionTriggerBridge(options: { runtime: MissionRuntime; document: SceneDocumentLike; triggers?: readonly AuthoredTrigger[]; }): MissionTriggerBridge — Wire authored markers to a {@link MissionRuntime}: an actor entering a `mission_start` marker starts the named mission, `mission_objective` completes the named objective, and `mission_fail` lands the named fail state. Pure membership math over the document, exactly like any other authored trigger.
1588
1829
  - `createModularItem` (function): function createModularItem(def: ModularItemDef, initial: readonly InstalledPart[] = []): ModularItem — ⚠ undocumented
1589
1830
  - `createMoodleStack` (function): function createMoodleStack(): MoodleStack — A stateful holder for timed status moodles (food buffs, temporary shelter, warmth). Meters and multi-region health derive their own moodles on read; combine all three through `stackMoodles(stack.list(), meterMoodles, ailmentMoodles)` for one display.
1590
1831
  - `createMultiRegionHealth` (function): function createMultiRegionHealth(config: MultiRegionHealthConfig): MultiRegionHealth — Per-region/limb health tracked separately, so each body part takes and heals damage on its own.
@@ -1603,24 +1844,30 @@
1603
1844
  - `createRecipeGraph` (function): function createRecipeGraph(defs: readonly RecipeDef[] = []): RecipeGraph — ⚠ undocumented
1604
1845
  - `createRecordBook` (function): function createRecordBook<K extends string>(config: RecordBookConfig<K>): RecordBook<K> — A personal-best record book: named numeric fields each racing toward "lower" (times) or "higher" (scores, streaks), persisted through a structural key-value storage (pass `localStorage` in a browser, a stub in tests, or `null` for in-memory only). Corrupt or unavailable storage degrades to an empty book — a record write never throws into a game tick.
1605
1846
  - `createResourceLedger` (function): function createResourceLedger(init?: Partial<ResourceLedger>): ResourceLedger — Create an empty scheduled-transaction ledger, or hydrate one from a saved snapshot. State is plain data: production, depletion, tax, and upkeep are all expressed as {@link ScheduledRule}s settled deterministically by {@link advanceLedger}.
1847
+ - `createRideChooser` (function): function createRideChooser(config: RideChooserConfig): RideChooser — Create one guest's ride choice over a park. Options are rebuilt when the park is retuned, so a coaster whose track changes its ratings is re-scored without rebuilding the guest.
1848
+ - `createRideSystem` (function): function createRideSystem(config: RideSystemConfig): RideSystem — Create the rides a park runs. Each ride cycles on its own data, boards from its own line up to its own capacity, wears against its own breakdown curve and reports its repairs through the sink, so the caller keeps the wallet, the bodies and the visuals.
1606
1849
  - `createRing` (function): function createRing(config: RingConfig): Ring — ⚠ undocumented
1607
1850
  - `createRuleRegistry` (function): function createRuleRegistry<TPayload = unknown>(initial?: readonly RuleDef<TPayload>[]): RuleRegistry<TPayload> — Create a {@link RuleRegistry}. Rules install through `register` (duplicate ids throw); selection and `layersFor` are data-driven, so the core never branches on which mutators exist.
1608
1851
  - `createRunDraft` (function): function createRunDraft<TStat extends string = string, TData = unknown>(config: RunDraftConfig<TStat, TData>): RunDraft<TStat, TData> — A roguelike run built from stacking drafted modifier picks that reshape the run.
1609
1852
  - `createSaveSlots` (function): function createSaveSlots(config: SaveSlotsConfig = {}): SaveSlots — Create a {@link SaveSlots} index — the serializable, observable list of save/profile slot metadata a save-select menu renders. It is a thin companion to `createSaveStore`: the store owns the real save payload keyed by slot, this owns only the display fields (name, empty, last-saved, and a free-string `meta` bag) the menu shows. `write` stamps a slot saved on the injected clock, `clear` empties one, `rename` relabels one, `mostRecent()` returns the newest non-empty slot to power a "Continue" button, and `list()` feeds the New / Continue / Load / Delete grid. `meta` keys are free strings the game owns — the model never interprets them — and `snapshot`/`restore` round-trips the whole index through a save.
1610
1853
  - `createSaveStore` (function): function createSaveStore<T>(config: SaveStoreConfig<T>): SaveStore<T> — Create a {@link SaveStore}. Same call for offline and cloud — only the `backend` differs (localStorage, memory, or an async DB/Convex endpoint). Turn on `autosave` and every `set`/`patch` persists on a debounce; leave it off and call `save()` at checkpoints. Bump `version` + pass `migrate` when the save shape changes so old players keep their progress.
1611
1854
  - `createShopStock` (function): function createShopStock(config: ShopStockConfig = {}): ShopStock — A serializable, observable vendor/shop stock. Each entry carries a game-owned free-string `kind`, a `price` in a free-string `currency`, a finite or unlimited (`qty: null`) count, and an optional `sellPrice` buy-back. All currency math is delegated to the existing pure `wallet` model — `buy` charges and `sell` grants a **caller-owned** {@link WalletState} (the shop never holds the wallet, it returns the new wallet for the caller to adopt), so the same stock composes with any wallet the game owns. Nothing here is genre-specific: `kind` and `currency` are free labels the game styles and the model never branches on. `restock`/`setPrice`/`add`/`remove` mutate the catalog, `subscribe` observes every change, and `snapshot`/`restore` round-trips the whole stock through a save.
1855
+ - `createSkillTree` (function): function createSkillTree<TStat extends string = string>(config: SkillTreeConfig<TStat>, options: { devtoolsName?: string } = {}): SkillTree<TStat> — A data-authored skill tree: nodes carry a point cost, prerequisites, passives and unlocked actions, synergies pay off two or more nodes together, and `respec` refunds exactly the points spent. Spend against `canSpend` for a reason a UI can show; read `resolved()` for the stat modifiers and action list to feed `createStats` / `createAbilityKit`.
1612
1856
  - `createSocial` (function): function createSocial(deps: SocialDeps): Social — Emotes and lightweight social interactions between nearby players.
1613
1857
  - `createSpawnPoints` (function): function createSpawnPoints(): SpawnPoints — Register spawn locations and choose where entities spawn or respawn.
1614
1858
  - `createStatGraph` (function): function createStatGraph(def: StatGraphDef): StatGraph — A data-driven stat graph: game-owned named inputs feed caller-authored derived formulas, with contribution provenance, uncommitted previews, cycle detection, and selective recomputation. Formula semantics and numeric tables stay entirely game-defined.
1615
1859
  - `createTalentTree` (function): function createTalentTree<TStat extends string = string>(config: TalentTreeConfig<TStat>): TalentTree<TStat> — ⚠ undocumented
1616
1860
  - `createToastQueue` (function): function createToastQueue<T = string>(options: ToastQueueOptions = {}): ToastQueue<T> — A capped, self-expiring toast queue — the append-with-limit plus TTL-prune list every HUD hand-rolled on top of a plain array. Feed it game time: `push` raises a message, `prune(now)` drops expired ones, `list()` is what the HUD renders. Unlike the append-only event feed, toasts evict themselves.
1617
1861
  - `createTouchGestureTracker` (function): function createTouchGestureTracker(tuning: TouchGestureTuning): TouchGestureTracker — ⚠ undocumented
1862
+ - `createTransactionLedger` (function): function createTransactionLedger(options: TransactionLedgerOptions = {}): TransactionLedger — The books a business keeps: per-account, per-currency balances in integer minor units with a journal that records every movement, the balance it produced and the reason it happened. {@link TransactionLedger.post} is the one money path — sales, purchases, upkeep and loan payments all go through it, so nothing moves without a reason on the record. {@link TransactionLedger.chargeDue} settles recurring charges (upkeep, rent) on their own cadence, bounded per advance; a cycle with no funds is counted as missed instead of overdrawing. Journal memory is bounded by `maxEntries`, with `totals()` keeping the per-reason summary beyond it.
1618
1863
  - `createTriggeredRuleEngine` (function): function createTriggeredRuleEngine(rules: readonly TriggeredRule[] = []): TriggeredRuleEngine — Create an empty triggered-rule engine. Rules are added as data; dispatching an event returns the firings the caller applies to its own effect system. Time is caller-supplied (`now` in ms), so the engine is deterministic and drives equally from a fixed-step loop or a save-restored timeline.
1619
1864
  - `createTurnLoop` (function): function createTurnLoop<TAction = unknown>(config: TurnLoopConfig): TurnLoop<TAction> — ⚠ undocumented
1620
1865
  - `createUnlockCatalog` (function): function createUnlockCatalog(defs: readonly UnlockDef[] = []): UnlockCatalog — A catalog of unlockable content gated behind conditions the player earns, tracking what is unlocked.
1621
1866
  - `createUnlocks` (function): function createUnlocks(defs: UnlockDef[] = []): Unlocks — ⚠ undocumented
1622
1867
  - `createUseBehaviorRegistry` (function): function createUseBehaviorRegistry<TWorld>(): UseBehaviorRegistry<TWorld> — Create an empty use-behavior registry. Games register their behaviors, then compose each item's stored refs into a dispatcher whose serializable state lives with the item.
1623
1868
  - `createVfxInstanceStore` (function): function createVfxInstanceStore(options: VfxInstanceStoreOptions = {}): VfxInstanceStore — Build a headless retained-VFX registry. The store is the serializable source of truth for long-lived effects (beams, tethers, zones, target lines, looping emitters) that must move and mutate without one-shot re-emit flicker: `upsert` creates or replaces by stable id, `update` nudges dynamic params, `stop` disposes with an optional fade, and `tick` enforces TTL heartbeats. It stays independent of renderer availability, so simulation and tests run without a shell; a wired `onOp` sink turns each op into a `combat.vfxInstance` event the shell binds to render resources.
1869
+ - `createVisitorPopulation` (function): function createVisitorPopulation(options: VisitorPopulationOptions): VisitorPopulation — Create the population a scene reports, with the game owning arrival and leaving policy. A visitor only exists once the world has made its body, so the HUD's count and the bodies on screen are the same list: `admit` returning `null` is a refused arrival, not a number.
1870
+ - `createWaitQueue` (function): function createWaitQueue(options: WaitQueueOptions = {}): WaitQueue — Create a line whose wait estimate is derived from its own throughput. The queue owns the order and the measurement; the ride owns how fast guests are taken off the head, so a slower cycle raises the estimate without the queue being told a cycle time.
1624
1871
  - `createWeaponStats` (function): function createWeaponStats(resolveEntry: (itemId: string) => WeaponEntry | null | undefined): WeaponStats — Resolve per-weapon stat values — damage, fire rate, spread — for combat math.
1625
1872
  - `createWorkQueue` (function): function createWorkQueue<TSpec, TReserve = undefined>(): WorkQueueState<TSpec, TReserve> — Create an empty timed work queue.
1626
1873
  - `crossThresholds` (function): function crossThresholds<Id>(boundaries: readonly ThresholdBoundary<Id>[], before: number, after: number, options: CrossThresholdsOptions = {}): ThresholdCrossing<Id>[] — Report every boundary crossed moving from `before` to `after`.
@@ -1631,8 +1878,10 @@
1631
1878
  - `decayMeterState` (function): function decayMeterState(values: DecayMeterValues, defs: readonly DecayMeterConfig[], id: string): DecayMeterState — Numeric state (value, bounds, 0..1 fraction) for one meter. Throws on an unknown id.
1632
1879
  - `decayMeters` (function): function decayMeters(values: DecayMeterValues, defs: readonly DecayMeterConfig[], dt: number, modifier?: DecayModifier): DecayMeterValues — Pure per-tick decay over plain data: drain (or fill) every meter by `rate * modifier * dt`, clamped to its range, returning a new `id → value` record. Returns `values` unchanged when `dt <= 0`. The serializable counterpart to {@link DecayMeterSet.tick}.
1633
1880
  - `defaultObjectiveLabel` (function): function defaultObjectiveLabel(objective: QuestObjective): string — Default objective label: a readable "verb count noun" from a {@link QuestObjective}.
1881
+ - `defendMission` (function): function defendMission(options: DefendMissionOptions): MissionDef — Hold a position for a duration, scored by what is left standing: one `survive` objective whose `minSurvivors` gate is the fail condition and whose completion banks the survivors as the mission score. Same primitive a timed delivery uses, with a guard.
1634
1882
  - `defineLootPipeline` (function): function defineLootPipeline<TCtx = unknown>(def: LootPipelineDef<TCtx>): LootPipelineDef<TCtx> — Validate a loot pipeline definition and return it unchanged, for use with {@link createLootPipeline}.
1635
1883
  - `defineSystem` (function): function defineSystem(definition: SystemDefinition): SystemDefinition — Declare a composable game system. Pure data + hooks — the engine compiles the schedule and installs lifecycle when the game boots.
1884
+ - `defineXpCurve` (function): function defineXpCurve(def: XpCurveDef): XpCurve — Resolve authored XP curve data: the threshold per level, the lifetime XP it sits at, a plain-words growth rule, and the level an XP total reaches under the cap. One data record with one growth rule keeps a rebalance in the save-visible definition rather than in formulas scattered across a game.
1636
1885
  - `deriveTouchScheme` (function): function deriveTouchScheme(input: ActionCodesMap | undefined, { reserved, firstPerson, config: rawConfig, mode }: DeriveTouchSchemeOptions): TouchScheme | null — Null means "render no touch controls" — either the game opted out or there is nothing to synthesize.
1637
1886
  - `describeTrackedQuest` (function): function describeTrackedQuest(def: QuestDef, instance: QuestInstance, label: (objective: QuestObjective) => string = defaultObjectiveLabel): TrackedQuestView — Join a quest's static {@link QuestDef} with a player's live {@link QuestInstance} into a flat, renderer-free view a HUD tracker draws (title, status, labelled objective progress). Pass `label` to override the derived objective text.
1638
1887
  - `dialogueSlot` (const): const dialogueSlot: StoreHandle<string | undefined> — Typed handle onto the open-dialogue slot — React reads it via `useOpenDialogueId`; game code uses `ctx.game.dialogue`.
@@ -1643,6 +1892,7 @@
1643
1892
  - `driftValue` (function): function driftValue(record: Record<string, number>, key: string, rate: number, rest = 0, bounds?: NumericBounds): number — Decay `key` toward a `rest` value (default `0`) by `rate` per call — the common "relationships cool off" / "heat fades" drift. Thin wrapper over {@link towardValue}.
1644
1893
  - `durabilityFraction` (function): function durabilityFraction(state: DurabilityState): number — ⚠ undocumented
1645
1894
  - `enqueue` (function): function enqueue<TSpec, TReserve, TOutput>(state: WorkQueueState<TSpec, TReserve>, config: WorkQueueConfig<TSpec, TReserve, TOutput>, spec: TSpec, options?: EnqueueOptions): EnqueueResult<TSpec, TReserve> — Enqueue a new job. Validates capacity and the injected `validate` policy, then computes and stores the reservation. Charging the reservation (now or later) is the caller's responsibility using {@link EnqueueResult.job}'s `reservation`.
1895
+ - `escortMission` (function): function escortMission(options: EscortMissionOptions): MissionDef — A protected actor that must reach a destination: a `reach` objective for the escortee, a `actor-died` fail condition on the escortee, and an optional timer. No escort system — the same `reach` objective a "go here" mission uses, plus the guard data.
1646
1896
  - `evalCurve` (function): function evalCurve(spec: Curve, x: number): number — Evaluate a progression {@link Curve} at position `x`, applying its rounding mode and min/max clamp. This is the shared numeric backbone for XP-per-level, cost ramps, difficulty scaling, and any other level/rank → value mapping — use it instead of hand-rolling growth formulas.
1647
1897
  - `evaluateLootFilter` (function): function evaluateLootFilter(rules: readonly LootFilterRule[], item: LootFilterItem): LootFilterOverride — First matching rule wins (PoE/Last Epoch block semantics) — later rules never override an earlier match. Returns overrides only; fields the rule doesn't set are left for the caller's baseline (rarity style) to fill in.
1648
1898
  - `evaluateObjective` (function): function evaluateObjective(objective: ThresholdObjective, value: number): ObjectiveStatus — Evaluate a single live-metric objective: is `value` at least (or at most) the target, and how far along. Unlike an event counter, this reads a continuously-changing metric — population, approval, pollution — the objective shape city-builders and management sims track every tick.
@@ -1652,6 +1902,8 @@
1652
1902
  - `finishRaceSession` (function): function finishRaceSession(session: RaceSessionState): RaceSessionState — Cross the flag: move a `racing` session to `finished`, freezing its `elapsed`. A no-op in any other phase.
1653
1903
  - `firstPastPost` (function): function firstPastPost(count = 1): RaceWinCondition — Race ends when `count` racers have crossed the finish; ranking is the current standings order.
1654
1904
  - `gamePhase` (function): function gamePhase(ctx: GameContext): GamePhase — Current phase; defaults to `playing` when unset — a game that runs live from boot declares that intent explicitly with `defineGame({ lifecycle: "always-live" })` rather than relying on the silent default.
1905
+ - `gameSaveDigest` (function): function gameSaveDigest(document: GameSaveDocument): string — A stable hash over the state-bearing part of a document — every system section, the world clock and the shared RNG cursor — excluding the wall-clock `savedAt`, which is not state.
1906
+ - `gearScore` (function): function gearScore(set: Readonly<Record<string, GearSlotItem | null | undefined>>, options: GearScoreOptions = {}): GearScore — Score a whole loadout with one documented formula:
1655
1907
  - `generate` (function): function generate(schema: GenSchema, rng: () => number, options: GenerateOptions = {}): GenOutcome — Run a caller-defined {@link GenSchema} against an injected `rng` into a deterministic, serializable {@link GenResult} with full provenance. Composes weighted/uniform choice, dependent choice, constraints with bounded backtracking, field transforms, and validation reroll over plain data — the generic seam procedural loot, affix, and modular-part rollers assemble on. Identical schema, seed, and pins reproduce an identical result across server/client and save/load.
1656
1908
  - `getRuleEffect` (function): function getRuleEffect(id: string): RuleEffectDefinition | undefined — Look up a declared rule effect, or `undefined` when the id was never registered — lets callers reject unknown effect references in authored content.
1657
1909
  - `getValue` (function): function getValue(record: Record<string, number>, key: string, fallback = 0): number — Current value for `key`, or `fallback` (default `0`) when the record has no entry.
@@ -1662,6 +1914,7 @@
1662
1914
  - `install` (function): function install(def: ModularItemDef, installed: readonly InstalledPart[], slotId: string, part: PartDef): InstallResult — ⚠ undocumented
1663
1915
  - `insureLost` (function): function insureLost(lost: readonly ItemStack[], policy: InsurancePolicy, userId: string, now: number, rng?: () => number): ScheduledDelivery | null — ⚠ undocumented
1664
1916
  - `isComplete` (function): function isComplete(def: ModularItemDef, installed: readonly InstalledPart[]): boolean — ⚠ undocumented
1917
+ - `isCounterObjective` (function): function isCounterObjective(kind: MissionObjectiveKind): boolean — True when an objective kind completes from a counter rather than a world condition — the test the counter-only lint is built on.
1665
1918
  - `isDisabled` (function): function isDisabled(spec: DurabilitySpec, state: DurabilityState): boolean — ⚠ undocumented
1666
1919
  - `isIdentityValid` (function): function isIdentityValid(identity: ItemIdentity, rules: readonly CompatibilityRule[]): boolean — Convenience predicate: true when {@link validateIdentity} finds no violations.
1667
1920
  - `isOverdrawn` (function): function isOverdrawn(state: WalletState, currency: string | CurrencyDefinition): boolean — True once `balance(state, currency)` has gone negative under an overdraft-enabled charge.
@@ -1673,11 +1926,15 @@
1673
1926
  - `loadBindingOverrides` (function): function loadBindingOverrides(gameId: string, storage: Pick<WebStorageLike, "getItem"> | null | undefined = defaultStorage()): BindingOverrides — ⚠ undocumented
1674
1927
  - `localSaveBackend` (function): function localSaveBackend(storage?: KeyValueStorage | null, policy: KeyValueStorageErrorPolicy = {}): SaveBackend — A {@link SaveBackend} over a synchronous {@link KeyValueStorage} — the browser's `localStorage` by default (offline, on-device saves), a test stub, or `null` for a no-op backend. Default fallback ignores failures; `errorMode: "throw"` rejects failed operations and unavailable storage, so SaveStore status/onError can report them. Use {@link memorySaveBackend} for deliberate ephemeral persistence.
1675
1928
  - `lootFilter` (function): function lootFilter(rules: readonly LootFilterRule[]): readonly LootFilterRule[] — Validating factory — rule ids must be unique so authoring mistakes fail loudly.
1929
+ - `lootLevelScale` (function): function lootLevelScale(catalog: LootCatalog, level: number): number — Stat multiplier at `level` from the catalog's level curve; `1` when the catalog declares none.
1930
+ - `lootStatContributions` (function): function lootStatContributions(stats: Readonly<Record<string, number>>, options: Pick<LootComparisonOptions, "weights" | "lowerIsBetter"> = {}): Record<string, number> — Per-stat weighted contribution: `value × weight`, negated for stats listed in `lowerIsBetter`. This is the single weight policy behind {@link scoreLootStats}, {@link compareLootItems} and a gear score, so an item and a whole loadout are never ranked by different arithmetic.
1676
1931
  - `lootTable` (function): function lootTable(def: LootTableDef): LootTableDef — Validates a loot table definition and returns it unchanged, for use with {@link createLootRegistry}.
1932
+ - `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.
1677
1933
  - `matchesQuery` (function): function matchesQuery(identity: ItemIdentity, query: IdentityQuery): boolean — Test whether an identity satisfies a declarative {@link IdentityQuery}.
1678
1934
  - `medalFor` (function): function medalFor(rank: number): "gold" | "silver" | "bronze" | null — A free-string medal token for the top-three ranks — `"gold"` (1), `"silver"` (2), `"bronze"` (3) — or `null` for any other rank. It is a semantic token, not a color: the UI maps it to an icon/color/theme, and the model never styles. Feed it a {@link RankedEntry.rank}.
1679
1935
  - `memorySaveBackend` (function): function memorySaveBackend(): SaveBackend — A memory-only {@link SaveBackend} — saves survive a reload only within the same session. For tests, SSR, or a "no persistence" mode that still exercises the same save code path.
1680
1936
  - `missingRequiredSlots` (function): function missingRequiredSlots(def: ModularItemDef, installed: readonly InstalledPart[]): string[] — ⚠ undocumented
1937
+ - `missionMarkers` (function): function missionMarkers(document: SceneDocumentLike): Record<string, ScenePoint3> — Marker positions from an authored scene, keyed by marker id — what a positional objective reads.
1681
1938
  - `moveCards` (function): function moveCards(state: CardPileState, ids: readonly string[], from: ZoneName, to: ZoneName, position: "top" | "bottom" = "top"): PileResult — ⚠ undocumented
1682
1939
  - `normalizePointerToAxis` (function): function normalizePointerToAxis(clientX: number, clientY: number, rect: PointerSurfaceRect): PointerAxisState — Normalize client coordinates against a surface rect into a `PointerAxisState`, clamped to `[-1, 1]` per axis.
1683
1940
  - `orderLayers` (function): function orderLayers(layers: readonly ParamLayer[]): readonly ParamLayer[] — Order layers into their well-defined application sequence: ascending `priority`, ties broken by the layer's index in `layers` (a stable sort). Same input, same order, on every platform.
@@ -1707,6 +1964,7 @@
1707
1964
  - `recallExpedition` (function): function recallExpedition<TCtx>(config: ExpeditionConfig<TCtx>, state: ExpeditionState, nowMs: number): ExpeditionState — Settle first, then recall safely. If the catch-up budget leaves due exploring ticks, return that state without recalling; repeat at the same target until the frontier catches up. Dead state never revives.
1708
1965
  - `redirect` (function): function redirect(endpoints: { source?: string; recipient?: string }): ResourcePolicy — Override the source and/or recipient accounts of a transaction.
1709
1966
  - `refillMeter` (function): function refillMeter(values: DecayMeterValues, defs: readonly DecayMeterConfig[], id: string, amount: number): DecayMeterValues — Refill (or drain, if negative) one meter by `amount`, clamped to its range. Returns a new record; throws on an unknown id. The pure counterpart to {@link DecayMeterSet.refill}.
1967
+ - `registerMissionTriggerActions` (function): function registerMissionTriggerActions(): void — Register {@link MISSION_TRIGGER_ACTIONS} so the editor lists them. Idempotent; call at module load next to the game's catalogs.
1710
1968
  - `registerRuleEffect` (function): function registerRuleEffect(definition: RuleEffectDefinition): void — Declare a rule effect id. Idempotent per id (last registration wins); call at module load next to catalogs so authored content and inspectors share one vocabulary.
1711
1969
  - `rejectWhen` (function): function rejectWhen(predicate: (txn: ResourceTransaction, ctx: PolicyContext) => boolean): ResourcePolicy — Drop a transaction when `predicate` holds — the reject arm of the pipeline.
1712
1970
  - `remoteSaveBackend` (function): function remoteSaveBackend(backend: SaveBackend): SaveBackend — Adopt any async `read`/`write`/`remove` trio as a {@link SaveBackend} — the seam for cloud saves backed by a database, an HTTP endpoint, or Convex (see `@jgengine/convex/convexSaveBackend`). Reads/writes may reject; the save store surfaces the failure as `"error"` status instead of throwing.
@@ -1721,9 +1979,15 @@
1721
1979
  - `resumeJob` (function): function resumeJob<TSpec, TReserve>(state: WorkQueueState<TSpec, TReserve>, id: JobId): WorkQueueState<TSpec, TReserve> — Resume a paused job; it re-enters the queue and competes for a slot by ordering.
1722
1980
  - `resumeRule` (function): function resumeRule(ledger: ResourceLedger, ruleId: string): ResourceLedger — Resume a paused rule. Its `nextDueSeconds` is unchanged, so the paused span counts as missed cycles that the rule's {@link CatchUpPolicy} decides how to settle on the next advance.
1723
1981
  - `returnExpeditionNow` (function): function returnExpeditionNow<TCtx>(config: ExpeditionConfig<TCtx>, state: ExpeditionState, nowMs: number): ExpeditionState — Settle first, then complete a live return instantly. Caller owns payment/authorization; pending ticks remain unsettled when the budget is exhausted, and dead/home state is never changed.
1982
+ - `rideAppeal` (function): function rideAppeal(ratings: RideRatings, taste: RideTaste, range: RideRatingRange = DEFAULT_RIDE_RATING_RANGE): number — One ride's standing appeal to one guest, before queue cost. Excitement draws, force and nausea push back — what a guest's own card or thought bubble can show.
1983
+ - `rideChoiceCandidates` (function): function rideChoiceCandidates(source: RideWaitSource, out: UtilityCandidate[] = []): UtilityCandidate[] — Cost each ride by the wait its own line reports, so a guest weighs a ride by seconds in line rather than by a fixed order. `out` is reused for the array; the candidate records are not.
1984
+ - `rideChoiceOptions` (function): function rideChoiceOptions(rides: readonly RideDefinition[], taste: RideTaste, policy: RideChoicePolicy = {}): UtilityOption[] — Compile a guest's taste and the park's ride data into utility options, so the guest's pick comes from `createUtilityBrain` like every other decision it makes. Ride choice is one more set of options a guest can score, not a separate scoring function.
1985
+ - `rideQueueCostWeight` (function): function rideQueueCostWeight(taste: RideTaste): number — Score points per second of queue wait: a guest's own tolerance is the denominator. A guest who merges rides into a brain that also scores food and exits uses this as its `costWeight`.
1724
1986
  - `ringSampleAt` (function): function ringSampleAt(config: RingConfig, time: number): RingSample — ⚠ undocumented
1987
+ - `rollLoot` (function): function rollLoot(catalog: LootCatalog, rng: () => number, options: LootRollOptions = {}): LootItem — Roll one item from a catalog: a base and maker, a weighted rarity, one part per slot, rarity-count affixes, level-scaled stats and a name composed from maker, rarity, part, affix words and base. Deterministic for a given `rng`; every field it reads is caller data. Validate a catalog built outside {@link createLootRoller} once with {@link assertLootCatalog} before rolling from it.
1725
1988
  - `runPipeline` (function): function runPipeline<V>(base: V, modifiers: readonly Modifier<V>[], equals: (a: V, b: V) => boolean = Object.is): PipelineResult<V> — ⚠ undocumented
1726
1989
  - `saveBindingOverride` (function): function saveBindingOverride(gameId: string, action: string, codes: ActionCodes, storage: Pick<WebStorageLike, "getItem" | "setItem" | "removeItem"> | null | undefined = defaultStorage()): BindingOverrides — ⚠ undocumented
1990
+ - `scoreLootStats` (function): function scoreLootStats(stats: Readonly<Record<string, number>>, options: Pick<LootComparisonOptions, "weights" | "lowerIsBetter"> = {}): number — One stat block's weighted score: the sum of `value × weight`, negated for stats listed in `lowerIsBetter`. Pair it with {@link lootStatContributions} when a caller also needs the split.
1727
1991
  - `seededRng` (function): function seededRng(seed: string | number): SeededRng — Deterministic pseudo-random generator seeded from a string or number — same seed, same sequence.
1728
1992
  - `seededStreams` (function): function seededStreams(seed: string | number): (stream: string) => () => number — Derives independent, deterministic {@link seededRng} streams from one base seed, keyed by stream name.
1729
1993
  - `selectDialogueView` (function): function selectDialogueView(graph: DialogueGraph, nodeId: string): DialogueGraphView | null — Project a {@link DialogueGraph} node id to its render-ready {@link DialogueGraphView} — the pure "current view" selector a stateless renderer reads (speaker, line, choices, done). Returns `null` when the id is not in the graph. No traversal or mutation.
@@ -1756,10 +2020,21 @@
1756
2020
  - `unitTrainingConfig` (function): function unitTrainingConfig(options: UnitTrainingOptions): WorkQueueConfig<UnitTrainingSpec, UnitReservation, UnitSpawnOrder> — Build a {@link WorkQueueConfig} for training units from a catalog. Reservation is the unit's cost + population; duration is its train time; completion output is a {@link UnitSpawnOrder} for the caller to route to its spawn primitive.
1757
2021
  - `validateIdentity` (function): function validateIdentity(identity: ItemIdentity, rules: readonly CompatibilityRule[]): ConstraintViolation[] — Collect every compatibility rule a completed identity violates. An empty result means the identity is legal.
1758
2022
  - `validateLayers` (function): function validateLayers(layers: readonly ParamLayer[]): readonly LayerConflict[] — Detect design conflicts in a layer set before resolving: duplicate ids, and competing `set` ops on the same parameter at the same priority (order-dependent, so worth surfacing). Returns an empty array when the set is clean, so callers can gate a difficulty/session build on it.
2023
+ - `validateLootItem` (function): function validateLootItem(item: LootItem, catalog: LootCatalog): LootValidationIssue[] — Check one rolled item against its catalog: base, maker and rarity exist, every slot holds an eligible part, affixes belong to the base's pools, stats are finite and the item is named.
2024
+ - `validateMissionCatalog` (function): function validateMissionCatalog(missions: readonly MissionDef[], options: MissionValidationOptions = {}): MissionIssue[] — Validate a mission catalog without mutation: duplicate ids, unknown branch/next targets, unknown fail states and markers, per-kind required fields, unreachable objectives, and the counter-only rule — a mission that has a completion path made entirely of `kill`/`collect` objectives is an error, because it can be won on a counter alone.
1759
2025
  - `wear` (function): function wear(spec: DurabilitySpec, state: DurabilityState, kind: WearKind, times = 1): DurabilityState — ⚠ undocumented
1760
2026
  - `withTouchCodes` (function): function withTouchCodes(map: ActionCodesMap | undefined): ActionCodesMap — Every action gains a synthetic touch code alongside its physical codes.
1761
2027
  - `worldHealthBarAllowsRole` (function): function worldHealthBarAllowsRole(roles: readonly CatalogEntityRole[] | undefined, role: CatalogEntityRole | undefined): boolean — ⚠ undocumented
1762
2028
 
2029
+ ## @jgengine/core/inventory/carryLoad
2030
+
2031
+ - `CharacterLoad` (interface): interface CharacterLoad extends EncumbranceState — A carrier's load: the encumbrance state, how it splits into carried and worn, and capacity left.
2032
+ - `CharacterLoadInput` (interface): interface CharacterLoadInput — Everything a {@link characterLoad} reading takes: carried inventories, worn gear and the capacity they are measured against.
2033
+ - `WeightLookup` (type): type WeightLookup = (itemId: string) => number — Per-item mass in the caller's weight unit; an unknown item weighs 0.
2034
+ - `canCarry` (function): function canCarry(load: Pick<CharacterLoad, "mass" | "capacity">, addedMass: number): boolean — Whether `addedMass` more weight still fits under `capacity`. Pure: the caller decides what to do with a refusal, so an overload policy stays with the game.
2035
+ - `carriedEntries` (function): function carriedEntries(states: readonly InventoryState[]): LoadEntry[] — Every filled stack in the given inventories as load entries, in slot order. Only the inventories the caller passes count, so a stash left out of the list adds no weight.
2036
+ - `characterLoad` (function): function characterLoad(input: CharacterLoadInput): CharacterLoad — Weigh a character: carried stacks plus worn gear against a capacity, resolved into the shared encumbrance bands. Movement reads `moveMultiplier`, `canSprint`, `canJump` and `immobile`; pickup gates read `remaining` or {@link canCarry}. The stash is not part of `carried`, so it adds nothing.
2037
+
1763
2038
  ## @jgengine/core/inventory/encumbrance
1764
2039
 
1765
2040
  - `EncumbranceConfig` (interface): interface EncumbranceConfig — Tuning for where the encumbrance bands sit and how hard the carrier is slowed. Both fields are load *fractions* / multipliers in `0..1`, never absolute mass.
@@ -1771,16 +2046,32 @@
1771
2046
  - `resolveEncumbrance` (function): function resolveEncumbrance(mass: number, capacity: number, config?: EncumbranceConfig): EncumbranceState — Resolve carried mass against carrying capacity into a serializable encumbrance state: load fraction, tier, and the movement consequences (`canSprint`/`canJump`/`immobile` gates plus a progressive `moveMultiplier`). Below `soft` the carrier is unencumbered and unhindered; from `soft` up to capacity it is encumbered — no sprint or jump, and speed decays from 1 toward `floor`; at or above capacity it is immobile (multiplier 0). Deterministic and allocation-light; pair with {@link totalLoad} to derive `mass` from an inventory and a mass table. A capacity of 0 or less pins any positive load as immobile (`fraction` 1) so the state stays finite/serializable.
1772
2047
  - `totalLoad` (function): function totalLoad(entries: readonly LoadEntry[], massOf: MassResolver): number — Sum the mass of every carried stack using an injected per-unit mass resolver. Bounded by the number of entries; allocates nothing. Missing or negative unit masses are treated as 0.
1773
2048
 
2049
+ ## @jgengine/core/inventory/equipment
2050
+
2051
+ - `EquipItemDef` (interface): interface EquipItemDef — The gear facts the equip rules read from an item. A rolled item resolves its runtime id through the instance registry to its base and answers from that base's data — this is not a second item type.
2052
+ - `EquipItemLookup` (type): type EquipItemLookup = (itemId: string) => EquipItemDef | null | undefined — Item lookup the rules read; returns nothing for an unknown id.
2053
+ - `EquipRejection` (type): type EquipRejection = "unknown-slot" | "not-equippable" | "wrong-slot" | "already-equipped" | "blocked" — Why an equip attempt was refused; `detail` names the item and slots involved.
2054
+ - `EquipResult` (type): type EquipResult = | { status: "ok"; state: EquipmentState; slotId: string; replaced: string | null } | { status: "rejected"; reason: EquipRejection; detail: string } — An equip attempt: the new worn set plus the replaced item, or the refusal with its reason and detail.
2055
+ - `EquipSlotDef` (interface): interface EquipSlotDef — One body slot: the item kinds it accepts and the slots it occupies while filled.
2056
+ - `Equipment` (interface): interface Equipment — A worn-gear set: one item per slot, rules from slot and item data, state out and back in.
2057
+ - `EquipmentOptions` (interface): interface EquipmentOptions — Options for {@link createEquipment}.
2058
+ - `EquipmentProbeReading` (interface): interface EquipmentProbeReading — What the debug overlay reads about a worn set.
2059
+ - `EquipmentState` (interface): interface EquipmentState — Serializable equip state: the filled slots as `[slotId, itemId]` pairs.
2060
+ - `UnequipResult` (type): type UnequipResult = | { status: "ok"; state: EquipmentState; itemId: string } | { status: "rejected"; reason: "unknown-slot" | "empty-slot"; detail: string } — An unequip attempt: the new worn set plus the item taken off, or the refusal with its detail.
2061
+ - `WornEntry` (interface): interface WornEntry — One worn item and the slot holding it.
2062
+ - `createEquipment` (function): function createEquipment(slotDefs: readonly EquipSlotDef[], lookup: EquipItemLookup, options: EquipmentOptions = {}): Equipment — A worn-gear set over caller slot and item data.
2063
+
1774
2064
  ## @jgengine/core/inventory/inventoryModel
1775
2065
 
1776
2066
  - `InventoryLayout` (interface): interface InventoryLayout { slots: number; accepts?: string | readonly string[] } — ⚠ undocumented · used by `createEmptyInventory`: A bag of stackable items supporting add, remove, count, and move — the base inventory model.
1777
2067
  - `InventorySet` (interface): interface InventorySet<TId extends string> { put(id: TId, itemId: string, count: number, options?: {slot?: number}): PutResult; take(id: TId, itemId: string, count: number): TakeResult; move(fromId: TId, fromSlot: number, toId: TId, toSlot?: number): MoveResult; count(id: TId, itemI… — ⚠ undocumented · used by `createInventorySet`: Build a keyed set of named inventories from their `layouts`, sharing one `traits` table — the stateful façade over put/take/move/count.
2068
+ - `InventorySetState` (type): type InventorySetState<TId extends string> = Record<TId, InventoryState> — Every inventory in a set, keyed by id, as plain slot data.
1778
2069
  - `InventorySlot` (type): type InventorySlot = { itemId: string; count: number } | null — ⚠ undocumented
1779
2070
  - `InventoryState` (interface): interface InventoryState { slots: InventorySlot[] } — ⚠ undocumented · used by `countItem`: Total quantity of `itemId` summed across every stack in the inventory.
1780
2071
  - `ItemTraits` (interface): interface ItemTraits { stackLimit(itemId: string): number; kind?(itemId: string): string | null } — ⚠ undocumented · used by `createInventorySet`: Build a keyed set of named inventories from their `layouts`, sharing one `traits` table — the stateful façade over put/take/move/count.
1781
2072
  - `MoveResult` (type): type MoveResult = | { status: "ok"; from: InventoryState; to: InventoryState } | { status: "rejected"; reason: "invalid-slot" | "empty-slot" | "wrong-kind" | "no-space" } — ⚠ undocumented · used by `moveItem`: Move the stack at `fromSlot` to `toSlot` (or auto-stack when omitted) — swapping, merging with remainder, or relocating across two inventori…
1782
2073
  - `PutResult` (type): type PutResult = | { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "no-space" | "wrong-kind" | "slot-occupied" | "invalid-slot" } — ⚠ undocumented · used by `putItem`: Add `count` of `itemId` — into a specific `options.slot`, or auto-stacked across free/partial slots.
1783
- - `SplitResult` (type): type SplitResult = | { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "invalid-slot" | "empty-slot" | "invalid-amount" | "no-space" | "slot-occupied" } — Outcome of {@link splitStack}: the new state, or a rejection reason.
2074
+ - `SplitResult` (type): type SplitResult = | { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "invalid-slot" | "empty-slot" | "invalid-amount" | "no-space" | "slot-occupied" | "over-stack" } — Outcome of {@link splitStack}: the new state, or a rejection reason.
1784
2075
  - `TakeResult` (type): type TakeResult = { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "insufficient" } — ⚠ undocumented · used by `takeItem`: Remove `count` of `itemId`, draining from the last matching stacks first.
1785
2076
  - `countItem` (function): function countItem(state: InventoryState, itemId: string): number — Total quantity of `itemId` summed across every stack in the inventory.
1786
2077
  - `createEmptyInventory` (function): function createEmptyInventory(layout: InventoryLayout): InventoryState — A bag of stackable items supporting add, remove, count, and move — the base inventory model.
@@ -1788,7 +2079,7 @@
1788
2079
  - `hasItem` (function): function hasItem(state: InventoryState, itemId: string, count: number): boolean — True when the inventory holds at least `count` of `itemId`.
1789
2080
  - `moveItem` (function): function moveItem(from: InventoryState, fromSlot: number, to: InventoryState, toLayout: InventoryLayout, traits: ItemTraits, toSlot?: number): MoveResult — Move the stack at `fromSlot` to `toSlot` (or auto-stack when omitted) — swapping, merging with remainder, or relocating across two inventories. Rejects on empty source, wrong kind, or no space.
1790
2081
  - `putItem` (function): function putItem(state: InventoryState, layout: InventoryLayout, traits: ItemTraits, itemId: string, count: number, options?: { slot?: number }): PutResult — Add `count` of `itemId` — into a specific `options.slot`, or auto-stacked across free/partial slots. Rejects on no space or wrong kind.
1791
- - `splitStack` (function): function splitStack(state: InventoryState, slot: number, amount: number, toSlot?: number): SplitResult — Splits `amount` off the stack in `slot` into `toSlot` (or the first empty slot when `toSlot` is omitted). Limit-agnostic: it does not consult {@link ItemTraits}, so a same-item target merges by simple addition. Rejects an empty source, `amount <= 0`, `amount >= stack count` (a split must leave both sides non-empty), no free slot, or a target holding a different item.
2082
+ - `splitStack` (function): function splitStack(state: InventoryState, slot: number, amount: number, toSlot?: number, limit?: number): SplitResult — Splits `amount` off the stack in `slot` into `toSlot` (or the first empty slot when `toSlot` is omitted). Without `limit` it is limit-agnostic: a same-item target merges by simple addition. Pass the item's stack limit to keep the merge legal, which rejects `over-stack`. Rejects an empty source, `amount <= 0`, `amount >= stack count` (a split must leave both sides non-empty), no free slot, or a target holding a different item.
1792
2083
  - `takeItem` (function): function takeItem(state: InventoryState, itemId: string, count: number): TakeResult — Remove `count` of `itemId`, draining from the last matching stacks first. Rejects if fewer than `count` are held.
1793
2084
 
1794
2085
  ## @jgengine/core/inventory/shapedGrid
@@ -1798,8 +2089,10 @@
1798
2089
  - `GridAdjacencyQuery` (interface): interface GridAdjacencyQuery { cellsOf(id: string): readonly Cell[]; neighborsOf(id: string): readonly string[]; touching(idA: string, idB: string): boolean; adjacentCells(cells: readonly Cell[]): readonly Cell[] } — ⚠ undocumented
1799
2090
  - `Placement` (interface): interface Placement<T> { id: string; value: T; footprint: Footprint; origin: Cell; rotation: Rotation } — ⚠ undocumented
1800
2091
  - `Rotation` (type): type Rotation = 0 | 1 | 2 | 3 — ⚠ undocumented · used by `canPlace`: Check bounds and overlap for a discrete footprint, optionally ignoring the item being moved.
2092
+ - `ShapedFitRejection` (type): type ShapedFitRejection = "duplicate-id" | "invalid-footprint" | "too-large" | "no-space" | "budget-exceeded" — Why a first-fit placement failed: the footprint cannot fit an empty board at any offered rotation (`too-large`), the board is genuinely full (`no-space`), or the search hit its budget.
2093
+ - `ShapedFitResult` (type): type ShapedFitResult<T> = | { status: "ok"; grid: ShapedGrid<T>; origin: Cell; rotation: Rotation; checks: number } | { status: "rejected"; reason: ShapedFitRejection; detail: string; checks: number } — A placed board, or a refusal that names why the item does not fit.
1801
2094
  - `ShapedGrid` (interface): interface ShapedGrid<T> { readonly width: number; readonly height: number; readonly placements: readonly Placement<T>[] } — ⚠ undocumented · used by `canPlace`: Check bounds and overlap for a discrete footprint, optionally ignoring the item being moved.
1802
- - `ShapedItem` (interface): interface ShapedItem<T> { id: string; value: T; footprint: Footprint } — ⚠ undocumented · used by `placeShaped`: Place a caller-defined item without mutating the source board; duplicate ids reject.
2095
+ - `ShapedItem` (interface): interface ShapedItem<T> { id: string; value: T; footprint: Footprint } — ⚠ undocumented · used by `fitShapedItem`: Place a shaped item at the first legal spot, or say why it does not fit.
1803
2096
  - `ShapedPlaceResult` (interface): interface ShapedPlaceResult<T> { status: "ok"; grid: ShapedGrid<T> } — ⚠ undocumented
1804
2097
  - `ShapedPlacementSearchOptions` (interface): interface ShapedPlacementSearchOptions — Search policy: rotation order first, then rows and columns; the work budget counts candidate origins.
1805
2098
  - `ShapedPlacementSearchResult` (type): type ShapedPlacementSearchResult = | { status: "found"; origin: Cell; rotation: Rotation; checks: number } | { status: "no-space"; checks: number } | { status: "budget-exceeded"; checks: number } — A detached placement, a completed no-fit search, or an incomplete search that reached its budget.
@@ -1809,6 +2102,7 @@
1809
2102
  - `canPlace` (function): function canPlace<T>(grid: ShapedGrid<T>, footprint: Footprint, origin: Cell, rotation: Rotation, ignoreId?: string): ShapedRejection | null — Check bounds and overlap for a discrete footprint, optionally ignoring the item being moved.
1810
2103
  - `createShapedGrid` (function): function createShapedGrid<T>(width: number, height: number): ShapedGrid<T> — An immutable spatial inventory board holding caller-defined multi-cell items.
1811
2104
  - `findShapedPlacement` (function): function findShapedPlacement<T>(grid: ShapedGrid<T>, footprint: Footprint, options: ShapedPlacementSearchOptions = {}): ShapedPlacementSearchResult — Find the first legal placement without mutating the board; occupancy is indexed once per search.
2105
+ - `fitShapedItem` (function): function fitShapedItem<T>(grid: ShapedGrid<T>, item: ShapedItem<T>, options: ShapedPlacementSearchOptions = {}): ShapedFitResult<T> — Place a shaped item at the first legal spot, or say why it does not fit. Distinguishes an item that can never fit the board (`too-large`, with its footprint and the board size in `detail`) from a full board (`no-space`) and an exhausted search (`budget-exceeded`, not a full bag).
1812
2106
  - `moveShaped` (function): function moveShaped<T>(grid: ShapedGrid<T>, id: string, origin: Cell, rotation?: Rotation): ShapedResult<T> — Move or rotate one placement, ignoring its current cells during overlap checks.
1813
2107
  - `placeShaped` (function): function placeShaped<T>(grid: ShapedGrid<T>, item: ShapedItem<T>, origin: Cell, rotation: Rotation = 0): ShapedResult<T> — Place a caller-defined item without mutating the source board; duplicate ids reject.
1814
2108
  - `removeShaped` (function): function removeShaped<T>(grid: ShapedGrid<T>, id: string): ShapedResult<T> — Remove one placement without mutating the source board.
@@ -1969,6 +2263,115 @@
1969
2263
  - `createWeaponStats` (function): function createWeaponStats(resolveEntry: (itemId: string) => WeaponEntry | null | undefined): WeaponStats — Resolve per-weapon stat values — damage, fire rate, spread — for combat math.
1970
2264
  - `getWeaponStat` (function): function getWeaponStat(entry: WeaponEntry | null | undefined, stat: string): number | null — ⚠ undocumented
1971
2265
 
2266
+ ## @jgengine/core/loot/lootGen
2267
+
2268
+ - `LootBaseDef` (interface): interface LootBaseDef extends ItemBaseDef — A base family (frame, housing, chassis — caller nouns) with its slots, stats and affix pools.
2269
+ - `LootCatalog` (interface): interface LootCatalog — Everything a roll reads: bases, parts, affixes, rarity odds, makers and the level curve.
2270
+ - `LootComparison` (interface): interface LootComparison — How two items compare: every stat delta plus one weighted score for each.
2271
+ - `LootComparisonOptions` (interface): interface LootComparisonOptions — Caller policy for the comparison score.
2272
+ - `LootInstalledPart` (interface): interface LootInstalledPart — One part occupying one slot of a rolled item.
2273
+ - `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-finite-stat" | "empty-… — A defect found by {@link validateLootItem}.
2274
+ - `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.
2275
+ - `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.
2276
+ - `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.
2277
+ - `LootRollOptions` (interface): interface LootRollOptions — Pin any part of a roll; omitted fields are drawn from the catalog.
2278
+ - `LootRoller` (interface): interface LootRoller — A roll counter over one catalog: every roll goes through {@link rollLoot}, and the handle keeps counters (a distribution readout in the debug overlay) that snapshot and restore as plain data.
2279
+ - `LootRollerOptions` (interface): interface LootRollerOptions — Options for {@link createLootRoller}.
2280
+ - `LootRollerState` (interface): interface LootRollerState — Saved counters of a {@link LootRoller}.
2281
+ - `LootStatDelta` (interface): interface LootStatDelta — A per-stat comparison entry; `delta` is `b - a`, so positive means the second item is larger.
2282
+ - `LootValidationIssue` (interface): interface LootValidationIssue — One reason a rolled item is not legal against its catalog, with the offending value named.
2283
+ - `MAX_LOOT_AFFIXES` (const): const MAX_LOOT_AFFIXES: 8 — Cap on affixes one item may roll, so a bad catalog cannot make a drop cost unbounded work.
2284
+ - `assertLootCatalog` (function): function assertLootCatalog(catalog: LootCatalog): void — Check a catalog before rolling; throws `RangeError` naming the offending entry.
2285
+ - `compareLootItems` (function): function compareLootItems(a: LootItem, b: LootItem, options: LootComparisonOptions = {}): LootComparison — Compare two rolled items: every stat delta plus one weighted score for each, with caller-named weights and which stats read better when small.
2286
+ - `createLootRoller` (function): function createLootRoller(catalog: LootCatalog, options: LootRollerOptions = {}): LootRoller — Wrap a catalog in a roll counter: same results as {@link rollLoot}, plus counts by rarity for a debug overlay, snapshot/restore of those counts, and an optional devtools probe.
2287
+ - `lootLevelScale` (function): function lootLevelScale(catalog: LootCatalog, level: number): number — Stat multiplier at `level` from the catalog's level curve; `1` when the catalog declares none.
2288
+ - `lootStatContributions` (function): function lootStatContributions(stats: Readonly<Record<string, number>>, options: Pick<LootComparisonOptions, "weights" | "lowerIsBetter"> = {}): Record<string, number> — Per-stat weighted contribution: `value × weight`, negated for stats listed in `lowerIsBetter`. This is the single weight policy behind {@link scoreLootStats}, {@link compareLootItems} and a gear score, so an item and a whole loadout are never ranked by different arithmetic.
2289
+ - `rollLoot` (function): function rollLoot(catalog: LootCatalog, rng: () => number, options: LootRollOptions = {}): LootItem — Roll one item from a catalog: a base and maker, a weighted rarity, one part per slot, rarity-count affixes, level-scaled stats and a name composed from maker, rarity, part, affix words and base. Deterministic for a given `rng`; every field it reads is caller data. Validate a catalog built outside {@link createLootRoller} once with {@link assertLootCatalog} before rolling from it.
2290
+ - `scoreLootStats` (function): function scoreLootStats(stats: Readonly<Record<string, number>>, options: Pick<LootComparisonOptions, "weights" | "lowerIsBetter"> = {}): number — One stat block's weighted score: the sum of `value × weight`, negated for stats listed in `lowerIsBetter`. Pair it with {@link lootStatContributions} when a caller also needs the split.
2291
+ - `validateLootItem` (function): function validateLootItem(item: LootItem, catalog: LootCatalog): LootValidationIssue[] — Check one rolled item against its catalog: base, maker and rarity exist, every slot holds an eligible part, affixes belong to the base's pools, stats are finite and the item is named.
2292
+
2293
+ ## @jgengine/core/loot/lootWeapon
2294
+
2295
+ - `LootWeaponProjection` (interface): interface LootWeaponProjection — A base weapon definition plus the stat map a rolled item overrides.
2296
+ - `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.
2297
+ - `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.
2298
+
2299
+ ## @jgengine/core/mission/compositions
2300
+
2301
+ - `ChaseMissionOptions` (interface): interface ChaseMissionOptions extends ComposedMissionOptions — Options for {@link chaseMission}: a target kept close until it is caught.
2302
+ - `ComposedMissionOptions` (interface): interface ComposedMissionOptions — Options shared by every composed mission.
2303
+ - `DefendMissionOptions` (interface): interface DefendMissionOptions extends ComposedMissionOptions — Options for {@link defendMission}: hold a position, scored by what is left standing.
2304
+ - `EscortMissionOptions` (interface): interface EscortMissionOptions extends ComposedMissionOptions — Options for {@link escortMission}: a protected actor that must reach a destination alive.
2305
+ - `chaseMission` (function): function chaseMission(options: ChaseMissionOptions): MissionDef — A target kept close until it is caught: a `pursue` objective with an escape distance and grace, and an optional `reach` objective to close the chase. The pursuit is the same primitive a bodyguard detail uses; only the numbers differ.
2306
+ - `defendMission` (function): function defendMission(options: DefendMissionOptions): MissionDef — Hold a position for a duration, scored by what is left standing: one `survive` objective whose `minSurvivors` gate is the fail condition and whose completion banks the survivors as the mission score. Same primitive a timed delivery uses, with a guard.
2307
+ - `escortMission` (function): function escortMission(options: EscortMissionOptions): MissionDef — A protected actor that must reach a destination: a `reach` objective for the escortee, a `actor-died` fail condition on the escortee, and an optional timer. No escort system — the same `reach` objective a "go here" mission uses, plus the guard data.
2308
+
2309
+ ## @jgengine/core/mission/mission
2310
+
2311
+ - `DEFAULT_ESCAPE_MULTIPLE` (const): const DEFAULT_ESCAPE_MULTIPLE: 3 — A pursuit escapes at this multiple of its keep-close distance when none is declared.
2312
+ - `DEFAULT_PURSUE_DISTANCE` (const): const DEFAULT_PURSUE_DISTANCE: 3 — Default keep-close distance (m) for a `pursue` objective.
2313
+ - `DEFAULT_REACH_RADIUS` (const): const DEFAULT_REACH_RADIUS: 2.5 — Default proximity radius (m) for a `reach` / `interact` objective.
2314
+ - `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.
2315
+ - `MissionActorFact` (interface): interface MissionActorFact — A live actor the machine tracks: whether it is alive and, when needed, where it is.
2316
+ - `MissionBranch` (interface): interface MissionBranch — One outgoing edge of an objective: the first branch whose `when` matches wins over `next`.
2317
+ - `MissionDef` (interface): interface MissionDef — A whole mission: the objective graph plus its fail states and rewards.
2318
+ - `MissionEvent` (type): type MissionEvent = | { kind: "started"; missionId: string; mission: MissionDef } | { kind: "objective"; missionId: string; objectiveId: string; label: string } | { kind: "branch"; missionId: string; from: string; to: string; label?: string } | { kind: "timeout"; missionId: string; objectiveId: stri… — Emitted as the machine advances.
2319
+ - `MissionEventFilter` (interface): interface MissionEventFilter — Which world events an objective counts or a fail condition reacts to. Absent fields do not filter.
2320
+ - `MissionFactCondition` (interface): interface MissionFactCondition — A readable fact test a branch or a `fact` fail condition reads. All comparators are optional.
2321
+ - `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.
2322
+ - `MissionFailDef` (interface): interface MissionFailDef — A named fail state an objective, timer or branch can land in.
2323
+ - `MissionIssue` (interface): interface MissionIssue — A located mission-catalog problem; `path` is a JSON Pointer into the supplied catalog.
2324
+ - `MissionObjectiveDef` (interface): interface MissionObjectiveDef — One objective. Every field is content; the machine reads only these.
2325
+ - `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`).
2326
+ - `MissionRewardAppliers` (interface): interface MissionRewardAppliers — Appliers a mission reward routes through — the same shape quests use, plus a loot-table hook.
2327
+ - `MissionRewards` (interface): interface MissionRewards extends QuestRewards — What a mission pays. Economy/XP/items/unlocks flow through the quest appliers; loot names tables.
2328
+ - `MissionRuntime` (interface): interface MissionRuntime — The mission state machine over one active mission.
2329
+ - `MissionRuntimeConfig` (interface): interface MissionRuntimeConfig — Everything a mission runtime needs.
2330
+ - `MissionRuntimeSnapshot` (interface): interface MissionRuntimeSnapshot — Serializable runtime state for a save.
2331
+ - `MissionState` (interface): interface MissionState — Serializable mission state — one active mission's position in its graph.
2332
+ - `MissionStats` (interface): interface MissionStats — Counters a devtools probe or a test reads.
2333
+ - `MissionStatus` (type): type MissionStatus = "active" | "completed" | "failed" — Where a mission is in its life.
2334
+ - `MissionTickInput` (interface): interface MissionTickInput — Everything the machine reads for one tick. All optional; a mission only touches what it declares.
2335
+ - `MissionValidationOptions` (interface): interface MissionValidationOptions — Declarations a mission catalog is validated against.
2336
+ - `MissionView` (interface): interface MissionView — Pooled per-frame readout a HUD tracker renders.
2337
+ - `MissionWorldEvent` (interface): interface MissionWorldEvent — One thing that happened in the world this tick, reported by the game. A constrained objective counts matching events and an `event` fail condition reacts to them, so the machine judges the world instead of the game polling its own counters.
2338
+ - `MissionWorldEventKind` (type): type MissionWorldEventKind = "kill" | "collect" | "deliver" — What a reported world event is.
2339
+ - `applyMissionRewards` (function): function applyMissionRewards(rewards: MissionRewards, appliers: MissionRewardAppliers): { reason: string } | null — Apply a mission's rewards through the same appliers quests use, then roll any named loot tables. Economy, XP, items and unlocks go through {@link applyQuestRewards}; loot is a table id the game's loot registry resolves, so a mission never grows its own reward path.
2340
+ - `createMissionRuntime` (function): function createMissionRuntime(config: MissionRuntimeConfig): MissionRuntime — Run missions as a state machine. One mission is active at a time; `tick` advances it against the world the caller supplies, moving through objectives on completion, branching on facts, and landing in a named fail state on a fail condition, an expiry or a `minSurvivors` breach. Completion pays rewards through {@link applyMissionRewards} appliers the caller wires, and a mission's `next` chains the follow-up. Escort, chase and defend are compositions of the same objective kinds (`compositions.ts`), never three bespoke systems.
2341
+ - `isCounterObjective` (function): function isCounterObjective(kind: MissionObjectiveKind): boolean — True when an objective kind completes from a counter rather than a world condition — the test the counter-only lint is built on.
2342
+ - `validateMissionCatalog` (function): function validateMissionCatalog(missions: readonly MissionDef[], options: MissionValidationOptions = {}): MissionIssue[] — Validate a mission catalog without mutation: duplicate ids, unknown branch/next targets, unknown fail states and markers, per-kind required fields, unreachable objectives, and the counter-only rule — a mission that has a completion path made entirely of `kill`/`collect` objectives is an error, because it can be won on a counter alone.
2343
+
2344
+ ## @jgengine/core/mission/triggers
2345
+
2346
+ - `MISSION_TRIGGER_ACTIONS` (const): const MISSION_TRIGGER_ACTIONS: readonly TriggerActionDefinition[] — The mission actions a game registers so the editor inspector can assign them to a marker or volume. Params land flat on the object's `meta` beside `on`/`action`, like any authored trigger.
2347
+ - `MissionTriggerBridge` (interface): interface MissionTriggerBridge — Watches a document's authored mission triggers against moving actors.
2348
+ - `createMissionTriggerBridge` (function): function createMissionTriggerBridge(options: { runtime: MissionRuntime; document: SceneDocumentLike; triggers?: readonly AuthoredTrigger[]; }): MissionTriggerBridge — Wire authored markers to a {@link MissionRuntime}: an actor entering a `mission_start` marker starts the named mission, `mission_objective` completes the named objective, and `mission_fail` lands the named fail state. Pure membership math over the document, exactly like any other authored trigger.
2349
+ - `missionMarkers` (function): function missionMarkers(document: SceneDocumentLike): Record<string, ScenePoint3> — Marker positions from an authored scene, keyed by marker id — what a positional objective reads.
2350
+ - `registerMissionTriggerActions` (function): function registerMissionTriggerActions(): void — Register {@link MISSION_TRIGGER_ACTIONS} so the editor lists them. Idempotent; call at module load next to the game's catalogs.
2351
+
2352
+ ## @jgengine/core/progression/gearScore
2353
+
2354
+ - `GearScore` (interface): interface GearScore — A scored loadout: the total, each slot's share, each stat's signed contribution, and how complete it is.
2355
+ - `GearScoreOptions` (interface): interface GearScoreOptions extends Pick<LootComparisonOptions, "weights" | "lowerIsBetter"> — Caller policy for a loadout score: stat weights, which stats read better small, and what level, rarity and an empty slot are worth.
2356
+ - `GearSlotItem` (interface): interface GearSlotItem — The least a worn item must expose to be scored: its stat block, plus optional level and rarity for the gear-score terms.
2357
+ - `compareGearSets` (function): function compareGearSets(a: Readonly<Record<string, GearSlotItem | null | undefined>>, b: Readonly<Record<string, GearSlotItem | null | undefined>>, options: GearScoreOptions = {}): { a: GearScore; b: GearScore; delta: number; better: "a" | "b" | "equal" } — Compare two loadouts; negative means `a` scores lower.
2358
+ - `gearScore` (function): function gearScore(set: Readonly<Record<string, GearSlotItem | null | undefined>>, options: GearScoreOptions = {}): GearScore — Score a whole loadout with one documented formula:
2359
+
2360
+ ## @jgengine/core/progression/skillTree
2361
+
2362
+ - `SkillActiveDef` (interface): interface SkillActiveDef — An action a node or synergy unlocks, shaped to bind straight into `createAbilityKit`'s slot config.
2363
+ - `SkillNodeDef` (interface): interface SkillNodeDef<TStat extends string = string> — One authored tree node. Every field except `id` is optional data, so a game states its own tree: what a rank costs, what must come first, the passive it folds in and the actions it opens.
2364
+ - `SkillRequirement` (interface): interface SkillRequirement — One node that must be at or above `rank` for a node or synergy to open; `rank` defaults to `1`.
2365
+ - `SkillResolution` (interface): interface SkillResolution<TStat extends string = string> — The whole outcome of a build: merged passives, unlocked actions, live synergies and point accounting.
2366
+ - `SkillSpendReason` (type): type SkillSpendReason = | "unknown-node" | "max-rank" | "no-points" | "requires" | "branch-points" | "excluded" — Why a spend was refused; each reason is reachable from authored data, so a UI can say which rule bit.
2367
+ - `SkillSpendResult` (type): type SkillSpendResult = | { ok: true; spent: number; pointsAvailable: number } | { ok: false; reason: SkillSpendReason; detail: string } — The outcome of one spend or legality check: what it costs and the balance after it, or the rule that stopped it.
2368
+ - `SkillSynergyDef` (interface): interface SkillSynergyDef<TStat extends string = string> — A payoff that exists only when two or more nodes are ranked together. This is what rewards breadth over dumping every point into one branch.
2369
+ - `SkillTree` (interface): interface SkillTree<TStat extends string = string> — A live skill tree over one authored config, with respec, snapshot/restore and an optional devtools probe.
2370
+ - `SkillTreeConfig` (interface): interface SkillTreeConfig<TStat extends string = string> — Everything {@link createSkillTree} reads: nodes, optional synergies, and starting points.
2371
+ - `SkillTreeState` (interface): interface SkillTreeState — Saved tree state: unspent points and the rank of every ranked node.
2372
+ - `assertSkillTreeConfig` (function): function assertSkillTreeConfig<TStat extends string>(config: SkillTreeConfig<TStat>): void — Check tree data before play; throws `RangeError` naming the offending node, requirement or synergy.
2373
+ - `createSkillTree` (function): function createSkillTree<TStat extends string = string>(config: SkillTreeConfig<TStat>, options: { devtoolsName?: string } = {}): SkillTree<TStat> — A data-authored skill tree: nodes carry a point cost, prerequisites, passives and unlocked actions, synergies pay off two or more nodes together, and `respec` refunds exactly the points spent. Spend against `canSpend` for a reason a UI can show; read `resolved()` for the stat modifiers and action list to feed `createStats` / `createAbilityKit`.
2374
+
1972
2375
  ## @jgengine/core/progression/statGraph
1973
2376
 
1974
2377
  - `StatContribution` (interface): interface StatContribution — One folded contribution to a value, retained so a sheet can explain "why is this value 42?" — every step carries the source that produced it.
@@ -1986,6 +2389,15 @@
1986
2389
  - `createStatGraph` (function): function createStatGraph(def: StatGraphDef): StatGraph — A data-driven stat graph: game-owned named inputs feed caller-authored derived formulas, with contribution provenance, uncommitted previews, cycle detection, and selective recomputation. Formula semantics and numeric tables stay entirely game-defined.
1987
2390
  - `statModifierContributions` (function): function statModifierContributions<TStat extends string>(source: string, set: StatModifierSet<TStat>): Record<string, StatContribution[]> — Bridge the shared {@link StatModifierSet} shape (add/multiply, used by talents, items, and buffs) into stat-graph contributions, so a ranked talent tree or gear roll feeds the graph as one named source instead of a parallel store.
1988
2391
 
2392
+ ## @jgengine/core/progression/xpCurve
2393
+
2394
+ - `XpCurve` (interface): interface XpCurve — A resolved, validated {@link XpCurveDef}: thresholds, lifetime totals, a stated rule, and a leveling track bound to the same numbers.
2395
+ - `XpCurveDef` (interface): interface XpCurveDef — Authorable XP curve data. Thresholds are generated from one growth rule, so a game states its curve and its cap instead of hand-writing a table it cannot rebalance, and every consumer of the same data agrees on the numbers.
2396
+ - `XpGrowthRule` (type): type XpGrowthRule = "flat" | "linear" | "power" | "geometric" | "table" — Named rule a level threshold grows by. `flat` repeats `step`; `linear` adds `growthRate` per level; `power` uses `level ^ growthRate`; `geometric` multiplies by `growthRate`; `table` reads `table`.
2397
+ - `XpLevelStep` (interface): interface XpLevelStep — One row of a resolved curve: the threshold at `level`, and the lifetime XP it sits at.
2398
+ - `assertXpCurve` (function): function assertXpCurve(def: XpCurveDef): void — Check curve data before use; throws `RangeError` naming the offending field.
2399
+ - `defineXpCurve` (function): function defineXpCurve(def: XpCurveDef): XpCurve — Resolve authored XP curve data: the threshold per level, the lifetime XP it sits at, a plain-words growth rule, and the level an XP total reaches under the cap. One data record with one growth rule keeps a rebalance in the save-visible definition rather than in formulas scattered across a game.
2400
+
1989
2401
  ## @jgengine/core/puzzle/cellGrid
1990
2402
 
1991
2403
  - `CellGrid` (interface): interface CellGrid<T> — A fixed-size 2D board of cells, row-major from the top-left; `null` is an empty cell. Plain data, so it serializes and restores as-is.
@@ -2184,6 +2596,63 @@
2184
2596
  - `OffScreenWorld` (interface): interface OffScreenWorld — The off-screen world: every resident exists, but only the ones within the policy radius are simulated. The rest are a position, a routine phase and a needs record that only move when the clock crosses a routine boundary — no navigation, no utility scoring, no per-frame need decay.
2185
2597
  - `createOffScreenWorld` (function): function createOffScreenWorld(options: OffScreenOptions): OffScreenWorld — Create the world that decides which agents deserve a simulation. Residents are cheap state until they come near, and cheap again when they leave; the cheap state is the schedule, the needs and the last place, so a returning resident never pops in at the origin.
2186
2598
 
2599
+ ## @jgengine/core/sim/population
2600
+
2601
+ - `Visitor` (interface): interface Visitor — One visitor on site: the id its body, its needs and its money are keyed by.
2602
+ - `VisitorAdoption` (interface): interface VisitorAdoption — How a body the game made itself joins the population.
2603
+ - `VisitorFlow` (interface): interface VisitorFlow — What a flow policy reads to decide how many arrive and when one has had enough.
2604
+ - `VisitorPolicy` (interface): interface VisitorPolicy — The game's flow policy: what draws visitors in, how many fit, and when one has had enough.
2605
+ - `VisitorPolicyPatch` (interface): interface VisitorPolicyPatch — Flow policy fields a scenario can change in play.
2606
+ - `VisitorPopulation` (interface): interface VisitorPopulation — The crowd a scene reports. Every visitor is a member with a body the game's world made, so the on-site figure is the member count and the daily figure is the sum of admissions the world accepted — there is no second tally to drift from what is on the ground. Arrivals, capacity and leaving are the game's policy; ids, day rollover and counting are this seam's.
2607
+ - `VisitorPopulationOptions` (interface): interface VisitorPopulationOptions — Options for {@link createVisitorPopulation}.
2608
+ - `VisitorPopulationSnapshot` (interface): interface VisitorPopulationSnapshot — Serializable state. No on-site count is stored: it is the restored visitor list.
2609
+ - `VisitorPopulationStats` (interface): interface VisitorPopulationStats — Counters a HUD, a probe or a test reads. The on-site figure is the visitor list's length.
2610
+ - `VisitorWorld` (interface): interface VisitorWorld — The game's world behind every counted visitor: it creates and removes the body.
2611
+ - `createVisitorPopulation` (function): function createVisitorPopulation(options: VisitorPopulationOptions): VisitorPopulation — Create the population a scene reports, with the game owning arrival and leaving policy. A visitor only exists once the world has made its body, so the HUD's count and the bodies on screen are the same list: `admit` returning `null` is a refused arrival, not a number.
2612
+
2613
+ ## @jgengine/core/sim/queue
2614
+
2615
+ - `DEFAULT_QUEUE_SEED_SERVICE_SECONDS` (const): const DEFAULT_QUEUE_SEED_SERVICE_SECONDS: 8 — Seconds one guest occupies the head of the line before any boarding interval has been measured. A bootstrap only: the estimate switches to the queue's own measured throughput as soon as two boardings have been observed.
2616
+ - `DEFAULT_QUEUE_WINDOW` (const): const DEFAULT_QUEUE_WINDOW: 8 — Boarding events the throughput window keeps.
2617
+ - `QueueEntry` (interface): interface QueueEntry — One guest waiting their turn, and when they joined the line.
2618
+ - `QueueWait` (interface): interface QueueWait — A wait estimate read off the queue's own measured throughput.
2619
+ - `WaitQueue` (interface): interface WaitQueue — A real line: guests join the back, leave the head, and the wait a new arrival faces comes from how fast this queue has actually been boarding — not from a configured constant. A broken ride stops boarding, the line grows, and the estimate grows with it.
2620
+ - `WaitQueueOptions` (interface): interface WaitQueueOptions — Tunables for {@link createWaitQueue}, every one retunable through a restore.
2621
+ - `WaitQueueSnapshot` (interface): interface WaitQueueSnapshot — Serializable line state: who is waiting, and the boardings the estimate is measured from.
2622
+ - `createWaitQueue` (function): function createWaitQueue(options: WaitQueueOptions = {}): WaitQueue — Create a line whose wait estimate is derived from its own throughput. The queue owns the order and the measurement; the ride owns how fast guests are taken off the head, so a slower cycle raises the estimate without the queue being told a cycle time.
2623
+
2624
+ ## @jgengine/core/sim/rides
2625
+
2626
+ - `DEFAULT_RIDE_BREAKDOWN` (const): const DEFAULT_RIDE_BREAKDOWN: RideBreakdown — A ride's breakdown curve a park starts from when it has none and retunes only part of one.
2627
+ - `DEFAULT_RIDE_FUN_CURVE` (const): const DEFAULT_RIDE_FUN_CURVE: ResponseCurve — How empty a bored guest's fun need has to be before a ride starts to pull, by default.
2628
+ - `DEFAULT_RIDE_RATING_RANGE` (const): const DEFAULT_RIDE_RATING_RANGE: RideRatingRange — A 0–10 rating scale, the usual shape of a ride catalog. Override it when a park rates differently.
2629
+ - `RideBreakdown` (interface): interface RideBreakdown — Breakdown curve and money for one ride. All data, so a park tunes its own economy.
2630
+ - `RideChoicePolicy` (interface): interface RideChoicePolicy — Policy a park might vary when it turns taste and ride data into utility options.
2631
+ - `RideChooser` (interface): interface RideChooser — One guest's ride choice over one park: taste becomes utility options, each ride is costed by its own line's wait, and the pick comes from the shared utility brain — so the same brain that picks food picks rides, and a guest's queue tolerance is a weight rather than a rule.
2632
+ - `RideChooserConfig` (interface): interface RideChooserConfig — Everything a ride chooser needs: the park, the guest's taste, and the guest's needs.
2633
+ - `RideChooserSnapshot` (interface): interface RideChooserSnapshot — Serializable chooser state: the guest's taste and the brain's held pick.
2634
+ - `RideDefinition` (interface): interface RideDefinition — Everything a ride is: its ratings, its cycle and its wear. Content, not code.
2635
+ - `RidePhase` (type): type RidePhase = "idle" | "loading" | "riding" | "unloading" | "broken" — Where a ride is in its cycle.
2636
+ - `RideQueueView` (interface): interface RideQueueView — A ride's line as a game or the devtools overlay reads it.
2637
+ - `RideRatingRange` (interface): interface RideRatingRange — The rating scale a park's taste curves are normalized against.
2638
+ - `RideRatings` (interface): interface RideRatings — What a ride is worth to a guest. All three come from the ride's own data.
2639
+ - `RideRetune` (interface): interface RideRetune — In-play dials a ride accepts from {@link RideSystem.retune}.
2640
+ - `RideServiceResult` (interface): interface RideServiceResult — What a service cost the caller.
2641
+ - `RideStats` (interface): interface RideStats — Counters a devtools probe or test reads.
2642
+ - `RideSystem` (interface): interface RideSystem — The park's rides: each runs its own load → ride → unload cycle at its own capacity, takes guests off its own line, wears out against its own breakdown curve, and reports its own repairs and services. The clock is internal and only `tick` moves it, so a replay, a save and a test read the same cycle.
2643
+ - `RideSystemConfig` (interface): interface RideSystemConfig — Everything a ride system needs.
2644
+ - `RideSystemSnapshot` (interface): interface RideSystemSnapshot — Serializable park state: the clock, each ride's cycle position, wear and line.
2645
+ - `RideTaste` (interface): interface RideTaste — What one guest wants from a ride: the intensity and nausea they are comfortable with, and how long they will stand in line. All three are the guest's own data.
2646
+ - `RideTickSink` (interface): interface RideTickSink — Where a ride's boardings, alightings and breakdowns are announced. Every hook is optional.
2647
+ - `RideView` (interface): interface RideView — One ride's live state.
2648
+ - `RideWaitSource` (interface): interface RideWaitSource — The live queue a ride-choice candidate is costed against. {@link RideSystem} satisfies this.
2649
+ - `createRideChooser` (function): function createRideChooser(config: RideChooserConfig): RideChooser — Create one guest's ride choice over a park. Options are rebuilt when the park is retuned, so a coaster whose track changes its ratings is re-scored without rebuilding the guest.
2650
+ - `createRideSystem` (function): function createRideSystem(config: RideSystemConfig): RideSystem — Create the rides a park runs. Each ride cycles on its own data, boards from its own line up to its own capacity, wears against its own breakdown curve and reports its repairs through the sink, so the caller keeps the wallet, the bodies and the visuals.
2651
+ - `rideAppeal` (function): function rideAppeal(ratings: RideRatings, taste: RideTaste, range: RideRatingRange = DEFAULT_RIDE_RATING_RANGE): number — One ride's standing appeal to one guest, before queue cost. Excitement draws, force and nausea push back — what a guest's own card or thought bubble can show.
2652
+ - `rideChoiceCandidates` (function): function rideChoiceCandidates(source: RideWaitSource, out: UtilityCandidate[] = []): UtilityCandidate[] — Cost each ride by the wait its own line reports, so a guest weighs a ride by seconds in line rather than by a fixed order. `out` is reused for the array; the candidate records are not.
2653
+ - `rideChoiceOptions` (function): function rideChoiceOptions(rides: readonly RideDefinition[], taste: RideTaste, policy: RideChoicePolicy = {}): UtilityOption[] — Compile a guest's taste and the park's ride data into utility options, so the guest's pick comes from `createUtilityBrain` like every other decision it makes. Ride choice is one more set of options a guest can score, not a separate scoring function.
2654
+ - `rideQueueCostWeight` (function): function rideQueueCostWeight(taste: RideTaste): number — Score points per second of queue wait: a guest's own tolerance is the denominator. A guest who merges rides into a brain that also scores food and exits uses this as its `costWeight`.
2655
+
2187
2656
  ## @jgengine/core/sim/routine
2188
2657
 
2189
2658
  - `RoutineConfig` (interface): interface RoutineConfig — Everything needed to resolve a routine: its authored stops, its cycle, and how far apart places are.