@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
@@ -0,0 +1,88 @@
1
+ # Recipe — minimal game (scaffold → authored scene → win)
2
+
3
+ **What this wires:** the default end-to-end path from nothing to a complete, genre-free
4
+ walk/interact/win game: a scaffold, one `defineGame` config off the happy-path kit, an
5
+ authored scene edited in the browser, one system, one store slot, an empty-canvas HUD,
6
+ and a win condition. Every new game starts here; add combat, multiplayer, or deeper
7
+ world features only after this loop plays.
8
+
9
+ ## 1. Scaffold
10
+
11
+ `npx jgengine create <name>` (inside the engine monorepo: `bun run new:game "<id-or-title>"`).
12
+ The file set is thin and already runs:
13
+
14
+ - `src/main.tsx` — mounts `GameHost` with `editor={() => import("@jgengine/editor")}`; the
15
+ host owns the F2+E / `?mode=editor` editor summon and the dev save endpoint.
16
+ - `src/game.config.ts` — the whole game definition (below).
17
+ - `src/editor.scene.json` + `src/editorLayers.ts` — the authored scene document, normalized
18
+ with `normalizeEditorLayers`. All placement lives here, never as coordinates in code.
19
+ - `src/loop.ts` and `src/game/ui/GameUI.tsx` — systems and HUD.
20
+
21
+ ## 2. Define the game — one import surface
22
+
23
+ ```ts
24
+ // game.config.ts — everything below comes from the kit; deeper paths are for outgrowing it.
25
+ import { defineGame } from "@jgengine/shell/gameKit";
26
+
27
+ import { editorLayers } from "./editorLayers";
28
+ import { GameUI } from "./game/ui/GameUI";
29
+ import { onNewPlayer, systems } from "./loop";
30
+
31
+ export const game = defineGame({
32
+ name: "my-game",
33
+ // Binding movement actions makes the shell drive the walk controller — the game walks day one.
34
+ input: { moveForward: ["KeyW"], moveBack: ["KeyS"], moveLeft: ["KeyA"], moveRight: ["KeyD"], interact: ["KeyE"] },
35
+ systems,
36
+ loop: { onNewPlayer },
37
+ GameUI,
38
+ editorLayers, // the authored scene auto-mounts; the player spawns at its player_spawn marker
39
+ });
40
+ // Solo game: omit `multiplayer` entirely — offline is the shell default.
41
+ ```
42
+
43
+ **World is optional and thin.** Omitting `world` is valid — a pure UI/rules game is not a
44
+ spatial place. When the game *is* a place, declare only substrate + laws with `world()` from
45
+ `@jgengine/core/world/place` (e.g. `{ id, ground: { mode: "flat", size: { x: Infinity, z: Infinity } }, physics: { gravity: -24 } }`,
46
+ or `mode: "board"` for 2D). Never seed, sky, or vegetation in `world` — the engine renders its
47
+ default sky until you author one in the editor, and foliage is editor scatter over the ground.
48
+
49
+
50
+ ```ts
51
+ // loop.ts — one store slot for run state, one system for the rules tick.
52
+ import { defineStore, defineSystem, authoredSpawnPosition, type GameContext } from "@jgengine/shell/gameKit";
53
+
54
+ import { editorLayers } from "./editorLayers";
55
+
56
+ export const run = defineStore("run", { collected: 0, won: false });
57
+
58
+ export const systems = [
59
+ defineSystem({
60
+ id: "rules",
61
+ tick: { type: "fixed" },
62
+ update(ctx: GameContext) {
63
+ // System-check win alternative: read/update `run` here (e.g. win at collected >= 3).
64
+ if (run.read(ctx).collected >= 3) run.update(ctx, (s) => ({ ...s, won: true }));
65
+ },
66
+ }),
67
+ ];
68
+
69
+ export function onNewPlayer(ctx: GameContext): void {
70
+ ctx.scene.entity.spawn("player", { id: ctx.player.userId, position: authoredSpawnPosition(editorLayers) ?? [0, 0, 0] });
71
+ }
72
+ ```
73
+
74
+ `GameUI` starts as an empty `HudCanvas` (`useHudLayout` + `HudCanvas` from the kit). That is
75
+ intentional: **every game owns its UI**. Write a short UI art direction, then build custom
76
+ panels for this pitch — do not ship stock drop-in widgets (`StatBar`, `Hotbar`, `Coins`, glass
77
+ frames) as the product face. Reach for engine pieces only as headless data, layout, or
78
+ interaction models under game-owned chrome (see `jgengine-ui`).
79
+
80
+ ## 3. Author, play, win
81
+
82
+ 1. `bun dev`, then F2+E opens the editor on `editor.scene.json` — move the spawn, place
83
+ props and trigger markers, Ctrl+S saves back to the document.
84
+ 2. The starter scene ships a goal marker with `{ on: "enter", action: "win" }` — walking
85
+ into it wins with zero game code. Author more rules the same way (triggers in the
86
+ editor), or read volumes yourself via the kit's `collectAuthoredTriggers(editorLayers)`.
87
+ 3. Prove it: `bun test` asserts the document (spawn, goal), and `jgengine-verify` covers
88
+ screenshot and gameplay evidence before any "it works" claim.
@@ -11,6 +11,25 @@ This skill owns combat resolution: target policy, combat stats, teams/factions a
11
11
 
12
12
  Search [capabilities.md](capabilities.md), then use [api.md](api.md) for signatures and [reference.md](reference.md) for complete pipelines.
13
13
 
14
+ Existing projects keep their resource state and follow the
15
+ [portable stat-pool recipe](recipes/portable-stat-pools.md). Adapt the caller's
16
+ store through `StatPoolAccess`; do not require `StatValueMap`, `GameContext`, or
17
+ a parallel entity store. To bolt firing onto an existing game, compose
18
+ `createWeaponRuntime` (cadence + magazine + the caller's own raycast + portable
19
+ `resolveDamageHit`) per the
20
+ [portable weapon-plumbing recipe](recipes/portable-weapon-plumbing.md); never add
21
+ a default gun or FPS kit.
22
+
23
+ Damage commitment accepts the same structural pool adapter while the matchup,
24
+ receiver, interceptor, and status stages remain pure. Follow the
25
+ [portable damage/effects recipe](recipes/portable-damage-effects.md) to compose
26
+ them over an existing store, clock, RNG, spatial index, and death flow.
27
+
28
+ Closure-backed magazines and stat modifiers expose plain snapshot/restore
29
+ state for caller-owned saves, rollback, replay, and workers. Follow the
30
+ [portable runtime-state recipe](recipes/portable-runtime-state.md); the caller
31
+ keeps its clock, reserve state, schema, and authority.
32
+
14
33
  ## Canonical pipeline
15
34
 
16
35
  1. Acquire eligible candidates from a bounded spatial source.
@@ -31,4 +50,3 @@ Compose stages through registration/policy seams; adding a damage type, effect,
31
50
  - Reward generation and allocation are different operations, especially in multiplayer.
32
51
  - Compose multiple loot pools with `createLootPipeline` (ordered stages, gates, fallbacks, roll modifiers, per-drop provenance) over `lootTable`; keep genre concepts (world/dedicated/boss/luck/pity) in game-space stages and modifiers, never in core.
33
52
  - UI bars, reticles, and feedback route to `jgengine-ui`.
34
-
@@ -28,6 +28,7 @@
28
28
  - `DEFAULT_FIRE_PULSE_SECONDS` (const): const DEFAULT_FIRE_PULSE_SECONDS: 0.12 — Default `RenderCueTuning.firePulseSeconds`.
29
29
  - `DEFAULT_HIT_PULSE_SECONDS` (const): const DEFAULT_HIT_PULSE_SECONDS: 0.2 — Default `RenderCueTuning.hitPulseSeconds`.
30
30
  - `DEFAULT_RENDER_CUES` (const): const DEFAULT_RENDER_CUES: Readonly<EntityRenderCues> — Neutral starting cue set: idle, unarmed, undamaged.
31
+ - `DEFAULT_VFX_PRESET` (const): const DEFAULT_VFX_PRESET: VfxPresetName — The fallback flavor used when a `vfx()` call names an unknown preset — so a typo still shows a spark instead of nothing.
31
32
  - `DamageChannelId` (type): type DamageChannelId = string — Caller-owned id for a damage channel — `"fire"`, `"ballistic"`, `"physical"`, `"corrosive"`. The engine never branches on the value; a channel is whatever a game's data declares.
32
33
  - `DamageClampConfig` (interface): interface DamageClampConfig — Caller data for a flat per-hit damage cap.
33
34
  - `DamageContext` (interface): interface DamageContext — The facts a received-damage modifier predicates over: the incoming `channel`, who dealt it, the statuses the target currently carries, and any caller tags. All caller-owned strings; the engine never assigns meaning to a channel, source, status, or tag.
@@ -50,6 +51,8 @@
50
51
  - `EntityRenderCues` (interface): interface EntityRenderCues — Per-entity render cues: the motion/animation signal a custom `renderEntity` or first-person viewmodel component needs to drive gait, muzzle flash, reload poses, and hit reactions — without diffing the parent group's position itself or reading a game-side module map for attack timing.
51
52
  - `EventMeter` (interface): interface EventMeter — ⚠ undocumented
52
53
  - `EventMeterFeedResult` (interface): interface EventMeterFeedResult — ⚠ undocumented
54
+ - `FireCadence` (interface): interface FireCadence — A minimal minimum-interval rate gate for a repeated action — weapon fire, ability spam, any "no faster than N per second" rule. Deterministic and serializable: its whole state is the elapsed time since the last action, exposed via {@link FireCadence.elapsedMs}.
55
+ - `FireCadenceConfig` (interface): interface FireCadenceConfig — Tuning for {@link createFireCadence}: the minimum interval between actions and whether it starts ready.
53
56
  - `FrameRange` (interface): interface FrameRange — ⚠ undocumented
54
57
  - `HitReactionConfig` (interface): interface HitReactionConfig — ⚠ undocumented
55
58
  - `ImmunityWindow` (interface): interface ImmunityWindow — A per-target timed immunity policy: an interceptor plus grant/query/serialize controls, toggled by transitions.
@@ -58,7 +61,9 @@
58
61
  - `InterceptRecord` (interface): interface InterceptRecord — One row of provenance: which interceptor acted, what it decided, and the amount before/after it acted.
59
62
  - `MAX_INTERCEPT_STEPS` (const): const MAX_INTERCEPT_STEPS: 256 — Upper bound on interceptor evaluations for a single resolution; guards against runaway `split`/`redirect` loops.
60
63
  - `Magazine` (interface): interface Magazine — A per-weapon magazine: discrete loaded rounds, a timed reload that refills from a reserve pool, and the reserve-pool interaction itself — the primitive that replaces hand-rolling mag size, reload delay, and reserve bookkeeping per game (#536.2).
64
+ - `MagazineConfig` (interface): interface MagazineConfig — Tuning for `createMagazine`: mag capacity, reload delay, and where reserve ammo is drawn from.
61
65
  - `MagazineReserve` (interface): interface MagazineReserve — Draws ammo for a `Magazine`'s reload from wherever the reserve pool actually lives.
66
+ - `MagazineSnapshot` (interface): interface MagazineSnapshot — Complete serializable state for a {@link Magazine}; `reserve: null` means infinite reserve.
62
67
  - `MatchupOutputs` (interface): interface MatchupOutputs — The independent, typed outputs a channel-vs-trait matchup can scale. Every field is an optional multiplier that defaults to `1` (identity), so a matchup entry names only the axes it bends. `impact` scales the direct hit, `applyChance` the odds a status lands, `magnitude` and `duration` the applied status itself, and `extra` carries any further caller-defined scalar (knockback, stagger, heat) the engine forwards without interpreting.
63
68
  - `MeterAddResult` (interface): interface MeterAddResult — ⚠ undocumented
64
69
  - `ObjectRaycastHit` (interface): interface ObjectRaycastHit — ⚠ undocumented
@@ -88,7 +93,14 @@
88
93
  - `ShotOriginPolicy` (type): type ShotOriginPolicy = | { kind: "converge"; muzzle?: EntityPosition; height?: number } | { kind: "eye"; height?: number } | { kind: "legacy" } | { kind: "entity" } | { kind: "entityOffset"; offset: EntityPosition } | { kind: "muzzle"; offset?: EntityPosition } | { kind: "camera"; origin: EntityPos… — How a shot's world-space origin (and optional direction) is resolved before prediction/settlement. - `converge` — the shot leaves the gun `muzzle` yet still passes through whatever the shooter's sightline (crosshair) covers: origin is the muzzle offset, direction is bent from the muzzle to the eye ray's aim point. The projectile system's default for a free `{ yaw, pitch }` aim, so a bullet visibly comes from the barrel without missing the reticle. Needs a scene raycast to find the aim point (`convergeShot`); a bare `resolveShot` degrades to a straight muzzle ray. Passes an explicit `{ origin, direction }` aim through untouched. - `eye` — `aim.origin` when present, else the shooter's entity position raised to eye height; the shot traces the shooter's sightline, so what the crosshair covers is what gets hit. - `legacy` — `aim.origin` when present, else the shooter's raw entity position (feet). - `entity` — always the shooter's entity position. - `entityOffset` / `muzzle` — entity-local offset rotated by the shooter's yaw (muzzle on a weapon model). - `camera` — explicit camera/reticle world origin (and optional direction override). - `world` — absolute world origin.
89
94
  - `SpawnProvider` (type): type SpawnProvider = ( params: Readonly<Record<string, unknown>>, ctx: SpawnProviderContext, ) => readonly EncounterSpawnRequest[] — A spawn provider: given the phase's `params` and context, returns the spawn requests to emit on enter. Deterministic; reads authored scene points from `ctx.spawnPoints` rather than embedding coordinates.
90
95
  - `SpawnProviderContext` (interface): interface SpawnProviderContext — Evaluation context a spawn provider receives when its phase is entered.
96
+ - `StatPool` (interface): interface StatPool — Serializable state for any bounded named resource. The caller defines what the resource means.
97
+ - `StatPoolAccess` (interface): interface StatPoolAccess<TOwnerId extends string = string, TStatId extends string = string> — Structural bridge to caller-owned resource state. Implement it over an ECS, Redux/Zustand store, plain object, database-backed model, or JGengine's native entity stats without moving ownership into the SDK.
98
+ - `StatPoolAccessResult` (type): type StatPoolAccessResult = | ({ status: "ok" } & StatPoolChange) | { status: "rejected"; reason: string } — Outcome of changing a pool through {@link StatPoolAccess}.
99
+ - `StatPoolChange` (interface): interface StatPoolChange — Inspectable result of a bounded pool change.
100
+ - `StatPoolInput` (interface): interface StatPoolInput — Input used to create a normalized pool. `current` defaults to `max`; `min` defaults to zero.
101
+ - `StatPoolPatch` (interface): interface StatPoolPatch — Partial update applied without mutating the previous pool.
91
102
  - `Stats` (interface): interface Stats<TStat extends string> — ⚠ undocumented
103
+ - `StatsSnapshot` (interface): interface StatsSnapshot<TStat extends string> — Complete plain-data state for a {@link Stats} runtime.
92
104
  - `StatusApplicationOutcome` (interface): interface StatusApplicationOutcome — The result of one status application: the outcome, the resulting instance (if any), and the roll.
93
105
  - `StatusApplicationSpec` (interface): interface StatusApplicationSpec — A serializable, effect-agnostic description of a status a hit tries to apply: an opaque `status` id the game routes to its own effect/DoT pipeline, a base landing `chance`, and the duration/ticks/magnitude/attribution/stacking data the resolver needs. The engine never interprets `status` — it is a reference, not a built-in condition.
94
106
  - `StatusImmunity` (interface): interface StatusImmunity — Statuses a target cannot receive at all — the immunity half of application policy.
@@ -102,6 +114,12 @@
102
114
  - `ThresholdMark` (interface): interface ThresholdMark — Generic numeric threshold-crossing tracker (#931).
103
115
  - `ThresholdTracker` (interface): interface ThresholdTracker — A stateful crossing tracker over one numeric source.
104
116
  - `ThresholdTrackerSnapshot` (interface): interface ThresholdTrackerSnapshot — Serializable tracker state for save/load.
117
+ - `VfxPreset` (interface): interface VfxPreset — A named visual flavor for a one-shot `combat.vfx` burst: the archetype (`kind`), a `0xRRGGBB` tint, and optional default `durationMs`/`radius`. Purely presentation — a preset never touches damage, range, or any authoritative value; it just answers "what does this attack look like" so a game does not hand-roll colors and archetypes.
118
+ - `VfxPresetName` (type): type VfxPresetName = keyof typeof vfxPresets — A named key into {@link vfxPresets} (`"arrow" | "fireball" | "lightning" | "web" | "slash" | "shield" | "heal" | ...`).
119
+ - `WeaponFireResult` (interface): interface WeaponFireResult<THit> — The result of a {@link WeaponRuntime.fire} attempt. On `"fired"`, `hits` is whatever the caller's raycast returned and `resolutions` holds the portable damage provenance for each hit that produced a {@link DamageHitInput}; on any blocked status both are empty and no round was spent.
120
+ - `WeaponFireStatus` (type): type WeaponFireStatus = "fired" | "cooling" | "empty" | "reloading" — Why a {@link WeaponRuntime.fire} attempt resolved the way it did.
121
+ - `WeaponRuntime` (interface): interface WeaponRuntime<TAim, THit> — A composed weapon fire controller: a {@link FireCadence} gate, an optional {@link Magazine}, the caller's raycast, and portable {@link resolveDamageHit} damage resolution wired together. The caller retains ownership of models, animations, muzzle flash, audio, input, camera, aiming, and the raycaster itself.
122
+ - `WeaponRuntimeConfig` (interface): interface WeaponRuntimeConfig<TAim, THit> — Configuration for {@link createWeaponRuntime}. Everything time/ammo-related is plain data; the two callbacks are where the caller keeps ownership of aiming, its own raycaster/projectile query, and how a hit maps to damage. There is deliberately no default weapon, projectile, or damage table here — those are game content.
105
123
  - `activePhaseId` (function): function activePhaseId<TData>(state: EncounterState<TData>): string | null — The deepest active phase id — the completion cursor a HUD reads to label the current wave/phase — or null before start and once done.
106
124
  - `advanceCombo` (function): function advanceCombo(input: AdvanceComboInput): AdvanceComboResult — ⚠ undocumented
107
125
  - `advanceMotionCues` (function): function advanceMotionCues(cues: EntityRenderCues, speed: number, dt: number, tuning?: RenderCueTuning): EntityRenderCues — Advances `bobPhase` and decays `recoil` from a live `speed` sample (e.g. `groundSpeed(entity)`); leaves event-driven fields untouched.
@@ -109,7 +127,9 @@
109
127
  - `applyRenderAnimationEvent` (function): function applyRenderAnimationEvent(cues: EntityRenderCues, event: string): EntityRenderCues — Applies a `entity.animation` event (`"fire"` / `"reload"` / `"reloadEnd"`, or any game-defined name) to the cue set. Unknown event names are a no-op.
110
128
  - `applyRenderDeathEvent` (function): function applyRenderDeathEvent(cues: EntityRenderCues): EntityRenderCues — Marks the cue set dead after `entity.died`; sticky for the lifetime of the render component.
111
129
  - `applyRenderHitEvent` (function): function applyRenderHitEvent(cues: EntityRenderCues): EntityRenderCues — Marks a `combat.hitReaction` pulse; the caller clears `hit` again after its own pulse window.
130
+ - `applyStatPoolDelta` (function): function applyStatPoolDelta<TOwnerId extends string, TStatId extends string>(access: StatPoolAccess<TOwnerId, TStatId>, ownerId: TOwnerId, statId: TStatId, amount: number): StatPoolAccessResult — Read, transition, and write a caller-owned pool through the structural adapter. Positive amounts increase the resource; negative amounts decrease it. The adapter receives a complete replacement value, never a hidden mutation.
112
131
  - `attackMeta` (function): function attackMeta(tags: readonly AttackTag[], extra?: Omit<AttackMeta, "tags">): AttackMeta — ⚠ undocumented
132
+ - `changeStatPool` (function): function changeStatPool(pool: StatPool, amount: number): StatPoolChange — Pure bounded delta transition with the exact applied amount and boundary flags.
113
133
  - `convergeShot` (function): function convergeShot(deps: ShotOriginDeps, from: string, aim: Aim, range: number, sightHit: (origin: EntityPosition, direction: EntityPosition) => EntityPosition | null, muzzleOffset?: EntityPosition): ResolvedShot | null — Resolves a `converge` shot with scene knowledge: fires from the gun muzzle but bends the direction so the shot passes through the aim point the shooter's eye ray covers. `sightHit` casts the eye ray and returns where it lands (first impact), or `null` to fall back to a point `range` metres down the sightline. A `{ origin, direction }` aim is passed through unchanged (nothing to converge).
114
134
  - `counters` (function): function counters(meta: AttackMeta, move: CounterMove): boolean — ⚠ undocumented
115
135
  - `createAbilityKit` (function): function createAbilityKit(configs: readonly AbilitySlotConfig[], options: AbilityKitOptions = {}): AbilityKit — A bar of cooldown-gated abilities the player fires by slot, tracking readiness and cooldown per ability.
@@ -125,17 +145,20 @@
125
145
  - `createDefensiveWindow` (function): function createDefensiveWindow(config: DefensiveWindowConfig): DefensiveWindow — Open a timed defensive window — block, parry, or i-frames — and test incoming hits against it.
126
146
  - `createDotField` (function): function createDotField(): DotField — Builds an empty {@link DotField}; `apply` DoTs onto it and drain damage each frame with `tick`.
127
147
  - `createDownedState` (function): function createDownedState(config: DownedConfig): DownedState — A downed/bleed-out state that ticks toward death and that teammates can revive before the timer runs out.
128
- - `createEffectSystem` (function): function createEffectSystem(deps: EffectSystemDeps): EffectSystem — Apply, stack, and tick timed status effects — buffs, debuffs, DoTs — on entities.
148
+ - `createEffectSystem` (function): function createEffectSystem(deps: EffectSystemDeps): EffectSystem — Resolve direct or area resource effects through caller-owned stat pools, including ordered absorption, restoration, modifiers, and lethal context.
129
149
  - `createEncounterState` (function): function createEncounterState<TData>(config: EncounterConfig<TData>): EncounterState<TData> — Build the initial, serializable state for an encounter from its config. Deep-copies the authored phase tree (so later injection never mutates the config), verifies phase ids are unique, and leaves every node pending. Does not enter the first phase — call {@link startEncounter} for that.
130
150
  - `createEventMeter` (function): function createEventMeter(config: EventMeterConfig): EventMeter — A heat/hype gauge that rises as tagged events land and cools between them, firing when it fills or breaks — the streak/overdrive meter shooters and fighters hand-roll.
151
+ - `createFireCadence` (function): function createFireCadence(config: FireCadenceConfig): FireCadence — Build a {@link FireCadence} rate gate. Reach for this instead of hand-tracking a `lastFiredAt` timestamp and comparing against a fire interval per game.
131
152
  - `createImmunityWindow` (function): function createImmunityWindow(id = "immunity"): ImmunityWindow — A per-target immunity/invulnerability window as an ordinary damage policy — granted and cleared by state transitions (i-frames, phase invuln), rejecting damage while active. Deterministic (time is passed in) and serializable.
132
153
  - `createMagazine` (function): function createMagazine(config: MagazineConfig): Magazine — Builds a {@link Magazine}: discrete loaded ammo, a timed reload, and reserve-pool interaction.
133
154
  - `createProjectileSystem` (function): function createProjectileSystem(deps: ProjectileSystemDeps): ProjectileSystem — Spawn and advance projectiles each frame, resolving travel, lifetime, and hits.
134
155
  - `createRegenShield` (function): function createRegenShield(config: RegenShieldConfig): RegenShield — Builds a {@link RegenShield} that suppresses regen for `regenDelayMs` after each hit.
135
156
  - `createResourcePool` (function): function createResourcePool(config: ResourcePoolConfig): ResourcePool — A regenerating resource pool — mana, stamina, energy — that actions spend from and that refills over time.
136
157
  - `createStaggerMeter` (function): function createStaggerMeter(config: StaggerMeterConfig): StaggerMeter — Build a stagger/poise gauge from landed hits toward a break threshold that staggers the target.
158
+ - `createStatPool` (function): function createStatPool(input: StatPoolInput): StatPool — Normalize caller data into a complete plain-data pool.
137
159
  - `createStats` (function): function createStats<TStat extends string>(base: Record<TStat, number>, options?: CreateStatsOptions): Stats<TStat> — A stat block whose base values take stacking, timed buffs and debuffs, resolving the modified value on read.
138
160
  - `createThresholdTracker` (function): function createThresholdTracker(config: ThresholdCrossingConfig): ThresholdTracker — Track a numeric source against named thresholds and emit ordered crossing events with direction, hysteresis, once/repeat policy, and skipped-threshold handling. Deterministic and serializable; drives phase transitions, armor breaks, resource warnings, and any other threshold consumer.
161
+ - `createWeaponRuntime` (function): function createWeaponRuntime<TAim, THit>(config: WeaponRuntimeConfig<TAim, THit>): WeaponRuntime<TAim, THit> — Compose a portable weapon fire controller from the existing generic primitives — a {@link FireCadence} gate, an optional {@link Magazine}, a caller-provided raycast, and portable {@link resolveDamageHit} damage resolution. No `GameContext`, entity store, renderer, or default weapon is involved; the caller owns aiming, its raycaster, and all presentation, and the result carries full damage provenance for authority to apply and replicate.
139
162
  - `encounterProgress` (function): function encounterProgress<TData>(state: EncounterState<TData>): { completed: number; total: number; done: boolean; } — Progress across the whole tree: how many phases have completed out of the total node count, plus whether the encounter has finished. Counts injected phases.
140
163
  - `eyeHeightFromColliders` (function): function eyeHeightFromColliders(set: EntityColliderSet | null | undefined): number — Eye height derived from a collider set: 90% of the tallest hitbox top, or the humanoid default when unknown.
141
164
  - `filterOutcomeFor` (function): function filterOutcomeFor(outcome: AllocationOutcome, viewerId: string): AllocationOutcome — Project an allocation outcome for one viewer, enforcing replication authoritatively: private grants belonging to other recipients are dropped, and pools the viewer cannot claim (not eligible, or reserved for someone else) are hidden — so private rewards never leak across the wire while provenance on the viewer's own grants is preserved.
@@ -145,6 +168,7 @@
145
168
  - `isBlockable` (function): function isBlockable(meta: AttackMeta): boolean — ⚠ undocumented
146
169
  - `isDodgeable` (function): function isDodgeable(meta: AttackMeta): boolean — ⚠ undocumented
147
170
  - `isParryable` (function): function isParryable(meta: AttackMeta): boolean — ⚠ undocumented
171
+ - `patchStatPool` (function): function patchStatPool(pool: StatPool, patch: StatPoolPatch): StatPool — Apply new bounds/current to a pool, clamping the result and leaving the input untouched.
148
172
  - `phaseStatus` (function): function phaseStatus<TData>(state: EncounterState<TData>, id: string): PhaseStatus | null — The runtime status of a phase by id (pending/active/complete), or null if the id is unknown — for driving per-phase HUD ticks and objective checklists.
149
173
  - `resistanceScale` (function): function resistanceScale<TCategory extends string = string, TProperty extends string = string>(matrix: ResistanceMatrix<TCategory, TProperty>, category: TCategory | string, targetProperties: readonly (TProperty | string)[]): number — ⚠ undocumented
150
174
  - `resolveClaim` (function): function resolveClaim(pool: ClaimablePool, claimantId: string, options?: { nowMs?: number; private?: boolean }): ClaimOutcome — Resolve an authoritative claim against a pool. Idempotent: re-claiming an already-owned pool returns the same grants without re-awarding, a losing claimant gets no grants, and eligibility, reservation, and expiry are all enforced here — the authority, not UI hiding, decides ownership. Returns the updated (serializable) pool plus the grants owed.
@@ -157,10 +181,12 @@
157
181
  - `resolveResistance` (function): function resolveResistance<TCategory extends string = string, TProperty extends string = string>(matrix: ResistanceMatrix<TCategory, TProperty>, category: TCategory | string, targetProperties: readonly (TProperty | string)[]): ResistanceResult — ⚠ undocumented
158
182
  - `resolveShot` (function): function resolveShot(deps: ShotOriginDeps, from: string, aim: Aim, policy: ShotOriginPolicy = { kind: "eye" }): ResolvedShot | null — ⚠ undocumented
159
183
  - `resolveStatusApplication` (function): function resolveStatusApplication(input: StatusApplicationInput): StatusApplicationOutcome — Resolve one status application: reject on immunity, roll landing chance against injected RNG, scale magnitude/duration by the matchup, then fold into any existing instance per the stacking policy. Deterministic given a seeded `rng`; pure over serializable data, so authority can run it and replicate only the returned `instance`.
184
+ - `resolveVfxPreset` (function): function resolveVfxPreset(name: string | undefined, overrides?: Partial<VfxPreset>): VfxPreset — Resolve a named visual flavor into a concrete `{ kind, color, durationMs?, radius? }`, with any provided `overrides` winning field by field. An unknown name resolves to {@link DEFAULT_VFX_PRESET} rather than throwing, so a flavor typo degrades to a visible spark. Used by `ctx.scene.entity.vfx({ preset })`; call it directly when you need the raw numbers (e.g. to seed a retained `vfxInstance` from the same vocabulary).
160
185
  - `rollCheck` (function): function rollCheck(input: CheckInput, rng: () => number = Math.random): CheckResult — Resolve a tabletop-style pass/fail roll against a target number with modifiers and crit/fumble bands.
161
186
  - `startEncounter` (function): function startEncounter<TData>(config: EncounterConfig<TData>, state: EncounterState<TData>, ctx: EncounterContext = {}): EncounterStep<TData> — Start a created encounter: enter the first root phase, descending to its first leaf, and return the initial enter/spawn events. A no-op (with no events) on an already-started or empty encounter. Mutates and returns `state`.
162
187
  - `tierAt` (function): function tierAt(value: number, tiers: readonly MeterTier[]): string | null — The highest tier id whose `at` threshold `value` has reached, or `null` below every tier — the pure lookup `createAccumulatorMeter`/`createEventMeter` call on every `add`/`tick`.
163
188
  - `updateEncounter` (function): function updateEncounter<TData>(config: EncounterConfig<TData>, state: EncounterState<TData>, dt: number, ctx: EncounterContext = {}): EncounterStep<TData> — Advance an encounter by `dt` seconds against the caller `ctx`. Accumulates time, then repeatedly: evaluates the cursor node's completion predicate; on completion, either re-runs its subtree (`repeat`) or marks it complete and advances to the next sibling — bubbling up to parent groups whose children are exhausted — entering the next chain and emitting enter/spawn/complete events. Emits a final `done` event when the last phase completes. Bounded per call by node count; mutates and returns `state`.
189
+ - `vfxPresets` (const): const vfxPresets: { readonly arrow: { readonly kind: "projectile"; readonly color: 14207392; }; readonly bolt: { readonly kind: "projectile"; readonly color: 16777215; }; readonly fireball: { readonly kind: "projectile"; readonly color: 16738842; readonly durationMs: 460; }; readonly firebolt: { rea… — Ready-made visual flavors for the things players actually shoot, swing, and cast — so `ctx.scene.entity.vfx({ preset: "arrow", from, to })` renders a visible bolt with zero tuning, and `"lightning"`, `"web"`, `"slash"`, `"shield"`, `"heal"`, `"explosion"` likewise just work. Each entry only picks a `kind` (`projectile | beam | nova | glow | spark`), a color, and a lifetime; it is visual vocabulary (like named CSS colors), not a gameplay archetype — combat numbers stay in the caller's own effect/ability data. Any field passed to `vfx()` overrides the preset, and unknown names fall back to `spark` so a flavor typo still shows *something* rather than nothing.
164
190
 
165
191
  ## @jgengine/core/combat/abilityKit
166
192
 
@@ -340,7 +366,7 @@
340
366
  - `ReceiveMap` (type): type ReceiveMap = Record<string, ReceiveRule> — ⚠ undocumented
341
367
  - `ReceiveRule` (interface): interface ReceiveRule — ⚠ undocumented
342
368
  - `SingleTargetEffectInput` (interface): interface SingleTargetEffectInput — ⚠ undocumented
343
- - `createEffectSystem` (function): function createEffectSystem(deps: EffectSystemDeps): EffectSystem — Apply, stack, and tick timed status effects — buffs, debuffs, DoTs — on entities.
369
+ - `createEffectSystem` (function): function createEffectSystem(deps: EffectSystemDeps): EffectSystem — Resolve direct or area resource effects through caller-owned stat pools, including ordered absorption, restoration, modifiers, and lethal context.
344
370
  - `resolveAreaTargets` (function): function resolveAreaTargets(spatial: CombatSpatialDeps, input: AreaTargetInput, accept?: (instanceId: string) => boolean): AreaTarget[] — ⚠ undocumented
345
371
 
346
372
  ## @jgengine/core/combat/encounterSequence
@@ -390,6 +416,7 @@
390
416
  - `Magazine` (interface): interface Magazine — A per-weapon magazine: discrete loaded rounds, a timed reload that refills from a reserve pool, and the reserve-pool interaction itself — the primitive that replaces hand-rolling mag size, reload delay, and reserve bookkeeping per game (#536.2).
391
417
  - `MagazineConfig` (interface): interface MagazineConfig — Tuning for `createMagazine`: mag capacity, reload delay, and where reserve ammo is drawn from.
392
418
  - `MagazineReserve` (interface): interface MagazineReserve — Draws ammo for a `Magazine`'s reload from wherever the reserve pool actually lives.
419
+ - `MagazineSnapshot` (interface): interface MagazineSnapshot — Complete serializable state for a {@link Magazine}; `reserve: null` means infinite reserve.
393
420
  - `createMagazine` (function): function createMagazine(config: MagazineConfig): Magazine — Builds a {@link Magazine}: discrete loaded ammo, a timed reload, and reserve-pool interaction.
394
421
 
395
422
  ## @jgengine/core/combat/projectiles
@@ -516,6 +543,25 @@
516
543
  - `ThresholdTrackerSnapshot` (interface): interface ThresholdTrackerSnapshot — Serializable tracker state for save/load.
517
544
  - `createThresholdTracker` (function): function createThresholdTracker(config: ThresholdCrossingConfig): ThresholdTracker — Track a numeric source against named thresholds and emit ordered crossing events with direction, hysteresis, once/repeat policy, and skipped-threshold handling. Deterministic and serializable; drives phase transitions, armor breaks, resource warnings, and any other threshold consumer.
518
545
 
546
+ ## @jgengine/core/combat/vfxPresets
547
+
548
+ - `DEFAULT_VFX_PRESET` (const): const DEFAULT_VFX_PRESET: VfxPresetName — The fallback flavor used when a `vfx()` call names an unknown preset — so a typo still shows a spark instead of nothing.
549
+ - `VfxPreset` (interface): interface VfxPreset — A named visual flavor for a one-shot `combat.vfx` burst: the archetype (`kind`), a `0xRRGGBB` tint, and optional default `durationMs`/`radius`. Purely presentation — a preset never touches damage, range, or any authoritative value; it just answers "what does this attack look like" so a game does not hand-roll colors and archetypes.
550
+ - `VfxPresetName` (type): type VfxPresetName = keyof typeof vfxPresets — A named key into {@link vfxPresets} (`"arrow" | "fireball" | "lightning" | "web" | "slash" | "shield" | "heal" | ...`).
551
+ - `resolveVfxPreset` (function): function resolveVfxPreset(name: string | undefined, overrides?: Partial<VfxPreset>): VfxPreset — Resolve a named visual flavor into a concrete `{ kind, color, durationMs?, radius? }`, with any provided `overrides` winning field by field. An unknown name resolves to {@link DEFAULT_VFX_PRESET} rather than throwing, so a flavor typo degrades to a visible spark. Used by `ctx.scene.entity.vfx({ preset })`; call it directly when you need the raw numbers (e.g. to seed a retained `vfxInstance` from the same vocabulary).
552
+ - `vfxPresets` (const): const vfxPresets: { readonly arrow: { readonly kind: "projectile"; readonly color: 14207392; }; readonly bolt: { readonly kind: "projectile"; readonly color: 16777215; }; readonly fireball: { readonly kind: "projectile"; readonly color: 16738842; readonly durationMs: 460; }; readonly firebolt: { rea… — Ready-made visual flavors for the things players actually shoot, swing, and cast — so `ctx.scene.entity.vfx({ preset: "arrow", from, to })` renders a visible bolt with zero tuning, and `"lightning"`, `"web"`, `"slash"`, `"shield"`, `"heal"`, `"explosion"` likewise just work. Each entry only picks a `kind` (`projectile | beam | nova | glow | spark`), a color, and a lifetime; it is visual vocabulary (like named CSS colors), not a gameplay archetype — combat numbers stay in the caller's own effect/ability data. Any field passed to `vfx()` overrides the preset, and unknown names fall back to `spark` so a flavor typo still shows *something* rather than nothing.
553
+
554
+ ## @jgengine/core/combat/weaponFire
555
+
556
+ - `FireCadence` (interface): interface FireCadence — A minimal minimum-interval rate gate for a repeated action — weapon fire, ability spam, any "no faster than N per second" rule. Deterministic and serializable: its whole state is the elapsed time since the last action, exposed via {@link FireCadence.elapsedMs}.
557
+ - `FireCadenceConfig` (interface): interface FireCadenceConfig — Tuning for {@link createFireCadence}: the minimum interval between actions and whether it starts ready.
558
+ - `WeaponFireResult` (interface): interface WeaponFireResult<THit> — The result of a {@link WeaponRuntime.fire} attempt. On `"fired"`, `hits` is whatever the caller's raycast returned and `resolutions` holds the portable damage provenance for each hit that produced a {@link DamageHitInput}; on any blocked status both are empty and no round was spent.
559
+ - `WeaponFireStatus` (type): type WeaponFireStatus = "fired" | "cooling" | "empty" | "reloading" — Why a {@link WeaponRuntime.fire} attempt resolved the way it did.
560
+ - `WeaponRuntime` (interface): interface WeaponRuntime<TAim, THit> — A composed weapon fire controller: a {@link FireCadence} gate, an optional {@link Magazine}, the caller's raycast, and portable {@link resolveDamageHit} damage resolution wired together. The caller retains ownership of models, animations, muzzle flash, audio, input, camera, aiming, and the raycaster itself.
561
+ - `WeaponRuntimeConfig` (interface): interface WeaponRuntimeConfig<TAim, THit> — Configuration for {@link createWeaponRuntime}. Everything time/ammo-related is plain data; the two callbacks are where the caller keeps ownership of aiming, its own raycaster/projectile query, and how a hit maps to damage. There is deliberately no default weapon, projectile, or damage table here — those are game content.
562
+ - `createFireCadence` (function): function createFireCadence(config: FireCadenceConfig): FireCadence — Build a {@link FireCadence} rate gate. Reach for this instead of hand-tracking a `lastFiredAt` timestamp and comparing against a fire interval per game.
563
+ - `createWeaponRuntime` (function): function createWeaponRuntime<TAim, THit>(config: WeaponRuntimeConfig<TAim, THit>): WeaponRuntime<TAim, THit> — Compose a portable weapon fire controller from the existing generic primitives — a {@link FireCadence} gate, an optional {@link Magazine}, a caller-provided raycast, and portable {@link resolveDamageHit} damage resolution. No `GameContext`, entity store, renderer, or default weapon is involved; the caller owns aiming, its raycaster, and all presentation, and the result carries full damage provenance for authority to apply and replicate.
564
+
519
565
  ## @jgengine/core/stats/accumulatorMeter
520
566
 
521
567
  - `AccumulatorMeter` (interface): interface AccumulatorMeter — ⚠ undocumented
@@ -540,10 +586,37 @@
540
586
  - `CheckResult` (interface): interface CheckResult — ⚠ undocumented
541
587
  - `rollCheck` (function): function rollCheck(input: CheckInput, rng: () => number = Math.random): CheckResult — Resolve a tabletop-style pass/fail roll against a target number with modifiers and crit/fumble bands.
542
588
 
589
+ ## @jgengine/core/stats/spawnLevelStats
590
+
591
+ - `CreatureStatInstance` (interface): interface CreatureStatInstance<TStat extends string> — A single creature's serializable stat record — a reference to its species (`speciesId`), the current `level`, the per-stat count of distributed points, and how many post-capture domestic levels have been spent. Plain data: round-trips through JSON, save/load, and multiplayer sync. Resolve display values with {@link resolveSpawnStats}.
592
+ - `SpawnSpeciesDef` (type): type SpawnSpeciesDef<TStat extends string> = Record<TStat, SpawnStatDef> — A species' stat table: every stat the creature owns keyed to its {@link SpawnStatDef}. The key order is the deterministic order random points are distributed over, so a serialized species definition reproduces the same rolls under the same rng sequence.
593
+ - `SpawnStatDef` (interface): interface SpawnStatDef — Per-stat growth definition for one species. `base` is the level-1 value of the stat; `growth` is how much a single distributed point adds, expressed as a fraction of `base` — so a point on a `growth: 0.1` stat is worth +10% of base. The resolved value is `base * (1 + points * growth)`.
594
+ - `applyCaptureEffectiveness` (function): function applyCaptureEffectiveness<TStat extends string>(species: SpawnSpeciesDef<TStat>, instance: CreatureStatInstance<TStat>, effectiveness: number, rng: () => number): CaptureResult<TStat> — Apply a capture at the given effectiveness: compute bonus levels via {@link captureBonusLevels}, distribute those extra points randomly with the injected rng, and return the post-capture instance (level raised to the tamed total) alongside the granted bonus levels and tamed level. The input instance is not mutated.
595
+ - `captureBonusLevels` (function): function captureBonusLevels(level: number, effectiveness: number): number — Bonus levels granted by a capture at the given effectiveness: `floor(level * clamp01(effectiveness) / 2)`. A perfect (1.0) capture grants half the wild level; a botched (0) capture grants none. Effectiveness outside `[0,1]` clamps.
596
+ - `resolveSpawnStats` (function): function resolveSpawnStats<TStat extends string>(species: SpawnSpeciesDef<TStat>, instance: CreatureStatInstance<TStat>): Record<TStat, number> — Resolve an instance's live stat values from its distributed points: `value = base * (1 + points * growth)` per stat. Stats with zero points return their base.
597
+ - `restoreSpawnInstance` (function): function restoreSpawnInstance<TStat extends string>(data: CreatureStatInstance<TStat>): CreatureStatInstance<TStat> — Rehydrate a creature stat instance from decoded data (e.g. `JSON.parse` output), returning a fresh deep copy the caller owns. Inverse of {@link snapshotSpawnInstance}.
598
+ - `rollSpawnInstance` (function): function rollSpawnInstance<TStat extends string>(speciesId: string, species: SpawnSpeciesDef<TStat>, level: number, rng: () => number): CreatureStatInstance<TStat> — Roll a fresh wild creature at `level`: distribute `(level - 1)` points randomly across the species' stats using the injected rng and return a serializable {@link CreatureStatInstance}. Deterministic — the same rng sequence yields the same point spread. Levels below 1 clamp to 1 (no points). Use `seededRng` from `../random/rng` for a persisted, reproducible stream.
599
+ - `snapshotSpawnInstance` (function): function snapshotSpawnInstance<TStat extends string>(instance: CreatureStatInstance<TStat>): CreatureStatInstance<TStat> — Deep-copy an instance into a detached, serializable record — the transport/save counterpart of {@link restoreSpawnInstance}. Copies the points map so later edits don't leak into the snapshot.
600
+ - `spendDomesticLevel` (function): function spendDomesticLevel<TStat extends string>(instance: CreatureStatInstance<TStat>, stat: TStat, options?: SpendLevelOptions): SpendLevelResult<TStat> — Spend one earned post-capture level into `stat`, adding a single growth increment (one point) and raising both `level` and `domesticLevels`. When `maxDomesticLevels` is set and already reached, the spend is refused (`spent: false`) and the instance is returned untouched. Pure: returns a new instance rather than mutating the input.
601
+
543
602
  ## @jgengine/core/stats/statModifiers
544
603
 
545
604
  - `CreateStatsOptions` (interface): interface CreateStatsOptions — ⚠ undocumented
546
605
  - `StatModifier` (interface): interface StatModifier — ⚠ undocumented
547
606
  - `StatModifierSet` (type): type StatModifierSet<TStat extends string> = Partial<Record<TStat, StatModifier>> — ⚠ undocumented
548
607
  - `Stats` (interface): interface Stats<TStat extends string> — ⚠ undocumented
608
+ - `StatsSnapshot` (interface): interface StatsSnapshot<TStat extends string> — Complete plain-data state for a {@link Stats} runtime.
549
609
  - `createStats` (function): function createStats<TStat extends string>(base: Record<TStat, number>, options?: CreateStatsOptions): Stats<TStat> — A stat block whose base values take stacking, timed buffs and debuffs, resolving the modified value on read.
610
+
611
+ ## @jgengine/core/stats/statPool
612
+
613
+ - `StatPool` (interface): interface StatPool — Serializable state for any bounded named resource. The caller defines what the resource means.
614
+ - `StatPoolAccess` (interface): interface StatPoolAccess<TOwnerId extends string = string, TStatId extends string = string> — Structural bridge to caller-owned resource state. Implement it over an ECS, Redux/Zustand store, plain object, database-backed model, or JGengine's native entity stats without moving ownership into the SDK.
615
+ - `StatPoolAccessResult` (type): type StatPoolAccessResult = | ({ status: "ok" } & StatPoolChange) | { status: "rejected"; reason: string } — Outcome of changing a pool through {@link StatPoolAccess}.
616
+ - `StatPoolChange` (interface): interface StatPoolChange — Inspectable result of a bounded pool change.
617
+ - `StatPoolInput` (interface): interface StatPoolInput — Input used to create a normalized pool. `current` defaults to `max`; `min` defaults to zero.
618
+ - `StatPoolPatch` (interface): interface StatPoolPatch — Partial update applied without mutating the previous pool.
619
+ - `applyStatPoolDelta` (function): function applyStatPoolDelta<TOwnerId extends string, TStatId extends string>(access: StatPoolAccess<TOwnerId, TStatId>, ownerId: TOwnerId, statId: TStatId, amount: number): StatPoolAccessResult — Read, transition, and write a caller-owned pool through the structural adapter. Positive amounts increase the resource; negative amounts decrease it. The adapter receives a complete replacement value, never a hidden mutation.
620
+ - `changeStatPool` (function): function changeStatPool(pool: StatPool, amount: number): StatPoolChange — Pure bounded delta transition with the exact applied amount and boundary flags.
621
+ - `createStatPool` (function): function createStatPool(input: StatPoolInput): StatPool — Normalize caller data into a complete plain-data pool.
622
+ - `patchStatPool` (function): function patchStatPool(pool: StatPool, patch: StatPoolPatch): StatPool — Apply new bounds/current to a pool, clamping the result and leaving the input untouched.
@@ -12,6 +12,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
12
12
 
13
13
  - `createAntiOneShotPolicy` (function) · `import { createAntiOneShotPolicy } from "@jgengine/core/combat"`
14
14
 
15
+ ## capture-bonus-levels — convert wild level and capture effectiveness into bonus levels
16
+
17
+ - `captureBonusLevels` (function) · `import { captureBonusLevels } from "@jgengine/core/stats/spawnLevelStats"`
18
+
19
+ ## capture-effectiveness-tame — apply capture effectiveness to grant and distribute bonus levels
20
+
21
+ - `applyCaptureEffectiveness` (function) · `import { applyCaptureEffectiveness } from "@jgengine/core/stats/spawnLevelStats"`
22
+
15
23
  ## cast-bar — run a channeled cast timer that movement or damage can interrupt
16
24
 
17
25
  - `createCastRunner` (function) · `import { createCastRunner } from "@jgengine/core/combat"`
@@ -56,6 +64,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
56
64
 
57
65
  - `rollCheck` (function) · `import { rollCheck } from "@jgengine/core/combat"`
58
66
 
67
+ ## domestic-level-spend — spend an earned post-capture level into a chosen stat under a cap
68
+
69
+ - `spendDomesticLevel` (function) · `import { spendDomesticLevel } from "@jgengine/core/stats/spawnLevelStats"`
70
+
59
71
  ## dot-field — track stacking damage-over-time effects and drain damage each frame
60
72
 
61
73
  - `createDotField` (function) · `import { createDotField } from "@jgengine/core/combat"`
@@ -108,6 +120,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
108
120
 
109
121
  - `createEventMeter` (function) · `import { createEventMeter } from "@jgengine/core/combat"`
110
122
 
123
+ ## fire-cadence — a serializable minimum-interval rate gate for repeated actions (weapon fire, ability spam)
124
+
125
+ - `createFireCadence` (function) · `import { createFireCadence } from "@jgengine/core/combat"`
126
+
111
127
  ## immunity-window — reject damage during a per-target invulnerability window installed by transitions
112
128
 
113
129
  - `createImmunityWindow` (function) · `import { createImmunityWindow } from "@jgengine/core/combat"`
@@ -125,6 +141,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
125
141
 
126
142
  - `createDefensiveWindow` (function) · `import { createDefensiveWindow } from "@jgengine/core/combat"`
127
143
 
144
+ ## portable-stat-pool — apply deterministic bounded resource changes through a caller-owned stat adapter
145
+
146
+ - `applyStatPoolDelta` (function) · `import { applyStatPoolDelta } from "@jgengine/core/combat"`
147
+
128
148
  ## projectiles — spawn and advance projectiles with travel and hit resolution
129
149
 
130
150
  - `createProjectileSystem` (function) · `import { createProjectileSystem } from "@jgengine/core/combat"`
@@ -147,6 +167,22 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
147
167
  - `filterOutcomeFor` (function) · `import { filterOutcomeFor } from "@jgengine/core/combat"`
148
168
  - `resolveClaim` (function) · `import { resolveClaim } from "@jgengine/core/combat"`
149
169
 
170
+ ## spawn-instance-restore — rehydrate a creature stat instance from decoded snapshot data
171
+
172
+ - `restoreSpawnInstance` (function) · `import { restoreSpawnInstance } from "@jgengine/core/stats/spawnLevelStats"`
173
+
174
+ ## spawn-instance-snapshot — deep-copy a creature stat instance into a serializable record
175
+
176
+ - `snapshotSpawnInstance` (function) · `import { snapshotSpawnInstance } from "@jgengine/core/stats/spawnLevelStats"`
177
+
178
+ ## spawn-level-roll — distribute a wild creature's spawn level into random per-stat points
179
+
180
+ - `rollSpawnInstance` (function) · `import { rollSpawnInstance } from "@jgengine/core/stats/spawnLevelStats"`
181
+
182
+ ## spawn-stat-values — resolve a creature's per-stat values from its distributed points
183
+
184
+ - `resolveSpawnStats` (function) · `import { resolveSpawnStats } from "@jgengine/core/stats/spawnLevelStats"`
185
+
150
186
  ## stagger-meter — accumulate an ailment buildup (bleed, freeze) until it procs
151
187
 
152
188
  - `createBuildupMeter` (function) · `import { createBuildupMeter } from "@jgengine/core/combat"`
@@ -156,14 +192,38 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
156
192
 
157
193
  - `createStats` (function) · `import { createStats } from "@jgengine/core/combat"`
158
194
 
195
+ ## stat-pool — create a serializable current/min/max pool for any caller-named resource
196
+
197
+ - `createStatPool` (function) · `import { createStatPool } from "@jgengine/core/combat"`
198
+
199
+ ## stat-pool-change — immutably increase or decrease any named bounded resource with clamp evidence
200
+
201
+ - `changeStatPool` (function) · `import { changeStatPool } from "@jgengine/core/combat"`
202
+
203
+ ## stat-pool-patch — immutably retune a bounded resource's current value or bounds
204
+
205
+ - `patchStatPool` (function) · `import { patchStatPool } from "@jgengine/core/combat"`
206
+
159
207
  ## status-application — resolve a data-defined status application with chance, matchup scaling, and stacking policy
160
208
 
161
209
  - `resolveStatusApplication` (function) · `import { resolveStatusApplication } from "@jgengine/core/combat"`
162
210
 
163
- ## status-effects — apply and tick timed status effects and buffs on entities
211
+ ## status-effects — commit direct or area effects through caller-owned stat pools with ordered spillover and lethal results
164
212
 
165
213
  - `createEffectSystem` (function) · `import { createEffectSystem } from "@jgengine/core/combat"`
166
214
 
167
215
  ## threshold-crossings — emit ordered crossing events as a numeric source passes named thresholds with hysteresis and once/repeat policy
168
216
 
169
217
  - `createThresholdTracker` (function) · `import { createThresholdTracker } from "@jgengine/core/combat"`
218
+
219
+ ## vfx-preset-resolve — turn a named attack flavor into concrete vfx kind/color/duration with per-field overrides
220
+
221
+ - `resolveVfxPreset` (function) · `import { resolveVfxPreset } from "@jgengine/core/combat"`
222
+
223
+ ## vfx-presets — render a named attack visual (arrow, fireball, lightning, web, slash, shield, heal, explosion) in one call
224
+
225
+ - `vfxPresets` (const) · `import { vfxPresets } from "@jgengine/core/combat"`
226
+
227
+ ## weapon-runtime — compose cadence, magazine, a caller-owned raycast, and portable damage resolution into a fire controller
228
+
229
+ - `createWeaponRuntime` (function) · `import { createWeaponRuntime } from "@jgengine/core/combat"`