@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
@@ -36,12 +36,15 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
36
36
 
37
37
  - `cancelJob` (function) · `import { cancelJob } from "@jgengine/core/gameplay"`
38
38
 
39
- ## carry-encumbrance — carried mass vs capacity into movement penalty tiers and a speed multiplier
39
+ ## carry-encumbrance — gate a pickup against the carrier's remaining capacity
40
40
 
41
+ - `canCarry` (function) · `import { canCarry } from "@jgengine/core/inventory/carryLoad"`
42
+ - `characterLoad` (function) · `import { characterLoad } from "@jgengine/core/inventory/carryLoad"`
41
43
  - `resolveEncumbrance` (function) · `import { resolveEncumbrance } from "@jgengine/core/inventory/encumbrance"`
42
44
 
43
- ## carry-load — sum carried mass from item stacks via an injected mass resolver
45
+ ## carry-load — read carried stacks out of one or more inventories for a weight query
44
46
 
47
+ - `carriedEntries` (function) · `import { carriedEntries } from "@jgengine/core/inventory/carryLoad"`
45
48
  - `totalLoad` (function) · `import { totalLoad } from "@jgengine/core/inventory/encumbrance"`
46
49
 
47
50
  ## cell-grid — immutable 2D board of cells with row clears, gravity collapse, and match runs
@@ -148,6 +151,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
148
151
 
149
152
  - `enqueue` (function) · `import { enqueue } from "@jgengine/core/gameplay"`
150
153
 
154
+ ## equipment-slot-inventory — wear gear by slot with item-kind rules, two-handed blocking and replace-on-equip
155
+
156
+ - `createEquipment` (function) · `import { createEquipment } from "@jgengine/core/inventory/equipment"`
157
+
151
158
  ## event-bus — typed publish/subscribe bus for gameplay events
152
159
 
153
160
  - `createGameEvents` (function) · `import { createGameEvents } from "@jgengine/core/gameplay"`
@@ -180,14 +187,23 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
180
187
 
181
188
  - `ActionCodesMap` (type) · `import { ActionCodesMap } from "@jgengine/core/gameplay"`
182
189
 
183
- ## game-save — save/load game state with a pluggable backend, autosave, slots, and migration
190
+ ## game-save — one versioned save document with per-system sections, named slots, caller-policy autosave, migration and a state digest
184
191
 
192
+ - `createGameSave` (function) · `import { createGameSave } from "@jgengine/core/gameplay"`
185
193
  - `createSaveStore` (function) · `import { createSaveStore } from "@jgengine/core/gameplay"`
186
194
 
195
+ ## game-save-digest — hash a save document's state for a determinism test or a diff
196
+
197
+ - `gameSaveDigest` (function) · `import { gameSaveDigest } from "@jgengine/core/gameplay"`
198
+
187
199
  ## game-system — declare a composable capability with its own schedule and lifecycle
188
200
 
189
201
  - `SystemDefinition` (interface) · `import { SystemDefinition } from "@jgengine/core/gameplay"`
190
202
 
203
+ ## gear-score — score a loadout from stat weights, level, rarity and slot coverage
204
+
205
+ - `gearScore` (function) · `import { gearScore } from "@jgengine/core/gameplay"`
206
+
191
207
  ## group-credits — turn a flat list of credited things into ordered credit sections
192
208
 
193
209
  - `groupCredits` (function) · `import { groupCredits } from "@jgengine/core/game/credits"`
@@ -212,7 +228,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
212
228
 
213
229
  - `createEmptyInventory` (function) · `import { createEmptyInventory } from "@jgengine/core/inventory/inventoryModel"`
214
230
 
215
- ## inventory-split — split a stack into a target or first-empty slot
231
+ ## inventory-split — split a stack into a target or first-empty slot, honoring a stack limit
216
232
 
217
233
  - `splitStack` (function) · `import { splitStack } from "@jgengine/core/inventory/inventoryModel"`
218
234
 
@@ -279,6 +295,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
279
295
 
280
296
  - `createLoadouts` (function) · `import { createLoadouts } from "@jgengine/core/gameplay"`
281
297
 
298
+ ## loan-schedule — build a loan's interest and principal repayment schedule in integer minor units
299
+
300
+ - `buildLoanSchedule` (function) · `import { buildLoanSchedule } from "@jgengine/core/gameplay"`
301
+
302
+ ## loans — draw, schedule, service and default a loan with interest, posted to a ledger
303
+
304
+ - `createLoanBook` (function) · `import { createLoanBook } from "@jgengine/core/gameplay"`
305
+
282
306
  ## local-save — persist a game to on-device localStorage (offline)
283
307
 
284
308
  - `localSaveBackend` (function) · `import { localSaveBackend } from "@jgengine/core/gameplay"`
@@ -287,20 +311,45 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
287
311
 
288
312
  - `RarityStyle` (interface) · `import { RarityStyle } from "@jgengine/core/gameplay"`
289
313
 
314
+ ## loot-comparison — compare two rolled items with per-stat deltas and a weighted score
315
+
316
+ - `compareLootItems` (function) · `import { compareLootItems } from "@jgengine/core/gameplay"`
317
+
290
318
  ## loot-filter — filter and highlight drops by rarity/name rules
291
319
 
292
320
  - `evaluateLootFilter` (function) · `import { evaluateLootFilter } from "@jgengine/core/gameplay"`
293
321
 
322
+ ## loot-generation — roll a named item from bases, parts, affixes, rarity weights, manufacturer personalities and level scaling
323
+
324
+ - `rollLoot` (function) · `import { rollLoot } from "@jgengine/core/gameplay"`
325
+
294
326
  ## loot-pipeline — compose ordered loot pools with fallbacks, roll modifiers, and provenance
295
327
 
296
328
  - `createLootPipeline` (function) · `import { createLootPipeline } from "@jgengine/core/gameplay"`
297
329
  - `defineLootPipeline` (function) · `import { defineLootPipeline } from "@jgengine/core/gameplay"`
298
330
 
331
+ ## loot-roller — roll items from one validated catalog while counting drops per rarity for a debug probe
332
+
333
+ - `createLootRoller` (function) · `import { createLootRoller } from "@jgengine/core/gameplay"`
334
+
335
+ ## loot-score — split one stat block into per-stat weighted contributions
336
+
337
+ - `lootStatContributions` (function) · `import { lootStatContributions } from "@jgengine/core/gameplay"`
338
+ - `scoreLootStats` (function) · `import { scoreLootStats } from "@jgengine/core/gameplay"`
339
+
299
340
  ## loot-table — register loot tables and roll weighted randomized drops
300
341
 
301
342
  - `createLootRegistry` (function) · `import { createLootRegistry } from "@jgengine/core/gameplay"`
302
343
  - `lootTable` (function) · `import { lootTable } from "@jgengine/core/gameplay"`
303
344
 
345
+ ## loot-validation — check a rolled item against its catalog and report each defect with its code
346
+
347
+ - `validateLootItem` (function) · `import { validateLootItem } from "@jgengine/core/gameplay"`
348
+
349
+ ## loot-weapon — feed a rolled item into a weapon definition so generated gear fires through the weapon system
350
+
351
+ - `lootWeaponDefinition` (function) · `import { lootWeaponDefinition } from "@jgengine/core/gameplay"`
352
+
304
353
  ## maturation-stage — resolve the baby/juvenile/adolescent/adult stage from elapsed maturation
305
354
 
306
355
  - `maturationStage` (function) · `import { maturationStage } from "@jgengine/core/game/breeding"`
@@ -309,6 +358,36 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
309
358
 
310
359
  - `mergeCredits` (function) · `import { mergeCredits } from "@jgengine/core/game/credits"`
311
360
 
361
+ ## mission-compositions — build escort, chase and defend missions from the shared objective kinds
362
+
363
+ - `chaseMission` (function) · `import { chaseMission } from "@jgengine/core/gameplay"`
364
+ - `defendMission` (function) · `import { defendMission } from "@jgengine/core/gameplay"`
365
+ - `escortMission` (function) · `import { escortMission } from "@jgengine/core/gameplay"`
366
+
367
+ ## mission-events — count constrained kills and fail a mission on a matching world event
368
+
369
+ - `MissionWorldEvent` (interface) · `import { MissionWorldEvent } from "@jgengine/core/gameplay"`
370
+
371
+ ## mission-rewards — pay mission rewards through the quest appliers and the loot registry
372
+
373
+ - `applyMissionRewards` (function) · `import { applyMissionRewards } from "@jgengine/core/gameplay"`
374
+
375
+ ## mission-runtime — missions as a data-driven objective state machine with fail states, timers, branches and rewards
376
+
377
+ - `createMissionRuntime` (function) · `import { createMissionRuntime } from "@jgengine/core/gameplay"`
378
+
379
+ ## mission-triggers — start, advance and fail a mission from authored scene markers
380
+
381
+ - `MISSION_TRIGGER_ACTIONS` (const) · `import { MISSION_TRIGGER_ACTIONS } from "@jgengine/core/gameplay"`
382
+ - `createMissionTriggerBridge` (function) · `import { createMissionTriggerBridge } from "@jgengine/core/gameplay"`
383
+ - `missionMarkers` (function) · `import { missionMarkers } from "@jgengine/core/gameplay"`
384
+ - `registerMissionTriggerActions` (function) · `import { registerMissionTriggerActions } from "@jgengine/core/gameplay"`
385
+
386
+ ## mission-validation — tell whether an objective kind completes from a counter alone
387
+
388
+ - `isCounterObjective` (function) · `import { isCounterObjective } from "@jgengine/core/gameplay"`
389
+ - `validateMissionCatalog` (function) · `import { validateMissionCatalog } from "@jgengine/core/gameplay"`
390
+
312
391
  ## modular-item — attach parts into item mount slots to compute combined stats
313
392
 
314
393
  - `slotAccepts` (function) · `import { slotAccepts } from "@jgengine/core/gameplay"`
@@ -482,6 +561,21 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
482
561
 
483
562
  - `createVfxInstanceStore` (function) · `import { createVfxInstanceStore } from "@jgengine/core/gameplay"`
484
563
 
564
+ ## ride-appeal — how much one guest likes one ride, from its ratings and their taste
565
+
566
+ - `rideAppeal` (function) · `import { rideAppeal } from "@jgengine/core/gameplay"`
567
+
568
+ ## ride-choice — guests pick rides by taste and queue tolerance through the utility brain
569
+
570
+ - `createRideChooser` (function) · `import { createRideChooser } from "@jgengine/core/gameplay"`
571
+ - `rideChoiceCandidates` (function) · `import { rideChoiceCandidates } from "@jgengine/core/gameplay"`
572
+ - `rideChoiceOptions` (function) · `import { rideChoiceOptions } from "@jgengine/core/gameplay"`
573
+ - `rideQueueCostWeight` (function) · `import { rideQueueCostWeight } from "@jgengine/core/gameplay"`
574
+
575
+ ## ride-sim — rides with capacity, cycles, queues, ratings, breakdowns and maintenance
576
+
577
+ - `createRideSystem` (function) · `import { createRideSystem } from "@jgengine/core/gameplay"`
578
+
485
579
  ## routine-timeline — authored daily routines (home, work, meal) resolved against the world clock
486
580
 
487
581
  - `resolveRoutine` (function) · `import { resolveRoutine } from "@jgengine/core/gameplay"`
@@ -496,6 +590,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
496
590
 
497
591
  - `createRunDraft` (function) · `import { createRunDraft } from "@jgengine/core/gameplay"`
498
592
 
593
+ ## save-slot-index — refresh a save-select index from the game's save documents
594
+
595
+ - `refreshSaveSlots` (function) · `import { refreshSaveSlots } from "@jgengine/core/game/saveSlotIndex"`
596
+
597
+ ## save-slot-meta — project a save document onto save-select display metadata
598
+
599
+ - `saveSlotMeta` (function) · `import { saveSlotMeta } from "@jgengine/core/game/saveSlotIndex"`
600
+
499
601
  ## save-slots — serializable save-slot / profile metadata index — write/clear/rename/mostRecent over free-string per-slot meta, powering New/Continue/Load/Delete menus, with snapshot/restore
500
602
 
501
603
  - `createSaveSlots` (function) · `import { createSaveSlots } from "@jgengine/core/gameplay"`
@@ -523,11 +625,16 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
523
625
  ## shaped-placement — find a shaped inventory item's first fit with ordered rotations, row-major placement, and an explicit search budget
524
626
 
525
627
  - `findShapedPlacement` (function) · `import { findShapedPlacement } from "@jgengine/core/inventory/shapedGrid"`
628
+ - `fitShapedItem` (function) · `import { fitShapedItem } from "@jgengine/core/inventory/shapedGrid"`
526
629
 
527
630
  ## shop-grid — serializable vendor/shop stock with buy/sell over a caller-owned wallet — finite/unlimited qty, sell-back, restock/setPrice, snapshot/restore
528
631
 
529
632
  - `createShopStock` (function) · `import { createShopStock } from "@jgengine/core/gameplay"`
530
633
 
634
+ ## skill-tree — spend, refuse and refund points over an authored tree with passives, actives and synergies
635
+
636
+ - `createSkillTree` (function) · `import { createSkillTree } from "@jgengine/core/gameplay"`
637
+
531
638
  ## social-emotes — emotes and social interactions between nearby players
532
639
 
533
640
  - `createSocial` (function) · `import { createSocial } from "@jgengine/core/gameplay"`
@@ -552,6 +659,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
552
659
 
553
660
  - `statModifierContributions` (function) · `import { statModifierContributions } from "@jgengine/core/gameplay"`
554
661
 
662
+ ## supply-demand-pricing — price goods from finite stock and live demand on a stated curve
663
+
664
+ - `createMarket` (function) · `import { createMarket } from "@jgengine/core/gameplay"`
665
+
555
666
  ## system-schedule — compile fixed/frame/interval system ticks into deterministic ordered stages
556
667
 
557
668
  - `compileSystemSchedule` (function) · `import { compileSystemSchedule } from "@jgengine/core/gameplay"`
@@ -582,6 +693,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
582
693
  - `setTouchControlsMode` (function) · `import { setTouchControlsMode } from "@jgengine/core/gameplay"`
583
694
  - `touchButtonShape` (function) · `import { touchButtonShape } from "@jgengine/core/gameplay"`
584
695
 
696
+ ## transaction-ledger — record every money movement with its reason over per-account integer minor-unit balances
697
+
698
+ - `createTransactionLedger` (function) · `import { createTransactionLedger } from "@jgengine/core/gameplay"`
699
+
585
700
  ## tribe-group — shared-ownership group (tribe/guild/clan) with ranked permissions, group vs personal assets, alliances, and a bounded event log
586
701
 
587
702
  - `createTribe` (function) · `import { createTribe } from "@jgengine/core/game/tribe"`
@@ -617,6 +732,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
617
732
 
618
733
  - `createUnlockCatalog` (function) · `import { createUnlockCatalog } from "@jgengine/core/gameplay"`
619
734
 
735
+ ## visitor-population — a crowd whose on-site and daily counts are the bodies the game made
736
+
737
+ - `createVisitorPopulation` (function) · `import { createVisitorPopulation } from "@jgengine/core/gameplay"`
738
+
739
+ ## wait-queue — a real line whose wait estimate comes from its own measured throughput
740
+
741
+ - `createWaitQueue` (function) · `import { createWaitQueue } from "@jgengine/core/gameplay"`
742
+
620
743
  ## wallet — hold currency balances with charge and affordability checks
621
744
 
622
745
  - `createEmptyWallet` (function) · `import { createEmptyWallet } from "@jgengine/core/gameplay"`
@@ -660,3 +783,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
660
783
  ## work-queued-jobs — list jobs waiting for a slot, in selection order
661
784
 
662
785
  - `queuedJobs` (function) · `import { queuedJobs } from "@jgengine/core/gameplay"`
786
+
787
+ ## xp-curve — resolve an authored xp growth rule into per-level thresholds, totals and a level cap
788
+
789
+ - `defineXpCurve` (function) · `import { defineXpCurve } from "@jgengine/core/gameplay"`
@@ -28,6 +28,124 @@ and level state, then emits every reached level in ascending order. State stays
28
28
  in the caller's save format and round-trips as ordinary JSON. See the
29
29
  [portable XP/leveling recipe](recipes/portable-xp-leveling.md).
30
30
 
31
+ ## Procedural gear
32
+
33
+ `loot/lootGen` rolls a whole named item from a caller-authored `LootCatalog`:
34
+ bases (`baseStats`, affix `pools`, mount `slots`), parts, affix pools, weighted
35
+ `RarityTier`s, `LootManufacturerDef`s (name, flat `bias`, per-stat `scale`,
36
+ favored pools) and a `levelScale` curve of `[level, multiplier]` points. A roll
37
+ picks maker, base, rarity, one part per slot and `affixCount` affixes; stats are
38
+ `base × rarity.statScale × levelScale`, plus part stats/multipliers, maker
39
+ personality, then additive affixes and multiplicative ones. Additive affix
40
+ rolls scale with level; multiplicative ones do not. Names follow one rule:
41
+ maker, rarity word, first part word, first prefix affix, base name, first suffix
42
+ affix. Every random draw uses the injected `rng`, so a seed reproduces the item.
43
+
44
+ ```ts
45
+ import { compareLootItems, createLootRoller, lootWeaponDefinition, seededRng } from "@jgengine/core/gameplay";
46
+
47
+ const roller = createLootRoller(catalog, { devtoolsName: "loot" });
48
+ const item = roller.roll(seededRng(dropSeed), { level: 30 });
49
+ const delta = compareLootItems(item, equipped, { lowerIsBetter: ["reload"] });
50
+ const weapon = lootWeaponDefinition(item, { template, stats: { damage: "power", roundsPerMinute: "cadence" } });
51
+ ```
52
+
53
+ `validateLootItem` returns coded issues (`unknown-base`, `part-category`,
54
+ `missing-slot-part`, `unknown-affix`, `empty-name`…) and the roller's counters
55
+ snapshot/restore as plain data. Register a roll through
56
+ `proceduralLootEntry` into `item/itemInstanceRegistry` and hand its runtime id
57
+ to `game/lootTable`; the weapon projection only maps stats it is told to, so a
58
+ catalog without weapon stats stays usable for non-weapon gear. Check a
59
+ distribution with a fixed seed over 10,000 drops against each tier's 4σ
60
+ binomial bound rather than eyeballing shares.
61
+
62
+ ## Progression and gear score
63
+
64
+ `progression/xpCurve` turns one authored growth rule into the level table, the cap and a
65
+ plain-words rule line, so the balance lives in save-visible data:
66
+
67
+ ```ts
68
+ import { createSkillTree, defineXpCurve, gearScore } from "@jgengine/core/gameplay";
69
+
70
+ const xp = defineXpCurve({ growth: "power", step: 100, growthRate: 1.5, maxLevel: 40, round: "floor" });
71
+ // rule: "power: 100 x level^1.5, floor (levels 1-40)"; xpForLevel(2) is 282, xpForLevel(40) is 0
72
+ xp.levelForXp(xp.totalXpFor(7)).level; // 7
73
+ xp.track(); // the same thresholds as a `leveling` track for a stat adapter
74
+
75
+ const tree = createSkillTree({ nodes, synergies, points: 6 }, { devtoolsName: "skills" });
76
+ tree.spend("strike"); // { ok: true, spent: 1, pointsAvailable: 5 }
77
+ tree.canSpend("sight"); // { ok: false, reason: "requires", detail: '...needs rank 1 of "focus"' }
78
+ const { stats, actives, synergies: live } = tree.resolved();
79
+ tree.respec(); // { refunded: 6, pointsAvailable: 6 }
80
+ ```
81
+
82
+ A `SkillNodeDef` is `{ id, branch?, cost?, maxRank?, requires?, requiresPointsInBranch?,
83
+ excludes?, passives?, actives?, tags? }`; a `SkillSynergyDef` lists at least two `requires` and
84
+ adds passives/actives only while they all hold. `excludes` is symmetric, so two nodes named as
85
+ alternatives block each other in both directions. Actives are `{ id, cooldownMs?, resourceCost?,
86
+ castType?, groups? }`, shaped to bind into `createAbilityKit` slots. Snapshot/restore carries
87
+ `{ points, ranks }`; restore refuses unknown nodes, over-ranked nodes and negative points without
88
+ mutating. A tree where breadth beats depth is the point: in the test fixture, four points down
89
+ `strike` give `damage +3`, while the same four points in `strike x2`, `focus`, `sight` give
90
+ `damage +4` plus the `overload` synergy and its action.
91
+
92
+ `gearScore(set, options)` scores `slot -> { stats, level?, rarity? }` as
93
+ `Σ_stat value × weight × (lowerIsBetter ? -1 : 1) + levelWeight × level + rarityScore[rarity]`,
94
+ summed over filled slots, minus `emptySlotPenalty` per expected-but-empty slot; it also returns
95
+ `bySlot`, `byStat`, `average`, `coverage` and `emptySlots`. `compareGearSets` ranks two loadouts.
96
+ The stat term comes from `lootStatContributions`, the same function behind `scoreLootStats` and
97
+ `compareLootItems`, so per-item and whole-loadout scores cannot drift apart.
98
+
99
+ ## Inventory, equipment and weight
100
+
101
+ A carried bag and a stash are two declared inventories, so they hold separate plain state from the
102
+ first frame and persist as `InventorySet.snapshot()`:
103
+
104
+ ```ts
105
+ import { createInventorySet } from "@jgengine/core/inventory/inventoryModel";
106
+ import { canCarry, characterLoad } from "@jgengine/core/inventory/carryLoad";
107
+ import { createEquipment } from "@jgengine/core/inventory/equipment";
108
+
109
+ const items = createInventorySet({ bag: { slots: 24 }, stash: { slots: 60 } }, traits);
110
+ items.move("bag", 3, "stash", 0); // across inventories; a rejected move leaves both untouched
111
+ const saved = items.snapshot(); // detached; JSON-safe; restore() refuses a bad save without mutating
112
+
113
+ const gear = createEquipment(
114
+ [{ id: "weapon", blocks: ["offhand"] }, { id: "offhand" }, { id: "chest", accepts: ["armor"] }],
115
+ (itemId) => baseDefs[registry.get(itemId)?.baseId ?? itemId]?.gear, // rolled item -> its base's gear facts
116
+ { devtoolsName: "gear" },
117
+ );
118
+ gear.equip("chest", "plate"); // { status: "ok", replaced: null, ... }
119
+ gear.equipBest("greatsword"); // first free preferred slot, else the first one it may replace
120
+
121
+ const load = characterLoad({
122
+ carried: [items.state("bag")],
123
+ worn: gear.worn().map((entry) => entry.itemId),
124
+ weightOf: (itemId) => baseDefs[itemId]?.weight ?? 0,
125
+ capacity: 60 + stats.value("carryCapacity"),
126
+ });
127
+ load.moveMultiplier; // 1 unencumbered, decays across the soft band, 0 at capacity
128
+ canCarry(load, 12); // gate a pickup; the overload policy stays the game's
129
+ ```
130
+
131
+ `EquipSlotDef` owns `accepts` (item kinds) and `blocks` (slots it occupies while filled), and
132
+ `EquipItemDef` owns the `slots` an item may occupy plus its `kind`; a filled slot swaps and returns
133
+ the replaced item, so nothing is silently destroyed, and `state()` / `restore()` carry
134
+ `{ slots: [[slotId, itemId]] }`. The stash is simply left out of `carried`, so its contents add no
135
+ weight, and declaring it `tier: "banked"` keeps it out of `partitionOnDeath`'s lost half.
136
+
137
+ For a footprint bag, `fitShapedItem` (`inventory/shapedGrid`) places the first legal spot or names
138
+ why it does not fit — `too-large` with both sizes, `no-space` for a full board, `budget-exceeded` for
139
+ an exhausted search. Stack rules stay in `ItemTraits`: `putItem` fills partial stacks then opens new
140
+ ones up to the max stack, `moveItem` merges with a remainder, and `splitStack(..., limit)` refuses an
141
+ `over-stack` merge instead of breaking the cap.
142
+
143
+ The rendered side is the engine's: `<InventoryGrid inventoryId="bag" />` (`packages/react`) binds a
144
+ live declared inventory with drag, swap, merge and split through the `inventory.move` /
145
+ `inventory.split` commands, and `<EquipmentSlots items={...} />` paints the worn slots from
146
+ `gear.worn()`. What the grid cannot yet do is drag from one inventory to another (bag to stash), so a
147
+ stash transfer goes through `InventorySet.move` behind a button or command.
148
+
31
149
  ## Coherent authored content
32
150
 
33
151
  Keep stable ids and canonical facts for characters, factions, places, and world rules in the game's authored data. Record each chapter's prerequisites, consequences, and which facts it may introduce or change. The game's creative pillars, story voice, content palette, and desired player experience come from its author; generated prose, items, art, and assets must serve those constraints. Reuse constrained generation and validation code across games while each game owns its content and visual identity; do not fill catalogs from default genre kits or mass generic content.
@@ -63,6 +63,14 @@ Route on an editor-baked polygon nav mesh through `createNavRuntime` (`nav/navRu
63
63
 
64
64
  Move many agents across that mesh with `createNavCrowd` (`nav/navCrowd`): it plans corridors, follows the funnel-smoothed waypoints, keeps them apart with `createRvoCrowd` (`ai/rvo`) reciprocal velocity obstacles, bounds path searches to a per-tick time and count budget, and serializes through `snapshot`/`restore`. Give a crowd converging on one spot a `spread` radius so agents fan out (`ai/crowd`'s stable per-id offset) instead of stacking. Avoidance keeps agents inside the mesh's outer bounds; interior walls remain the game's collision layer.
65
65
 
66
+ ### Street traffic
67
+
68
+ Vehicles on roads do not path-find: they follow authored lane spans. `createLaneGraph` (`nav/laneGraph`) resolves those spans — `traffic_lane` paths, or `road` paths from the editor's street bake, split where streets cross — into junctions, crossings with a signal program or first-come right-of-way, and parking spots; `laneGraphFromDocument` reads that straight off the scene document, so editing a road moves its traffic. `createTraffic` (`nav/traffic`) then drives cars on it: IDM car following and braking, one car at a time through each intersection box (only once the exit lane has room, which is what keeps a blocked crossing from wedging the network), signals with phases, lane changes across authored `left`/`right` neighbours, parking that reserves a spot, and `setDemand(count)` for fleet size — the time-of-day curve is the caller's, not the engine's. Cars are posed, not driven: when a player or a chase needs real handling, feed intent into `physics/kinematicVehicle` and let this seam keep the ambient city.
69
+
70
+ ### Heat and police response
71
+
72
+ A crime only counts when a witness holds the suspect. `createPoliceResponse` (`ai/policeResponse`) reads witness facts from `sensor/perception` memory — `memoryOf` per observer, keeping the sighting's `sourceId` and `sourceTick` — and adds heat on the shared `ai/heatSystem` curve, so there is no second sight path and no second decay. The caller's tier table decides what police do at each heat: patrol, investigate, pursue, roadblock, with a pursuer budget per tier. The search area sits on the suspect's last known position, grows while the trail is cold, and a roadblock tier cordons it with sites `policeRoadsFromLaneGraph` reads off the lane graph (`traffic_roadblock` markers projected onto the lane they block, else the graph's intersections). Breaking sight for `escapeSeconds` is what starts the decay; standing in view keeps the heat. Heat, curve, tiers and search shape are caller policy with defaults, `state()`/`restore()` round-trip mid-chase, and the injected generator is the only source of variety.
73
+
66
74
  ### Fallback-seam diagnostics
67
75
 
68
76
  Render seams fall back to placeholders when content is unauthored: default green ground (no environment), primitive capsule/box actors (no `entityModels`/`objectModels` mapping), stylized proxy foliage (no scatter `resolveItem`). These placeholders are sometimes intended, so runtime never changes — but a dev-only, opt-in probe (`@jgengine/core/devtools/fallbackSeams`) tells "unauthored/misconfigured" apart from "intended". The shell arms it via `armFallbackSeams(devtoolsEnabled)`; each seam reports at its choice site (`reportFallbackSeam`), and counts surface through the devtools `fallbacks` probe. Disarmed (production) it is a pure, allocation-free no-op.