@jgengine/core 0.11.0 → 0.13.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 (256) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/ai/populationDirector.d.ts +103 -0
  3. package/dist/ai/populationDirector.js +190 -0
  4. package/dist/ai/pursuit.d.ts +56 -0
  5. package/dist/ai/pursuit.js +53 -0
  6. package/dist/authoring.d.ts +1 -1
  7. package/dist/authoring.js +1 -1
  8. package/dist/combat/effects.d.ts +11 -4
  9. package/dist/combat/effects.js +24 -17
  10. package/dist/combat/magazine.d.ts +13 -0
  11. package/dist/combat/magazine.js +27 -0
  12. package/dist/combat/vfxPresets.d.ts +176 -0
  13. package/dist/combat/vfxPresets.js +74 -0
  14. package/dist/combat/weaponFire.d.ts +114 -0
  15. package/dist/combat/weaponFire.js +96 -0
  16. package/dist/combat.d.ts +5 -2
  17. package/dist/combat.js +4 -1
  18. package/dist/devtools/controls.d.ts +38 -0
  19. package/dist/devtools/controls.js +181 -0
  20. package/dist/devtools/devtools.d.ts +5 -197
  21. package/dist/devtools/devtools.js +38 -774
  22. package/dist/devtools/discover.d.ts +32 -0
  23. package/dist/devtools/discover.js +240 -0
  24. package/dist/devtools/fallbackSeams.d.ts +50 -0
  25. package/dist/devtools/fallbackSeams.js +111 -0
  26. package/dist/devtools/frame.d.ts +15 -0
  27. package/dist/devtools/frame.js +247 -0
  28. package/dist/devtools/logs.d.ts +15 -0
  29. package/dist/devtools/logs.js +86 -0
  30. package/dist/devtools/overrides.d.ts +16 -0
  31. package/dist/devtools/overrides.js +66 -0
  32. package/dist/devtools/types.d.ts +212 -0
  33. package/dist/devtools/types.js +1 -0
  34. package/dist/devtools/urlFlags.d.ts +22 -0
  35. package/dist/devtools/urlFlags.js +62 -0
  36. package/dist/economy/auctionBook.d.ts +122 -0
  37. package/dist/economy/auctionBook.js +213 -0
  38. package/dist/economy/priceHistory.d.ts +44 -0
  39. package/dist/economy/priceHistory.js +89 -0
  40. package/dist/economy/unlockPoints.d.ts +92 -0
  41. package/dist/economy/unlockPoints.js +103 -0
  42. package/dist/editor/commandApply.d.ts +5 -0
  43. package/dist/editor/commandApply.js +755 -0
  44. package/dist/editor/commands.d.ts +48 -3
  45. package/dist/editor/commands.js +40 -754
  46. package/dist/editor/directives.d.ts +94 -0
  47. package/dist/editor/directives.js +206 -0
  48. package/dist/editor/document.d.ts +37 -2
  49. package/dist/editor/document.js +422 -6
  50. package/dist/editor/entityCatalog.d.ts +42 -0
  51. package/dist/editor/entityCatalog.js +101 -0
  52. package/dist/editor/environment.d.ts +74 -0
  53. package/dist/editor/environment.js +169 -0
  54. package/dist/editor/index.d.ts +26 -1
  55. package/dist/editor/index.js +8 -1
  56. package/dist/editor/minimap.d.ts +31 -0
  57. package/dist/editor/minimap.js +73 -0
  58. package/dist/editor/streamer.d.ts +66 -0
  59. package/dist/editor/streamer.js +68 -0
  60. package/dist/editor/types.d.ts +143 -0
  61. package/dist/editor/world.d.ts +136 -0
  62. package/dist/editor/world.js +303 -0
  63. package/dist/game/breeding.d.ts +161 -0
  64. package/dist/game/breeding.js +181 -0
  65. package/dist/game/cameraConfig.d.ts +451 -0
  66. package/dist/game/cameraConfig.js +1 -0
  67. package/dist/game/clipRoles.d.ts +51 -0
  68. package/dist/game/clipRoles.js +142 -0
  69. package/dist/game/defineGame.d.ts +16 -9
  70. package/dist/game/defineGame.js +14 -6
  71. package/dist/game/defineSystem.d.ts +2 -0
  72. package/dist/game/defineSystem.js +2 -0
  73. package/dist/game/events.d.ts +28 -0
  74. package/dist/game/partAnimation.d.ts +78 -0
  75. package/dist/game/partAnimation.js +147 -0
  76. package/dist/game/playableGame.d.ts +72 -440
  77. package/dist/game/playableGame.js +1 -1
  78. package/dist/game/progression.d.ts +35 -0
  79. package/dist/game/progression.js +22 -1
  80. package/dist/game/quest.js +1 -3
  81. package/dist/game/tribe.d.ts +184 -0
  82. package/dist/game/tribe.js +432 -0
  83. package/dist/gameplay.d.ts +3 -1
  84. package/dist/gameplay.js +3 -1
  85. package/dist/inventory/encumbrance.d.ts +69 -0
  86. package/dist/inventory/encumbrance.js +68 -0
  87. package/dist/inventory/inventoryModel.d.ts +24 -8
  88. package/dist/inventory/inventoryModel.js +47 -8
  89. package/dist/item/durability.js +1 -3
  90. package/dist/meta/changelog.d.ts +1 -1
  91. package/dist/meta/changelog.js +48 -1
  92. package/dist/movement/movementModel.d.ts +15 -1
  93. package/dist/movement/movementModel.js +33 -15
  94. package/dist/movement/playerMovement.js +102 -2
  95. package/dist/multiplayer/combatSnapshot.js +13 -8
  96. package/dist/multiplayer/presenceModel.d.ts +2 -0
  97. package/dist/multiplayer/presenceModel.js +8 -0
  98. package/dist/physics/drivableVehicle.d.ts +2 -0
  99. package/dist/physics/drivableVehicle.js +1 -1
  100. package/dist/physics/flightDynamics.d.ts +82 -0
  101. package/dist/physics/flightDynamics.js +165 -0
  102. package/dist/physics/gravityField.d.ts +25 -0
  103. package/dist/physics/gravityField.js +41 -0
  104. package/dist/physics/kinematicVehicle.d.ts +87 -0
  105. package/dist/physics/kinematicVehicle.js +193 -11
  106. package/dist/physics/physicsWorld.d.ts +26 -0
  107. package/dist/physics/physicsWorld.js +109 -117
  108. package/dist/physics/vehicleObstacles.d.ts +44 -0
  109. package/dist/physics/vehicleObstacles.js +115 -0
  110. package/dist/random/rng.d.ts +4 -1
  111. package/dist/random/rng.js +4 -1
  112. package/dist/runtime/adapter.d.ts +13 -0
  113. package/dist/runtime/adapter.js +13 -0
  114. package/dist/runtime/context/combat.d.ts +51 -0
  115. package/dist/runtime/context/combat.js +131 -0
  116. package/dist/runtime/context/combatFx.js +15 -5
  117. package/dist/runtime/context/deathLoot.d.ts +30 -0
  118. package/dist/runtime/context/deathLoot.js +39 -0
  119. package/dist/runtime/context/player.d.ts +48 -0
  120. package/dist/runtime/context/player.js +125 -0
  121. package/dist/runtime/context/scene.d.ts +60 -0
  122. package/dist/runtime/context/scene.js +329 -0
  123. package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
  124. package/dist/runtime/context/snapshotCodecs.js +26 -0
  125. package/dist/runtime/descriptors/baseline.js +26 -3
  126. package/dist/runtime/descriptors/features.d.ts +5 -0
  127. package/dist/runtime/descriptors/features.js +20 -2
  128. package/dist/runtime/gameContext.d.ts +2 -428
  129. package/dist/runtime/gameContext.js +133 -500
  130. package/dist/runtime/gameContextTypes.d.ts +500 -0
  131. package/dist/runtime/gameContextTypes.js +1 -0
  132. package/dist/runtime/headlessRunner.d.ts +17 -2
  133. package/dist/runtime/headlessRunner.js +15 -0
  134. package/dist/runtime/hostPolicy.d.ts +51 -0
  135. package/dist/runtime/hostPolicy.js +68 -0
  136. package/dist/runtime/hostedGameRunner.d.ts +3 -1
  137. package/dist/runtime/hostedGameRunner.js +1 -0
  138. package/dist/runtime/hostedWorldSession.d.ts +3 -1
  139. package/dist/runtime/hostedWorldSession.js +1 -0
  140. package/dist/runtime/worldSnapshot.d.ts +13 -1
  141. package/dist/runtime/worldSnapshot.js +15 -3
  142. package/dist/scene/assetCatalog.d.ts +7 -0
  143. package/dist/scene/assetSpace.js +1 -3
  144. package/dist/scene/authoredTriggers.d.ts +50 -0
  145. package/dist/scene/authoredTriggers.js +114 -0
  146. package/dist/scene/builtinSceneKinds.js +8 -3
  147. package/dist/scene/colliders.d.ts +79 -0
  148. package/dist/scene/colliders.js +170 -0
  149. package/dist/scene/collisionMesh.d.ts +94 -0
  150. package/dist/scene/collisionMesh.js +699 -0
  151. package/dist/scene/companion.d.ts +147 -0
  152. package/dist/scene/companion.js +193 -0
  153. package/dist/scene/entityStats.d.ts +7 -9
  154. package/dist/scene/entityStats.js +19 -15
  155. package/dist/scene/sceneKinds.d.ts +16 -0
  156. package/dist/scene/sceneKinds.js +4 -0
  157. package/dist/scene/sceneRaycast.js +15 -0
  158. package/dist/stats/spawnLevelStats.d.ts +110 -0
  159. package/dist/stats/spawnLevelStats.js +140 -0
  160. package/dist/stats/statModifiers.d.ts +16 -0
  161. package/dist/stats/statModifiers.js +30 -1
  162. package/dist/stats/statPool.d.ts +70 -0
  163. package/dist/stats/statPool.js +62 -0
  164. package/dist/store/changeSignal.d.ts +3 -0
  165. package/dist/store/changeSignal.js +2 -0
  166. package/dist/store/observableKeyedStore.d.ts +2 -0
  167. package/dist/store/observableKeyedStore.js +1 -0
  168. package/dist/survival/decayMeter.js +1 -3
  169. package/dist/survival/regionHealth.js +1 -3
  170. package/dist/ui/hudLayout.d.ts +5 -0
  171. package/dist/ui/hudLayout.js +17 -1
  172. package/dist/ui/panelModel.d.ts +120 -0
  173. package/dist/ui/panelModel.js +168 -0
  174. package/dist/ui.d.ts +1 -0
  175. package/dist/ui.js +1 -0
  176. package/dist/world/authoredEntities.d.ts +29 -0
  177. package/dist/world/authoredEntities.js +29 -0
  178. package/dist/world/authoredObjects.d.ts +15 -1
  179. package/dist/world/authoredObjects.js +13 -1
  180. package/dist/world/authoredSpawn.d.ts +39 -0
  181. package/dist/world/authoredSpawn.js +37 -0
  182. package/dist/world/buildSockets.d.ts +152 -0
  183. package/dist/world/buildSockets.js +188 -0
  184. package/dist/world/buildingLots.d.ts +84 -0
  185. package/dist/world/buildingLots.js +119 -0
  186. package/dist/world/buildings.d.ts +6 -0
  187. package/dist/world/cityBlocks.d.ts +152 -0
  188. package/dist/world/cityBlocks.js +670 -0
  189. package/dist/world/cityContent.d.ts +77 -0
  190. package/dist/world/cityContent.js +261 -0
  191. package/dist/world/cityGenerator.d.ts +41 -0
  192. package/dist/world/cityGenerator.js +58 -0
  193. package/dist/world/cityGeometry.d.ts +114 -0
  194. package/dist/world/cityGeometry.js +573 -0
  195. package/dist/world/cityKind.d.ts +314 -0
  196. package/dist/world/cityKind.js +1578 -0
  197. package/dist/world/environmentSummary.js +57 -1
  198. package/dist/world/features.d.ts +53 -5
  199. package/dist/world/features.js +15 -3
  200. package/dist/world/grassKind.js +2 -2
  201. package/dist/world/markers.d.ts +50 -4
  202. package/dist/world/markers.js +49 -0
  203. package/dist/world/massing.js +1 -1
  204. package/dist/world/minimapBake.d.ts +78 -0
  205. package/dist/world/minimapBake.js +208 -0
  206. package/dist/world/minimapTrack.d.ts +17 -0
  207. package/dist/world/minimapTrack.js +22 -0
  208. package/dist/world/place.d.ts +166 -0
  209. package/dist/world/place.js +96 -0
  210. package/dist/world/placeAsset.js +3 -1
  211. package/dist/world/poleLineKind.d.ts +40 -0
  212. package/dist/world/poleLineKind.js +97 -0
  213. package/dist/world/polyline.js +9 -2
  214. package/dist/world/resourceNode.d.ts +109 -0
  215. package/dist/world/resourceNode.js +140 -0
  216. package/dist/world/roads.d.ts +19 -2
  217. package/dist/world/roads.js +60 -3
  218. package/dist/world/scatter.js +2 -20
  219. package/dist/world/streetGenerator.d.ts +159 -0
  220. package/dist/world/streetGenerator.js +684 -0
  221. package/dist/world/streets.d.ts +103 -1
  222. package/dist/world/streets.js +273 -0
  223. package/dist/world/terraform.js +58 -88
  224. package/dist/world/terrain.d.ts +65 -1
  225. package/dist/world/terrain.js +119 -2
  226. package/dist/world.d.ts +14 -5
  227. package/dist/world.js +12 -3
  228. package/package.json +1 -1
  229. package/skills/game-design/SKILL.md +28 -0
  230. package/skills/game-design/agents/openai.yaml +4 -0
  231. package/skills/game-design/references/application-playbook.md +170 -0
  232. package/skills/game-design/references/field-guide.md +288 -0
  233. package/skills/jgengine/SKILL.md +8 -4
  234. package/skills/jgengine/api.md +182 -81
  235. package/skills/jgengine/capabilities.md +104 -2
  236. package/skills/jgengine/recipes/minimal-game.md +88 -0
  237. package/skills/jgengine-combat/SKILL.md +19 -1
  238. package/skills/jgengine-combat/api.md +75 -2
  239. package/skills/jgengine-combat/capabilities.md +61 -1
  240. package/skills/jgengine-combat/recipes/portable-damage-effects.md +197 -0
  241. package/skills/jgengine-combat/recipes/portable-runtime-state.md +154 -0
  242. package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
  243. package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +156 -0
  244. package/skills/jgengine-combat/recipes/visible-attacks.md +81 -0
  245. package/skills/jgengine-combat/reference.md +33 -4
  246. package/skills/jgengine-gameplay/SKILL.md +8 -0
  247. package/skills/jgengine-gameplay/api.md +182 -26
  248. package/skills/jgengine-gameplay/capabilities.md +96 -2
  249. package/skills/jgengine-gameplay/recipes/portable-xp-leveling.md +168 -0
  250. package/skills/jgengine-gameplay/reference-systems.md +10 -5
  251. package/skills/jgengine-world/SKILL.md +13 -0
  252. package/skills/jgengine-world/api.md +453 -21
  253. package/skills/jgengine-world/capabilities.md +112 -2
  254. package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
  255. package/skills/jgengine-world/reference.md +43 -4
  256. package/skills/jgengine/reference-cartridge.md +0 -72
@@ -151,6 +151,21 @@
151
151
  - `JsonDataSourceOptions` (type): type JsonDataSourceOptions = DataSourceOptions & Omit<FetchJsonOptions, "signal"> — ⚠ undocumented
152
152
  - `createJsonDataSource` (function): function createJsonDataSource<T>(url: string, options: JsonDataSourceOptions = {}): DataSource<T> — ⚠ undocumented
153
153
 
154
+ ## @jgengine/core/economy/auctionBook
155
+
156
+ - `Auction` (interface): interface Auction — One live timed auction in an {@link AuctionBook}: an item stack under open bidding until it closes.
157
+ - `AuctionBook` (interface): interface AuctionBook — A timed-bid auction marketplace plus the per-player collection boxes behind it. See {@link createAuctionBook}.
158
+ - `AuctionBookConfig` (interface): interface AuctionBookConfig — Tunables for {@link createAuctionBook} — per-seller auction cap, run length, house cut, and the anti-snipe window: a bid landing within `antiSnipeWindowSeconds` of the close pushes the close out so it ends at least `antiSnipeExtensionSeconds` after that bid.
159
+ - `AuctionSettlement` (type): type AuctionSettlement = | { status: "sold"; auction: Auction; winnerId: string; price: number; houseCut: number; sellerProceeds: number; } | { status: "returned"; auction: Auction } — How one closed auction resolved during {@link AuctionBook.settleExpired}.
160
+ - `BidReason` (type): type BidReason = | "not-found" | "ended" | "own-auction" | "already-leading" | "bid-too-low" | "invalid-amount" | "no-buyout" — Why {@link AuctionBook.bid} refused a bid.
161
+ - `BidResult` (type): type BidResult = | { status: "ok"; auction: Auction; escrowed: number; won: boolean } | { status: "rejected"; reason: BidReason } — Result of {@link AuctionBook.bid}. On `ok` the caller must escrow `escrowed` from the bidder's wallet; any previously escrowed leading bid has already been refunded to that bidder's collection box. `won` is true when the bid met the buyout price and settled the auction immediately.
162
+ - `CancelAuctionResult` (type): type CancelAuctionResult = | { status: "ok"; auction: Auction } | { status: "rejected"; reason: "not-found" | "not-owner" | "has-bids" } — Result of {@link AuctionBook.cancel}: a bid-on auction can no longer be withdrawn.
163
+ - `HighBid` (interface): interface HighBid — The current leading bid on an {@link Auction}.
164
+ - `PostAuctionInput` (interface): interface PostAuctionInput — Input to {@link AuctionBook.post} — the goods, opening terms, and current game-time.
165
+ - `PostAuctionReason` (type): type PostAuctionReason = | "invalid-count" | "invalid-price" | "invalid-increment" | "invalid-buyout" | "auction-cap-reached" — Why {@link AuctionBook.post} refused an auction.
166
+ - `PostAuctionResult` (type): type PostAuctionResult = | { status: "ok"; auction: Auction } | { status: "rejected"; reason: PostAuctionReason } — Result of {@link AuctionBook.post}.
167
+ - `createAuctionBook` (function): function createAuctionBook(config: AuctionBookConfig): AuctionBook — Timed-bid auctions in the WoW/BDO auction-house mold: post an item with a start price, minimum increment, and optional buyout; bids escrow currency, outbid players are refunded into their collection box, bids near the close extend it (anti-snipe), and settlement pays the seller minus the house cut while the item lands in the winner's collection box. Unsold auctions return the goods to the seller's box. Wallet and inventory movement is the caller's job (mirrors `economy/listingBook`) — this primitive owns the auction lifecycle and the escrowed collection-box bookkeeping behind it.
168
+
154
169
  ## @jgengine/core/economy/currency
155
170
 
156
171
  - `CurrencyAdjustment` (type): type CurrencyAdjustment = | { success: true; newBalance: number; appliedDelta: number } | { success: false; reason: string } — ⚠ undocumented
@@ -171,6 +186,14 @@
171
186
  - `PostListingResult` (type): type PostListingResult = | { status: "ok"; listing: Listing } | { status: "rejected"; reason: PostListingReason } — Result of {@link ListingBook.post}.
172
187
  - `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.
173
188
 
189
+ ## @jgengine/core/economy/priceHistory
190
+
191
+ - `PriceHistory` (interface): interface PriceHistory — A bounded rolling record of completed sales per item. See {@link createPriceHistory}.
192
+ - `PriceHistoryConfig` (interface): interface PriceHistoryConfig — Tunables for {@link createPriceHistory} — the per-item sample cap bounds memory; the optional window drops samples older than `windowSeconds` whenever the history is written or read with a newer timestamp.
193
+ - `PriceStats` (interface): interface PriceStats — Aggregated market stats for one item over the retained samples.
194
+ - `SaleRecord` (interface): interface SaleRecord — One completed sale recorded into a {@link PriceHistory}.
195
+ - `createPriceHistory` (function): function createPriceHistory(config: PriceHistoryConfig): PriceHistory — The "market price" readout behind every auction house: a bounded rolling record of completed sales per item, aggregated into min/max/volume-weighted-average/latest unit-price stats so games can show current value and recent trends. Memory is bounded by `maxSamplesPerItem` (oldest samples drop first) and optionally by a sliding time window; timestamps are injected by the caller so the history stays deterministic.
196
+
174
197
  ## @jgengine/core/economy/resourceLedger
175
198
 
176
199
  - `AdvanceOptions` (interface): interface AdvanceOptions — Per-advance settings: the policy pipeline, caller context, rounding, and safety bounds.
@@ -217,6 +240,16 @@
217
240
  - `TechState` (type): type TechState = UnlockState — ⚠ undocumented
218
241
  - `TechTree` (interface): interface TechTree — ⚠ undocumented
219
242
 
243
+ ## @jgengine/core/economy/unlockPoints
244
+
245
+ - `SpendOptions` (interface): interface SpendOptions — Options for {@link UnlockPoints.spend}.
246
+ - `SpendRejection` (type): type SpendRejection = | "invalid-cost" | "already-unlocked" | "missing-prerequisites" | "insufficient-points" — Why a {@link UnlockPoints.spend} was refused; the pool is left untouched on any rejection.
247
+ - `SpendResult` (type): type SpendResult = { ok: true; available: number } | { ok: false; reason: SpendRejection } — Result of {@link UnlockPoints.spend}: the new `available` balance on success, else a reason.
248
+ - `UnlockPoints` (interface): interface UnlockPoints — A spendable unlock-point economy: a pool you earn (per level or by direct grant) and spend to unlock nodes, with per-unlock refunds and a full respec.
249
+ - `UnlockPointsConfig` (interface): interface UnlockPointsConfig — Configuration for {@link createUnlockPoints}.
250
+ - `UnlockPointsState` (interface): interface UnlockPointsState — Plain, JSON-serializable snapshot of a point pool. `earned` is the lifetime total ever banked; every committed unlock lives in `unlocked` as `id → cost paid`. `spent` and `available` are always derived (`spent = Σ unlocked`, `available = earned − spent`) so the two can never drift out of sync across a serialize/hydrate round-trip.
251
+ - `createUnlockPoints` (function): function createUnlockPoints(config: UnlockPointsConfig = {}): UnlockPoints — Spendable unlock-point economy — the seam between leveling and a tech tree. Points are earned per level (via a configurable, possibly non-uniform grant curve) or granted directly, then spent to unlock nodes; spending can defer prerequisite checks to an injected predicate so it composes with `economy/techTree` (or any gate) without depending on it. Individual unlocks refund, and `respec` fully refunds a build while preserving lifetime earned points. All state is plain and JSON round-trips.
252
+
220
253
  ## @jgengine/core/economy/wallet
221
254
 
222
255
  - `ChargeOptions` (interface): interface ChargeOptions — Options for {@link charge}/{@link chargeAll}: opt one call into overdraft debt via `overdraft`.
@@ -231,6 +264,49 @@
231
264
  - `grant` (function): function grant(state: WalletState, currency: string, amount: number): WalletState — ⚠ undocumented
232
265
  - `isOverdrawn` (function): function isOverdrawn(state: WalletState, currency: string): boolean — True once `balance(state, currency)` has gone negative under an overdraft-enabled charge.
233
266
 
267
+ ## @jgengine/core/game/breeding
268
+
269
+ - `BreedConfig` (interface): interface BreedConfig — Tuning for {@link breedOffspring}. Every field has an ARK-style default; all are optional.
270
+ - `BreedResult` (interface): interface BreedResult — Result of {@link breedOffspring}: the new genome plus the mutations that produced it.
271
+ - `Genome` (interface): interface Genome — A creature's heritable genome. `stats` are the inherited/mutated point values; `mutationCount` and `colorMutationCount` are the lineage counters that carry across generations and drive the mutation soft cap. Fully serializable — no methods or closures.
272
+ - `ImprintBonusConfig` (interface): interface ImprintBonusConfig — Options controlling how an imprint bonus is applied.
273
+ - `IncubationConfig` (interface): interface IncubationConfig — Temperature gate for {@link tickIncubation}.
274
+ - `IncubationState` (interface): interface IncubationState — Serializable incubation/gestation state advanced by {@link tickIncubation}.
275
+ - `MaturationStage` (interface): interface MaturationStage — A maturation stage keyed by the elapsed fraction (0..1) at which it begins.
276
+ - `MutationEvent` (interface): interface MutationEvent — One mutation that occurred while breeding a single offspring.
277
+ - `MutationSoftCap` (interface): interface MutationSoftCap — Soft-cap rule for mutation eligibility. When a parent's `mutationCount` exceeds `threshold` its lineage is considered "capped"; the per-roll mutation chance is then either zeroed (`mode: "block"`) or scaled by `reducedFactor` (`mode: "reduce"`). `scope` decides whether it takes `both` parents capped, or `either` one, to trigger.
278
+ - `StatBlock` (type): type StatBlock = Record<string, number> — Deterministic creature breeding & genetics — a genre-agnostic engine primitive for taming/husbandry games. Everything here operates over plain serializable records so a genome round-trips through save/load and multiplayer sync, and every random decision is driven by an injected `rng: () => number` (values in `[0, 1)`) so identical inputs and an identical rng stream always produce the identical offspring.
279
+ - `applyImprintBonus` (function): function applyImprintBonus(stats: StatBlock, imprint: number, config: ImprintBonusConfig = {}, options: { asOwner?: boolean } = {}): StatBlock — Apply the imprint rearing bonus to a stat block. Every stat is scaled by `1 + statBonusAtFull * imprint`; when `asOwner` is set, the imprinting handler additionally gets `1 + handlerBonusAtFull * imprint`. `imprint` is a 0..1 fraction (clamped) and scales both bonuses linearly, so half-imprint yields half the bonus. Returns a fresh stat block.
280
+ - `breedOffspring` (function): function breedOffspring(parentA: Genome, parentB: Genome, rng: () => number, config: BreedConfig = {}): BreedResult — Breed one offspring from two parents. For each stat present in either parent (iterated in sorted id order for a stable rng stream) the offspring independently inherits the higher parent's value with probability `higherStatChance` (default `0.55`), else the lower — a per-stat coin flip, so a strong parent does not guarantee a strong child. Then up to `mutationRolls` mutation rolls fire at the soft-capped chance; each success adds `mutationDelta` to a random stat, bumps the heritable `mutationCount`, and is flagged cosmetic with probability `colorMutationChance`. Lineage counters carry as the max of the two parents plus the new mutations, keeping them monotonic and heritable.
281
+ - `imprintIncrementPerRequest` (function): function imprintIncrementPerRequest(requestCount: number): number — Imprint gained per fulfilled care request when `requestCount` requests are scheduled over the maturation window: `1 / requestCount`, so fulfilling every request reaches full (1.0) imprint. Returns `0` for a non-positive request count.
282
+ - `incubationViable` (function): function incubationViable(state: IncubationState): boolean — True while the incubation still has health to hatch.
283
+ - `maturationStage` (function): function maturationStage(elapsed: number, duration: number, stages: readonly MaturationStage[]): string — Resolve the maturation stage for an elapsed time against a duration. Computes the clamped `elapsed / duration` fraction and returns the id of the last stage whose `at` threshold has been reached (boundaries inclusive). Stages need not be pre-sorted. Returns `""` when no stage qualifies (e.g. an empty list or all thresholds above the current fraction).
284
+ - `tickIncubation` (function): function tickIncubation(state: IncubationState, temperature: number, dt: number, config: IncubationConfig): IncubationState — Advance incubation one tick. While `temperature` is within `[minTemp, maxTemp]` the embryo makes progress (`elapsed += dt`) and keeps its health; outside the range it makes no progress and loses `healthLossPerTick * dt` health (clamped at `0`). Pure — returns a new state, never mutates the input.
285
+
286
+ ## @jgengine/core/game/cameraConfig
287
+
288
+ - `CAMERA_FRUSTUM_DEFAULTS` (const): const CAMERA_FRUSTUM_DEFAULTS: { readonly fov: 55; readonly near: 0.1; readonly far: 300; readonly zoom: 50; } — ⚠ undocumented
289
+ - `CameraFollowState` (interface): interface CameraFollowState — ⚠ undocumented
290
+ - `CameraKeyframe` (interface): interface CameraKeyframe — One stop on a scripted camera path (#29).
291
+ - `CameraProjection` (type): type CameraProjection = "perspective" | "orthographic" — Canvas camera projection. "orthographic" renders a flat 2D-style view (side-scrollers, falling-block puzzles) — pair with `rig: "sideScroll"`; default "perspective".
292
+ - `CameraRigKind` (type): type CameraRigKind = | "orbit" | "first" | "topDown" | "rts" | "shoulder" | "lockOn" | "chase" | "observer" | "turntable" | "sideScroll" | "inspection" | "none" — Which camera rig the shell mounts. Every rig accepts `followEntityId: null` (avatar-less games — city-builders, card games, auto-battlers — still get a camera). Rigs are tuned through their config block below, never by writing camera positions from `onTick`. - `orbit` — third-person chase (the historical default; `perspective: "third"`). - `first` — pointer-lock mouse-look (`perspective: "first"`). - `topDown` — fixed height/pitch/yaw with decoupled follow (ARPG iso, top-down). - `rts` — free-pan / edge-scroll / rotate / zoom, optional follow. - `shoulder` — over-the-shoulder with ADS transition + shoulder swap. - `lockOn` — yaw bound to the player→target vector; move axis becomes strafe. - `chase` — speed-reactive vehicle chase (speed→FOV, spring arm, shake) + cockpit/hood/rear views. - `observer` — detached spectator/photo cam bound to any entity or fixed point; never reads player input. - `turntable` — slow auto-orbit of a fixed point: a rotating display stand for a scene. The friendly, flat spelling of `observer`'s point-orbit mode; providing `camera.turntable` selects it without an explicit `rig`. - `sideScroll` — fixed lateral follow (2.5D platformer/beat-'em-up side view); reads no player input. - `inspection` — model-viewer / editor rig (#207.7, #866): middle-drag pan, right-drag orbit, scroll zoom toward a configurable anchor; orbits a fixed point, reads no player/entity input. - `none` — no camera rig is mounted; use for HUD-only presentations or a game that manages its own camera.
293
+ - `CameraShakeConfig` (interface): interface CameraShakeConfig — Camera-shake / trauma defaults (#28). Any rig reads the trauma channel; game systems feed it via `cameraShake()`.
294
+ - `ChaseCameraConfig` (interface): interface ChaseCameraConfig — Speed-reactive vehicle chase rig (#27) — speed→FOV, spring arm, procedural shake, interior views.
295
+ - `ChaseView` (type): type ChaseView = "chase" | "cockpit" | "hood" | "rear" — Fixed interior view for the chase rig (#27).
296
+ - `CinematicCameraConfig` (interface): interface CinematicCameraConfig — Scripted keyframe / path player (#29). When set it overrides the active rig.
297
+ - `FirstPersonCameraConfig` (interface): interface FirstPersonCameraConfig — ⚠ undocumented
298
+ - `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.
299
+ - `InspectionCameraConfig` (interface): interface InspectionCameraConfig — Model-viewer / inspection rig (#207.7) — orbit + pan + anchored zoom around a fixed point, never reads player input.
300
+ - `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.
301
+ - `LockOnCameraConfig` (interface): interface LockOnCameraConfig — Lock-on / strafe rig (#26) — yaw bound to player→target, move axis becomes strafe.
302
+ - `ObserverCameraConfig` (interface): interface ObserverCameraConfig — Detached spectator/photo cam (#120) — binds to any entity or fixed point, never reads player input.
303
+ - `PlayerFovConfig` (interface): interface PlayerFovConfig — Player-facing FOV preference applied across every perspective camera rig. Orthographic projections ignore it.
304
+ - `RtsCameraConfig` (interface): interface RtsCameraConfig extends TopDownCameraConfig — Free-pan / edge-scroll RTS rig (#24) — pan/rotate/zoom independent of any avatar.
305
+ - `ShoulderCameraConfig` (interface): interface ShoulderCameraConfig — Over-the-shoulder combat rig (#25) — offset, ADS, shoulder swap, decoupled reticle.
306
+ - `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.
307
+ - `TopDownCameraConfig` (interface): interface TopDownCameraConfig — Fixed top-down / isometric rig (#23) — height/pitch/yaw + decoupled follow.
308
+ - `TurntableCameraConfig` (interface): interface TurntableCameraConfig — Turntable / showcase rig — slowly auto-orbits a fixed world point (no player input), the way a museum turntable rotates an object on display. A flat, self-describing spelling of `observer`'s point-orbit mode: `target` names the point directly instead of `bind: { kind: "point", position }`. Set `camera.turntable` and the rig is inferred — you don't also write `rig`.
309
+
234
310
  ## @jgengine/core/game/chat
235
311
 
236
312
  - `Chat` (interface): interface Chat — ⚠ undocumented
@@ -260,6 +336,16 @@
260
336
  - `createChatFilter` (function): function createChatFilter(config: ChatFilterConfig): ChatFilter — ⚠ undocumented
261
337
  - `normalizeChatText` (function): function normalizeChatText(text: string): string — ⚠ undocumented
262
338
 
339
+ ## @jgengine/core/game/clipRoles
340
+
341
+ - `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.
342
+ - `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.
343
+ - `DEFAULT_CLIP_ROLE_TABLE` (const): const DEFAULT_CLIP_ROLE_TABLE: ClipRoleTable — Default vocabulary covering KayKit, Quaternius, and Mixamo-style clip naming.
344
+ - `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).
345
+ - `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.
346
+ - `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.
347
+ - `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.
348
+
263
349
  ## @jgengine/core/game/connectedPlayers
264
350
 
265
351
  - `ConnectedPlayer` (interface): interface ConnectedPlayer — A player currently joined to a hosted world — the unit a shared-world loop iterates instead of `ctx.player`. Frozen by the registry (see {@link ConnectedPlayers.get}); fields are `readonly` so a caller can't edit its own copy and assume the change stuck.
@@ -283,17 +369,17 @@
283
369
 
284
370
  ## @jgengine/core/game/defineGame
285
371
 
286
- - `GameDefinition` (interface): interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> — Fully-resolved game description produced by {@link defineGame} — assets, scene, and opted-in subsystems.
287
- - `GameDefinitionConfig` (type): type GameDefinitionConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> = Omit<GameDefinition<TAssetRef, TMultiplayer>, "scene" | "assets"> & { assets?: AssetCatalog<TAssetRef>; } — Input to {@link defineGame} — a `GameDefinition` with `scene` derived and `assets` optional.
372
+ - `GameDefinition` (interface): interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> — Fully-resolved game description produced by {@link defineGameDefinition} — assets, scene, and opted-in subsystems.
373
+ - `GameDefinitionConfig` (type): type GameDefinitionConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> = Omit<GameDefinition<TAssetRef, TMultiplayer>, "scene" | "assets"> & { assets?: AssetCatalog<TAssetRef>; } — Input to {@link defineGameDefinition} — a `GameDefinition` with `scene` derived and `assets` optional.
288
374
  - `GameFeatures` (interface): interface GameFeatures — Opt-in `ctx.game.*` subsystems. Absent = off: the game doesn't carry (or expose) it, and `ctx.game.<name>` is `undefined`. Present (`true`) builds it. The universal base — `commands`, `events`, `store`, `feed` — is always on and not listed here. This is what keeps core genre-agnostic: a puzzle game isn't handed an MMO's leaderboard/roster/turn plumbing it never asked for.
289
375
  - `GameLoop` (interface): interface GameLoop<TContext = unknown> — Lifecycle hooks a game implements to drive init, per-tick simulation, and player join/leave.
290
376
  - `GameServerConfig` (type): type GameServerConfig = "persistent" | { mode: string; [key: string]: unknown } — Hosting mode for a game's multiplayer server: `"persistent"`, or a custom mode with its own options.
291
377
  - `InventoryDeclaration` (interface): interface InventoryDeclaration — Shape of one named inventory a game declares — slot count, accepted item types, HUD binding.
292
378
  - `LifecycleConfig` (interface): interface LifecycleConfig<TState = unknown> — Declarative start/restart run lifecycle: the state transitions a game's run phase every genre repeats (title screen → live run → live run → title screen again), expressed as pure functions over one typed {@link StoreHandle} slot instead of hand-rolled `commands.define("start"/"restart")` glue that re-derives phase after every mutation. `start`/`restart` receive the store's own value type — the store's `TState`, never `ctx.game.store.get(key) as T` — and return the next value; the runtime writes it back and derives {@link GamePhase} from it via `phaseOf` in one place, so every adopting game gets identical, correct phase-sync for free.
293
379
  - `LoopPlayer` (interface): interface LoopPlayer — Identity of a player joining or leaving a hosted world — passed to the multiplayer loop hooks.
294
- - `PersistConfig` (interface): interface PersistConfig — Tunes offline whole-world save (`defineGame({ persist })`). Defaults: continuous `autosave` to `localStorage`, one slot, no version.
380
+ - `PersistConfig` (interface): interface PersistConfig — Tunes offline whole-world save (`defineGameDefinition({ persist })`). Offline games already autosave to `localStorage` by default — pass this only to tune it (or `persist: false` to opt out). Defaults: continuous `autosave` to `localStorage`, one slot, no version.
295
381
  - `PhysicsConfig` (interface): interface PhysicsConfig — World gravity and jump tuning, plus scene-object collision opt-ins, for the game's physics step.
296
- - `defineGame` (function): function defineGame<TAssetRef extends ModelAssetRef, TMultiplayer>(config: GameDefinitionConfig<TAssetRef, TMultiplayer>): GameDefinition<TAssetRef, TMultiplayer> — Task-first entry point for authoring a game: fills in `scene` and default `assets`, validates `name`, OR-merges `features` from installed systems, and composes `loop` from `systems` + any classic hooks.
382
+ - `defineGameDefinition` (function): function defineGameDefinition<TAssetRef extends ModelAssetRef, TMultiplayer>(config: GameDefinitionConfig<TAssetRef, TMultiplayer>): GameDefinition<TAssetRef, TMultiplayer> — Host-level constructor for a bare `GameDefinition`: fills in `scene` and default `assets`, validates `name`, OR-merges `features` from installed systems, and composes `loop` from `systems` + any classic hooks. Games author through `defineGame` from `@jgengine/shell/defineGame`, which calls this and adds the presentation layer; call this directly only from headless hosts that mount no shell.
297
383
 
298
384
  ## @jgengine/core/game/defineSystem
299
385
 
@@ -311,6 +397,9 @@
311
397
 
312
398
  ## @jgengine/core/game/events
313
399
 
400
+ - `AudioLoopSetEvent` (interface): interface AudioLoopSetEvent — Live-update the retained loop `id`: `rate` re-pitches it (1 = authored, the shell clamps to 0.25–4), `gain` rescales its volume (0–1), and `at` repositions its emitter. Emitted every tick to track a live signal (RPM, tire slip); the shell smooths rate/gain and ignores an unknown `id` (#1051).
401
+ - `AudioLoopStartEvent` (interface): interface AudioLoopStartEvent — Start (or idempotently keep) the retained, id-keyed audio loop `id` from catalog `sound`, optionally anchored at world `at`. Restarting with the same `sound` does not restart the source (no click); a different `sound` replaces it. Drives RPM-pitched engine loops and slip-scaled tire squeal (#1051).
402
+ - `AudioLoopStopEvent` (interface): interface AudioLoopStopEvent — Stop and dispose the retained loop `id`; an unknown `id` is ignored (#1051).
314
403
  - `AudioMusicEvent` (interface): interface AudioMusicEvent — Crossfade the procedural soundtrack to `theme` (null fades out), optionally transposing the incoming theme by `transpose` semitones.
315
404
  - `AudioPlayEvent` (interface): interface AudioPlayEvent — ⚠ undocumented
316
405
  - `AudioResumeEvent` (type): type AudioResumeEvent = Record<string, never> — Request that the shell's audio engine resume its (browser-gesture-suspended) context; carries no payload.
@@ -477,6 +566,17 @@
477
566
  - `resolveSelection` (function): function resolveSelection(base: Readonly<Record<string, number>>, registry: LayerRegistry, ids: LayerSelection): { readonly snapshot: ParamSnapshot; readonly unknown: readonly string[] } — Reproducible session setup in one call: resolve a saved {@link LayerSelection} through a registry, fold the found layers over `base`, and return the snapshot alongside any unknown ids. The same base + registry + selection always yields the same snapshot.
478
567
  - `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.
479
568
 
569
+ ## @jgengine/core/game/partAnimation
570
+
571
+ - `PartMotionInput` (interface): interface PartMotionInput — Live signals a driver feeds the samplers each frame. All values are render-side only.
572
+ - `PartMotionParams` (interface): interface PartMotionParams — Tuning for {@link samplePartPose}/{@link sampleBodyPose}; every field has a sensible default.
573
+ - `PartPose` (interface): interface PartPose — Sampled offsets to add onto a part's authored transform (or the character root). `scale` is multiplicative (1 = unchanged).
574
+ - `PartRole` (type): type PartRole = "head" | "arm.l" | "arm.r" | "leg.l" | "leg.r" | "tail" | "wing.l" | "wing.r" — Procedural animation for part-composed (rig-less) models — characters kit-bashed from primitives/`ModelPart`s with no skeleton or clips. Pure, deterministic curve sampling: the same (time, speed, phase, flinch, death) input always yields the same pose, so replays and multi-client renders stay in lockstep and the hot path allocates nothing when an `out` pose is reused.
575
+ - `createPartPose` (function): function createPartPose(): PartPose — A reusable identity pose (zero offsets, unit scale).
576
+ - `partMotionPhase` (function): function partMotionPhase(id: string): number — Deterministic per-character stride phase in [0, 1) from an id (e.g. the entity instance id), so a crowd of identical part-composed characters doesn't animate in lockstep.
577
+ - `sampleBodyPose` (function): function sampleBodyPose(input: PartMotionInput, params?: PartMotionParams, out: PartPose = createPartPose()): PartPose — Sample the character-root pose: walk bob, idle breathe/sway, backward hit flinch, and death — a sideways topple by default, or a flatten-out splat with `deathStyle: "splat"`. With `squashAmp > 0` the body also squashes and stretches (footfall squash, jelly idle breathe, flinch squash pulse), volume-conserving. A driver applies this to the group wrapping the whole composition (base model plus parts) — limbs ride along, as they would on a real torso.
578
+ - `samplePartPose` (function): function samplePartPose(role: PartRole, input: PartMotionInput, params?: PartMotionParams, out: PartPose = createPartPose()): PartPose — Sample one part's pose offset by role: counter-phase leg/arm swing driven by speed, a head counter-sway, tail wag, and wing flap. Offsets are added onto the part's authored position/rotation. Motion fades to zero as `death` ramps so the topple reads as one piece.
579
+
480
580
  ## @jgengine/core/game/ping
481
581
 
482
582
  - `DEFAULT_PING_CATEGORIES` (const): const DEFAULT_PING_CATEGORIES: Record<PingCategory, PingCategoryDef> — Content-agnostic default ping wheel: enemy / loot / location / danger.
@@ -510,7 +610,7 @@
510
610
  - `DirectionalLightingConfig` (interface): interface DirectionalLightingConfig — ⚠ undocumented
511
611
  - `EntitySpriteConfig` (interface): interface EntitySpriteConfig — ⚠ undocumented
512
612
  - `FirstPersonCameraConfig` (interface): interface FirstPersonCameraConfig — ⚠ undocumented
513
- - `GameCameraConfig` (interface): interface GameCameraConfig — ⚠ undocumented
613
+ - `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.
514
614
  - `GameCaptureConfig` (interface): interface GameCaptureConfig — How a screenshot host reaches live gameplay in this game — the data behind `shoot --mode play`.
515
615
  - `HemisphereLightingConfig` (interface): interface HemisphereLightingConfig — ⚠ undocumented
516
616
  - `InspectionCameraConfig` (interface): interface InspectionCameraConfig — Model-viewer / inspection rig (#207.7) — orbit + pan + anchored zoom around a fixed point, never reads player input.
@@ -531,6 +631,7 @@
531
631
  - `PlayerFovConfig` (interface): interface PlayerFovConfig — Player-facing FOV preference applied across every perspective camera rig. Orthographic projections ignore it.
532
632
  - `PlayerMovementConfig` (interface): interface PlayerMovementConfig — Movement-control levers for the shell-driven local player walk controller.
533
633
  - `PointerConfig` (interface): interface PointerConfig — ⚠ undocumented
634
+ - `PresentationEffectsConfig` (interface): interface PresentationEffectsConfig — Per-channel combat presentation toggles for the 3D shell canvas. Missing keys default to enabled when the parent `presentationEffects` is an object.
534
635
  - `RtsCameraConfig` (interface): interface RtsCameraConfig extends TopDownCameraConfig — Free-pan / edge-scroll RTS rig (#24) — pan/rotate/zoom independent of any avatar.
535
636
  - `ShoulderCameraConfig` (interface): interface ShoulderCameraConfig — Over-the-shoulder combat rig (#25) — offset, ADS, shoulder swap, decoupled reticle.
536
637
  - `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.
@@ -543,16 +644,16 @@
543
644
 
544
645
  ## @jgengine/core/game/progression
545
646
 
546
- - `Curve` (type): type Curve = CurveDef & CurveShape — ⚠ undocumented
547
- - `CurveDef` (type): type CurveDef = | { kind: "const"; value: number } | { kind: "linear"; base: number; per: number } | { kind: "power"; base: number; exponent: number } | { kind: "geometric"; base: number; ratio: number } | { kind: "steps"; values: number[] } | { kind: "piecewise"; points: [number, number][] } — ⚠ undocumented
548
- - `CurveShape` (interface): interface CurveShape — ⚠ undocumented
549
- - `LevelProgress` (interface): interface LevelProgress — ⚠ undocumented
550
- - `LevelingConfig` (interface): interface LevelingConfig — ⚠ undocumented
551
- - `LevelingStatAccess` (interface): interface LevelingStatAccess — ⚠ undocumented
552
- - `LevelingTrack` (interface): interface LevelingTrack — ⚠ undocumented
553
- - `curve` (function): function curve(spec: Curve): (x: number) => number — ⚠ undocumented
554
- - `evalCurve` (function): function evalCurve(spec: Curve, x: number): number — ⚠ undocumented
555
- - `leveling` (function): function leveling(config: LevelingConfig): LevelingTrack — ⚠ undocumented
647
+ - `Curve` (type): type Curve = CurveDef & CurveShape — A fully specified progression curve — a {@link CurveDef} growth shape plus optional {@link CurveShape} rounding/clamp.
648
+ - `CurveDef` (type): type CurveDef = | { kind: "const"; value: number } | { kind: "linear"; base: number; per: number } | { kind: "power"; base: number; exponent: number } | { kind: "geometric"; base: number; ratio: number } | { kind: "steps"; values: number[] } | { kind: "piecewise"; points: [number, number][] } — The shape of a progression curve before rounding/clamping: a constant, a linear ramp, a `power` or `geometric` growth, discrete `steps`, or a `piecewise` interpolation between explicit points. Feed it to {@link evalCurve}.
649
+ - `CurveShape` (interface): interface CurveShape — Post-evaluation adjustments applied to a curve's raw output: rounding mode and min/max clamp.
650
+ - `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.
651
+ - `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).
652
+ - `LevelingStatAccess` (interface): interface LevelingStatAccess — Adapter a {@link LevelingTrack} uses to read and write a user's level/xp stats, so the track stays decoupled from any concrete stats store.
653
+ - `LevelingTrack` (interface): interface LevelingTrack — A resolved leveling track: the immutable `maxLevel`/`startLevel`, the `xpForLevel` threshold lookup, a pure `resolve`, and `grantXp` which writes back through a {@link LevelingStatAccess} and fires `onLevelUp` once for every reached level in ascending order.
654
+ - `curve` (function): function curve(spec: Curve): (x: number) => number — Bind a {@link Curve} into a reusable `(x) => value` evaluator — a partial application of {@link evalCurve}.
655
+ - `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.
656
+ - `leveling` (function): function leveling(config: LevelingConfig): LevelingTrack — Build a leveling track from an xp-per-level {@link Curve} and a max level. The returned {@link LevelingTrack} resolves how many levels a given xp total earns (rolling over surplus xp, capping at `maxLevel`) and, via `grantXp`, writes the new level/xp back through a {@link LevelingStatAccess} and fires `onLevelUp` for each level gained in ascending order. Reach for this instead of hand-tracking xp thresholds; pair it with `resource-pool` (mana/stamina) and `ability-bar` (cooldowns) for a hero.
556
657
 
557
658
  ## @jgengine/core/game/quest
558
659
 
@@ -737,6 +838,26 @@
737
838
  - `TradeSystemDeps` (interface): interface TradeSystemDeps — ⚠ undocumented
738
839
  - `TradeWallet` (interface): interface TradeWallet — ⚠ undocumented
739
840
 
841
+ ## @jgengine/core/game/tribe
842
+
843
+ - `AssetAction` (type): type AssetAction = "inventory" | "use" | "command" — Per-asset action gated by rank on group-owned assets.
844
+ - `AssetOwnership` (type): type AssetOwnership = { scope: "group" } | { scope: "personal"; memberId: string } — Who inside the group owns an asset: the group as a whole, or a specific member personally.
845
+ - `AssetRef` (interface): interface AssetRef — Opaque reference to a shared thing (structure, creature, container) by kind + id.
846
+ - `Tribe` (interface): interface Tribe — A shared-ownership group — a tribe, guild, clan, or company — as one first-class aggregate. Unifies ranked membership, group-vs-personal asset ownership, mutual alliances, and a bounded event log that otherwise have to be hand-assembled from parties, factions, build permissions, and per-owner rosters.
847
+ - `TribeAssetRecord` (interface): interface TribeAssetRecord — An asset (structure/creature) owned within a tribe — group-shared or held by one member.
848
+ - `TribeConfig` (interface): interface TribeConfig — Construction options for {@link createTribe}: id, founder, rank ladder, and log cap.
849
+ - `TribeEvent` (interface): interface TribeEvent — One entry in a tribe's bounded event log. Optional fields present only when meaningful.
850
+ - `TribeEventType` (type): type TribeEventType = | "member-added" | "member-removed" | "member-left" | "rank-changed" | "founder-transferred" | "asset-registered" | "asset-unregistered" | "alliance-formed" | "alliance-broken" — The kind of change recorded in a tribe's event log (membership, ownership, or alliance).
851
+ - `TribeMemberRecord` (interface): interface TribeMemberRecord — A member of a tribe and the rank id that governs their permissions.
852
+ - `TribeRankDef` (interface): interface TribeRankDef — A rank within a group's hierarchy. Higher `level` means more authority: a member can only manage (kick, re-rank) members strictly below their own level, and cannot promote anyone to a level at or above their own. `permissions` are opaque, genre-agnostic capability slugs the game checks with {@link Tribe.can} (e.g. `"build"`, `"demolish"`, `"invite"`, `"kick"`, `"manage-ranks"`, plus the {@link AssetAction} slugs `"inventory"`, `"use"`, `"command"` for group-owned assets). A `bypass` rank (an admin) skips every permission check, exactly like the founder.
853
+ - `TribeRegistry` (interface): interface TribeRegistry — A collection of {@link Tribe tribes} with mutual alliance management and cross-member relation lookups. Alliances are kept symmetric here (both sides updated together), and member-to-member relations resolve through tribe membership + alliances, mirroring the `faction` graph vocabulary.
854
+ - `TribeRegistryDeps` (interface): interface TribeRegistryDeps — Injected dependencies for {@link createTribeRegistry} (e.g. a `now()` clock for event timestamps).
855
+ - `TribeRegistrySnapshot` (interface): interface TribeRegistrySnapshot — Full serializable state of a tribe registry — every tribe's snapshot in one payload.
856
+ - `TribeResult` (type): type TribeResult = { reason: string } | null — Standard `{ reason }`-on-failure / `null`-on-success result for authorized mutations.
857
+ - `TribeSnapshot` (interface): interface TribeSnapshot — Full serializable state of one tribe — the save/replication baseline.
858
+ - `createTribe` (function): function createTribe(config: TribeConfig): Tribe — Create a shared-ownership group aggregate (tribe / guild / clan / company). Members hold a {@link TribeRankDef rank} whose configurable permission slugs gate management actions and access to group-owned assets, while personally-owned assets stay with their member — and follow them out when they leave. Group↔group {@link Tribe.addAlly alliances} resolve allied tribes as friendly, and every membership / ownership / alliance change lands in a bounded ring-buffer {@link Tribe.events log}. All state is plain JSON via {@link Tribe.snapshot}/{@link Tribe.hydrate}; timestamps come from an injected clock for determinism. Compose many tribes with {@link createTribeRegistry} for mutual alliances and cross-member relation lookups.
859
+ - `createTribeRegistry` (function): function createTribeRegistry(deps: TribeRegistryDeps = {}): TribeRegistry — Create a registry that owns many tribes, keeps alliances mutual, and resolves member relations.
860
+
740
861
  ## @jgengine/core/game/unlocks
741
862
 
742
863
  - `UnlockCatalog` (interface): interface UnlockCatalog — ⚠ undocumented
@@ -786,6 +907,8 @@
786
907
  - `AdvanceResult` (interface): interface AdvanceResult — The outcome of {@link advanceLedger}: the new ledger plus applied transactions and events.
787
908
  - `AffixPool` (interface): interface AffixPool — ⚠ undocumented
788
909
  - `AppliedTransaction` (type): type AppliedTransaction = ResourceTransaction — A {@link ResourceTransaction} after policies and rounding, as actually applied to balances.
910
+ - `Auction` (interface): interface Auction — One live timed auction in an {@link AuctionBook}: an item stack under open bidding until it closes.
911
+ - `AuctionSettlement` (type): type AuctionSettlement = | { status: "sold"; auction: Auction; winnerId: string; price: number; houseCut: number; sellerProceeds: number; } | { status: "returned"; auction: Auction } — How one closed auction resolved during {@link AuctionBook.settleExpired}.
789
912
  - `AxisBindingMap` (type): type AxisBindingMap = Record<AxisName, AxisBinding> — ⚠ undocumented
790
913
  - `AxisChannelConfig` (interface): interface AxisChannelConfig — ⚠ undocumented
791
914
  - `AxisInput` (interface): interface AxisInput — ⚠ undocumented
@@ -823,7 +946,7 @@
823
946
  - `CropDef` (interface): interface CropDef — ⚠ undocumented
824
947
  - `CropTileState` (interface): interface CropTileState — ⚠ undocumented
825
948
  - `CrossThresholdsOptions` (interface): interface CrossThresholdsOptions — Exact-boundary and dead-band policy for {@link crossThresholds}.
826
- - `Curve` (type): type Curve = CurveDef & CurveShape — ⚠ undocumented
949
+ - `Curve` (type): type Curve = CurveDef & CurveShape — A fully specified progression curve — a {@link CurveDef} growth shape plus optional {@link CurveShape} rounding/clamp.
827
950
  - `DEFAULT_CHAT_BODY_LENGTH` (const): const DEFAULT_CHAT_BODY_LENGTH: 500 — ⚠ undocumented
828
951
  - `DEFAULT_CHAT_HISTORY_LIMIT` (const): const DEFAULT_CHAT_HISTORY_LIMIT: 100 — ⚠ undocumented
829
952
  - `DEFAULT_CHAT_RATE_LIMIT` (const): const DEFAULT_CHAT_RATE_LIMIT: ChatRateLimit — ⚠ undocumented
@@ -855,9 +978,9 @@
855
978
  - `FriendEntry` (interface): interface FriendEntry — ⚠ undocumented
856
979
  - `FriendRequestEntry` (interface): interface FriendRequestEntry — ⚠ undocumented
857
980
  - `Friends` (interface): interface Friends — ⚠ undocumented
858
- - `GameCameraConfig` (interface): interface GameCameraConfig — ⚠ undocumented
859
- - `GameDefinition` (interface): interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> — Fully-resolved game description produced by {@link defineGame} — assets, scene, and opted-in subsystems.
860
- - `GameDefinitionConfig` (type): type GameDefinitionConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> = Omit<GameDefinition<TAssetRef, TMultiplayer>, "scene" | "assets"> & { assets?: AssetCatalog<TAssetRef>; } — Input to {@link defineGame} — a `GameDefinition` with `scene` derived and `assets` optional.
981
+ - `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.
982
+ - `GameDefinition` (interface): interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> — Fully-resolved game description produced by {@link defineGameDefinition} — assets, scene, and opted-in subsystems.
983
+ - `GameDefinitionConfig` (type): type GameDefinitionConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> = Omit<GameDefinition<TAssetRef, TMultiplayer>, "scene" | "assets"> & { assets?: AssetCatalog<TAssetRef>; } — Input to {@link defineGameDefinition} — a `GameDefinition` with `scene` derived and `assets` optional.
861
984
  - `GameEventMap` (interface): interface GameEventMap — ⚠ undocumented
862
985
  - `GameEvents` (interface): interface GameEvents<TMap extends GameEventMap = GameEventMap> — ⚠ undocumented
863
986
  - `GameLoop` (interface): interface GameLoop<TContext = unknown> — Lifecycle hooks a game implements to drive init, per-tick simulation, and player join/leave.
@@ -902,10 +1025,10 @@
902
1025
  - `LeaderboardScope` (type): type LeaderboardScope = "global" | "server" | "profile" — ⚠ undocumented
903
1026
  - `LedgerEvent` (interface): interface LedgerEvent — Lifecycle signal emitted by {@link advanceLedger}.
904
1027
  - `LedgerEventKind` (type): type LedgerEventKind = "started" | "fired" | "skipped" | "depleted" | "ended" — The kinds of lifecycle signal {@link advanceLedger} can emit for a rule.
905
- - `LevelProgress` (interface): interface LevelProgress — ⚠ undocumented
1028
+ - `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.
906
1029
  - `LevelSequence` (interface): interface LevelSequence<TLevelConfig> — ⚠ undocumented
907
- - `LevelingConfig` (interface): interface LevelingConfig — ⚠ undocumented
908
- - `LevelingTrack` (interface): interface LevelingTrack — ⚠ undocumented
1030
+ - `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).
1031
+ - `LevelingTrack` (interface): interface LevelingTrack — A resolved leveling track: the immutable `maxLevel`/`startLevel`, the `xpForLevel` threshold lookup, a pure `resolve`, and `grantXp` which writes back through a {@link LevelingStatAccess} and fires `onLevelUp` once for every reached level in ascending order.
909
1032
  - `LifecycleConfig` (interface): interface LifecycleConfig<TState = unknown> — Declarative start/restart run lifecycle: the state transitions a game's run phase every genre repeats (title screen → live run → live run → title screen again), expressed as pure functions over one typed {@link StoreHandle} slot instead of hand-rolled `commands.define("start"/"restart")` glue that re-derives phase after every mutation. `start`/`restart` receive the store's own value type — the store's `TState`, never `ctx.game.store.get(key) as T` — and return the next value; the runtime writes it back and derives {@link GamePhase} from it via `phaseOf` in one place, so every adopting game gets identical, correct phase-sync for free.
910
1033
  - `LightingConfig` (interface): interface LightingConfig — Declarative lighting replacing the shell's hardcoded ambient/directional default (#207.5); mounts regardless of world kind, only when supplied.
911
1034
  - `Listing` (interface): interface Listing — One active post in a {@link ListingBook}: an item stack a seller offered at a fixed price until it expires.
@@ -966,6 +1089,7 @@
966
1089
  - `PredicatePath` (type): type PredicatePath = string — Dot path into a fact bag, e.g. `"hit.crit"` or `"attacker.team"`.
967
1090
  - `PredicateValue` (type): type PredicateValue = string | number | boolean | null — A declarative, serializable predicate AST evaluated against a plain fact bag. Predicates carry no closures, so they survive save/load and stay deterministic — the reusable condition seam that event-conditioned rules, quests, perks, and reactive AI gate on instead of hand-rolled callbacks.
968
1091
  - `PresenceInfo` (interface): interface PresenceInfo — ⚠ undocumented
1092
+ - `PriceStats` (interface): interface PriceStats — Aggregated market stats for one item over the retained samples.
969
1093
  - `QuestDef` (interface): interface QuestDef — ⚠ undocumented
970
1094
  - `QuestInstance` (interface): interface QuestInstance — ⚠ undocumented
971
1095
  - `QuestRewards` (interface): interface QuestRewards — ⚠ undocumented
@@ -998,6 +1122,7 @@
998
1122
  - `RuleSelectionConfig` (interface): interface RuleSelectionConfig — Deterministic selection inputs — plain serializable data a host can persist and replay.
999
1123
  - `RunDraft` (interface): interface RunDraft<TStat extends string = string, TData = unknown> — ⚠ undocumented
1000
1124
  - `RunModifierOffer` (interface): interface RunModifierOffer<TStat extends string = string, TData = unknown> — ⚠ undocumented
1125
+ - `SaleRecord` (interface): interface SaleRecord — One completed sale recorded into a {@link PriceHistory}.
1001
1126
  - `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.
1002
1127
  - `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"` means the backend rejected a read or write.
1003
1128
  - `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.
@@ -1115,6 +1240,7 @@
1115
1240
  - `craft` (function): function craft(state: InventoryState, layout: InventoryLayout, traits: ItemTraits, recipe: RecipeDef, context: CraftContext = {}): CraftResult — ⚠ undocumented
1116
1241
  - `craftSeconds` (function): function craftSeconds(recipe: RecipeDef): number — ⚠ undocumented
1117
1242
  - `createAffixRoller` (function): function createAffixRoller(config: RollerConfig): AffixRoller — ⚠ undocumented
1243
+ - `createAuctionBook` (function): function createAuctionBook(config: AuctionBookConfig): AuctionBook — Timed-bid auctions in the WoW/BDO auction-house mold: post an item with a start price, minimum increment, and optional buyout; bids escrow currency, outbid players are refunded into their collection box, bids near the close extend it (anti-snipe), and settlement pays the seller minus the house cut while the item lands in the winner's collection box. Unsold auctions return the goods to the seller's box. Wallet and inventory movement is the caller's job (mirrors `economy/listingBook`) — this primitive owns the auction lifecycle and the escrowed collection-box bookkeeping behind it.
1118
1244
  - `createBehaviourWorld` (function): function createBehaviourWorld(): BehaviourWorld — ⚠ undocumented
1119
1245
  - `createCardPile` (function): function createCardPile(config: CardPileConfig, initial?: Partial<Record<ZoneName, readonly string[]>>): CardPile — ⚠ undocumented
1120
1246
  - `createCardPileState` (function): function createCardPileState(config: CardPileConfig, initial?: Partial<Record<ZoneName, readonly string[]>>): CardPileState — ⚠ undocumented
@@ -1148,6 +1274,7 @@
1148
1274
  - `createNameGenerator` (function): function createNameGenerator(options: NameGeneratorOptions): NameGenerator — Generate procedural names from templates and word banks with an injected random source.
1149
1275
  - `createPairKeyCodec` (function): function createPairKeyCodec(options: PairKeyOptions = {}): PairKeyCodec — Build a pair-key codec for keyed relation values. Ids are escaped before joining, so any id (including ones containing the separator or a backslash) round-trips through {@link PairKeyCodec.key} → {@link PairKeyCodec.parse} without collision. Undirected codecs (the default) canonicalize so `key(a, b) === key(b, a)`.
1150
1276
  - `createPingSystem` (function): function createPingSystem(deps: PingSystemDeps): PingSystem — Contextual ping/marker communication between teammates, classified by what was pinged.
1277
+ - `createPriceHistory` (function): function createPriceHistory(config: PriceHistoryConfig): PriceHistory — The "market price" readout behind every auction house: a bounded rolling record of completed sales per item, aggregated into min/max/volume-weighted-average/latest unit-price stats so games can show current value and recent trends. Memory is bounded by `maxSamplesPerItem` (oldest samples drop first) and optionally by a sliding time window; timestamps are injected by the caller so the history stays deterministic.
1151
1278
  - `createProductionState` (function): function createProductionState(): ProductionState — A production building that converts input items into outputs over time — factory/crafting station.
1152
1279
  - `createQuestJournal` (function): function createQuestJournal(deps: QuestJournalDeps): QuestJournal — Track accepted quests and their per-objective progress, granting rewards on completion.
1153
1280
  - `createRaceState` (function): function createRaceState(config: RaceStateConfig): RaceState — A checkpoint race state machine — laps, forks, live standings, splits, and pluggable win conditions.
@@ -1173,13 +1300,12 @@
1173
1300
  - `createWeaponStats` (function): function createWeaponStats(resolveEntry: (itemId: string) => WeaponEntry | null | undefined): WeaponStats — Resolve per-weapon stat values — damage, fire rate, spread — for combat math.
1174
1301
  - `createWorkQueue` (function): function createWorkQueue<TSpec, TReserve = undefined>(): WorkQueueState<TSpec, TReserve> — Create an empty timed work queue.
1175
1302
  - `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`.
1176
- - `curve` (function): function curve(spec: Curve): (x: number) => number — ⚠ undocumented
1303
+ - `curve` (function): function curve(spec: Curve): (x: number) => number — Bind a {@link Curve} into a reusable `(x) => value` evaluator — a partial application of {@link evalCurve}.
1177
1304
  - `curveScale` (function): function curveScale(read: PolicyRead, shape: Curve): ResourcePolicy — Scale the amount by a {@link Curve} evaluated at the read value — a generic curve modifier (population upkeep, depletion falloff, difficulty income) over caller-provided context.
1178
1305
  - `decayMeterMoodles` (function): function decayMeterMoodles(values: DecayMeterValues, defs: readonly DecayMeterConfig[]): Moodle[] — Moodles for every crossed threshold, worst-first per meter in declared order.
1179
1306
  - `decayMeterSnapshot` (function): function decayMeterSnapshot(values: DecayMeterValues, defs: readonly DecayMeterConfig[]): Record<string, DecayMeterState> — Numeric state for every meter, keyed by id — the pure counterpart to {@link DecayMeterSet.snapshot}.
1180
1307
  - `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.
1181
1308
  - `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}.
1182
- - `defineGame` (function): function defineGame<TAssetRef extends ModelAssetRef, TMultiplayer>(config: GameDefinitionConfig<TAssetRef, TMultiplayer>): GameDefinition<TAssetRef, TMultiplayer> — Task-first entry point for authoring a game: fills in `scene` and default `assets`, validates `name`, OR-merges `features` from installed systems, and composes `loop` from `systems` + any classic hooks.
1183
1309
  - `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}.
1184
1310
  - `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.
1185
1311
  - `deriveTouchScheme` (function): function deriveTouchScheme(input: ActionCodesMap | undefined, { reserved, firstPerson, config }: DeriveTouchSchemeOptions): TouchScheme | null — Null means "render no touch controls" — either the game opted out or there is nothing to synthesize.
@@ -1190,7 +1316,7 @@
1190
1316
  - `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}.
1191
1317
  - `durabilityFraction` (function): function durabilityFraction(state: DurabilityState): number — ⚠ undocumented
1192
1318
  - `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`.
1193
- - `evalCurve` (function): function evalCurve(spec: Curve, x: number): number — ⚠ undocumented
1319
+ - `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.
1194
1320
  - `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.
1195
1321
  - `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.
1196
1322
  - `evaluatePredicate` (function): function evaluatePredicate(predicate: Predicate | undefined, facts: PredicateFacts): boolean — Evaluate a predicate against a fact bag. An omitted predicate matches unconditionally, so callers can treat "no condition" and "always" identically. Pure and deterministic — no allocation beyond path splits and no reliance on evaluation order between sibling clauses.
@@ -1215,7 +1341,7 @@
1215
1341
  - `jobById` (function): function jobById<TSpec, TReserve>(state: WorkQueueState<TSpec, TReserve>, id: JobId): Job<TSpec, TReserve> | null — Look up a job by id, or `null` if absent/terminal.
1216
1342
  - `jobProgress` (function): function jobProgress<TSpec, TReserve>(job: Job<TSpec, TReserve>): number — Fractional progress of a job (0…1); a zero-duration job reads as complete.
1217
1343
  - `lapDurations` (function): function lapDurations(splits: readonly number[], gatesPerLap: number): number[] — Per-lap durations from a cumulative split book with `gatesPerLap` checkpoints per lap — each lap's time is its finish-gate split minus the previous lap's finish. Only complete laps are returned.
1218
- - `leveling` (function): function leveling(config: LevelingConfig): LevelingTrack — ⚠ undocumented
1344
+ - `leveling` (function): function leveling(config: LevelingConfig): LevelingTrack — Build a leveling track from an xp-per-level {@link Curve} and a max level. The returned {@link LevelingTrack} resolves how many levels a given xp total earns (rolling over surplus xp, capping at `maxLevel`) and, via `grantXp`, writes the new level/xp back through a {@link LevelingStatAccess} and fires `onLevelUp` for each level gained in ascending order. Reach for this instead of hand-tracking xp thresholds; pair it with `resource-pool` (mana/stamina) and `ability-bar` (cooldowns) for a hero.
1219
1345
  - `listRuleEffects` (function): function listRuleEffects(): RuleEffectDefinition[] — Every declared rule effect, for populating inspector dropdowns and validating a content set.
1220
1346
  - `loadBindingOverrides` (function): function loadBindingOverrides(gameId: string, storage: Pick<WebStorageLike, "getItem"> | null | undefined = defaultStorage()): BindingOverrides — ⚠ undocumented
1221
1347
  - `localSaveBackend` (function): function localSaveBackend(storage?: KeyValueStorage | null): 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 memory-only. Storage errors (quota exceeded, private mode, no DOM) degrade to no-ops, so a save never throws into a game tick.
@@ -1295,6 +1421,17 @@
1295
1421
  - `withTouchCodes` (function): function withTouchCodes(map: ActionCodesMap | undefined): ActionCodesMap — Every action gains a synthetic touch code alongside its physical codes.
1296
1422
  - `worldHealthBarAllowsRole` (function): function worldHealthBarAllowsRole(roles: readonly CatalogEntityRole[] | undefined, role: CatalogEntityRole | undefined): boolean — ⚠ undocumented
1297
1423
 
1424
+ ## @jgengine/core/inventory/encumbrance
1425
+
1426
+ - `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.
1427
+ - `EncumbranceState` (interface): interface EncumbranceState — Serializable snapshot of a carrier's load versus capacity and the movement consequences.
1428
+ - `EncumbranceTier` (type): type EncumbranceTier = "unencumbered" | "encumbered" | "immobile" — Coarse carry state: free-moving, slowed, or pinned in place.
1429
+ - `LoadEntry` (interface): interface LoadEntry — One carried stack: an item id and how many of it the carrier holds.
1430
+ - `MassResolver` (type): type MassResolver = (itemId: string) => number — Resolves the per-unit mass of an item id. Injected so mass tables stay decoupled from this module.
1431
+ - `encumbranceMoveMultiplier` (function): function encumbranceMoveMultiplier(fraction: number, config?: EncumbranceConfig): number — Progressive move-speed multiplier for a raw load fraction: 1 while at/below `soft`, a linear decay from 1 down toward `floor` across the `soft..1` band, and 0 once at/over capacity. Pure and side-effect free — the curve `resolveEncumbrance` reads for `moveMultiplier`.
1432
+ - `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.
1433
+ - `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.
1434
+
1298
1435
  ## @jgengine/core/inventory/inventoryModel
1299
1436
 
1300
1437
  - `InventoryLayout` (interface): interface InventoryLayout — ⚠ undocumented
@@ -1304,7 +1441,16 @@
1304
1441
  - `ItemTraits` (interface): interface ItemTraits — ⚠ undocumented
1305
1442
  - `MoveResult` (type): type MoveResult = | { status: "ok"; from: InventoryState; to: InventoryState } | { status: "rejected"; reason: "invalid-slot" | "empty-slot" | "wrong-kind" | "no-space" } — ⚠ undocumented
1306
1443
  - `PutResult` (type): type PutResult = | { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "no-space" | "wrong-kind" | "slot-occupied" | "invalid-slot" } — ⚠ undocumented
1444
+ - `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.
1307
1445
  - `TakeResult` (type): type TakeResult = { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "insufficient" } — ⚠ undocumented
1446
+ - `countItem` (function): function countItem(state: InventoryState, itemId: string): number — Total quantity of `itemId` summed across every stack in the inventory.
1447
+ - `createEmptyInventory` (function): function createEmptyInventory(layout: InventoryLayout): InventoryState — A bag of stackable items supporting add, remove, count, and move — the base inventory model.
1448
+ - `createInventorySet` (function): function createInventorySet<TId extends string>(layouts: Record<TId, InventoryLayout>, traits: ItemTraits): InventorySet<TId> — Build a keyed set of named inventories from their `layouts`, sharing one `traits` table — the stateful façade over put/take/move/count.
1449
+ - `hasItem` (function): function hasItem(state: InventoryState, itemId: string, count: number): boolean — True when the inventory holds at least `count` of `itemId`.
1450
+ - `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.
1451
+ - `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.
1452
+ - `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.
1453
+ - `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.
1308
1454
 
1309
1455
  ## @jgengine/core/inventory/shapedGrid
1310
1456
 
@@ -1639,6 +1785,16 @@
1639
1785
  - `RoundState` (interface): interface RoundState<TPhase extends string = RoundPhase> — ⚠ undocumented
1640
1786
  - `RoundTeam` (interface): interface RoundTeam — A team entry with an optional role tag (e.g. "attacker", "defender") retrievable via `RoundState.roleOf`.
1641
1787
 
1788
+ ## @jgengine/core/store/defineKeyedStore
1789
+
1790
+ - `KeyedStoreHandle` (interface): interface KeyedStoreHandle<T> — A typed, cast-free handle onto a per-owner keyed family of slots on the reactive game store (`ctx.game.store`) — the {@link StoreHandle} shape widened with an `id` (userId, instanceId, …) so one definition covers every owner instead of one `defineStore` call per key. `keyFor` composes the underlying store key from the id; call sites never see it. Writes flow through the engine store, so they bump `ctx.version()`, serialize into a {@link WorldSnapshot}, and replay under host authority — the same guarantees a hand-rolled `` `prefix:${id}` `` string plus `store.get(key) as T` gives up.
1791
+ - `defineKeyedStore` (function): function defineKeyedStore<T>(keyFor: (id: string) => string, initial: T | (() => T)): KeyedStoreHandle<T> — Define a typed per-owner keyed family on the game store: game code expresses `read(ctx, userId)`/`write(ctx, userId, v)`/`update(...)` with the value type it means, and both the `` `prefix:${id}` `` key composition and the `unknown → T` cast live once, here, behind the boundary. Reach for this over N separate `defineStore` calls whenever the owning id varies at runtime (per-user class, per-instance auras) — `defineStore` stays the right call for a single fixed slot.
1792
+
1793
+ ## @jgengine/core/store/defineStore
1794
+
1795
+ - `StoreHandle` (interface): interface StoreHandle<T> — A typed, cast-free handle onto one slot of the reactive game store (`ctx.game.store`). The single type parameter is fixed at definition; `read`/`write`/`update` never widen to `unknown`, so no call site repeats `store.get(key) as T`. Writes flow through the engine store, so they bump `ctx.version()`, serialize into a {@link WorldSnapshot}, and replay under host authority — the same guarantees a hand-rolled `store.get`/`store.set` pair gives up the moment it forks run state into a module-level singleton.
1796
+ - `defineStore` (function): function defineStore<T>(key: string, initial: T | (() => T)): StoreHandle<T> — Define a typed slot on the game store: game code expresses `read(ctx)`/`write(ctx, v)`/`update(...)` with the value type it means, and the `unknown → T` cast lives once, here, behind the boundary. Pass a factory for `initial` when the fallback is a fresh mutable object; the factory runs at most once and its result is reused, so an unwritten slot keeps a stable identity across reads (no per-read churn for a React selector, no allocation on a hot path).
1797
+
1642
1798
  ## @jgengine/core/survival/decayMeter
1643
1799
 
1644
1800
  - `DecayMeterConfig` (interface): interface DecayMeterConfig — ⚠ undocumented