@jgengine/core 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/ai/populationDirector.d.ts +103 -0
- package/dist/ai/populationDirector.js +190 -0
- package/dist/ai/pursuit.d.ts +56 -0
- package/dist/ai/pursuit.js +53 -0
- package/dist/authoring.d.ts +1 -1
- package/dist/authoring.js +1 -1
- package/dist/combat.d.ts +1 -0
- package/dist/combat.js +1 -0
- package/dist/devtools/controls.d.ts +38 -0
- package/dist/devtools/controls.js +181 -0
- package/dist/devtools/devtools.d.ts +5 -197
- package/dist/devtools/devtools.js +38 -774
- package/dist/devtools/discover.d.ts +32 -0
- package/dist/devtools/discover.js +240 -0
- package/dist/devtools/fallbackSeams.d.ts +50 -0
- package/dist/devtools/fallbackSeams.js +111 -0
- package/dist/devtools/frame.d.ts +15 -0
- package/dist/devtools/frame.js +247 -0
- package/dist/devtools/logs.d.ts +15 -0
- package/dist/devtools/logs.js +86 -0
- package/dist/devtools/overrides.d.ts +16 -0
- package/dist/devtools/overrides.js +66 -0
- package/dist/devtools/types.d.ts +212 -0
- package/dist/devtools/types.js +1 -0
- package/dist/economy/auctionBook.d.ts +122 -0
- package/dist/economy/auctionBook.js +213 -0
- package/dist/economy/priceHistory.d.ts +44 -0
- package/dist/economy/priceHistory.js +89 -0
- package/dist/economy/unlockPoints.d.ts +92 -0
- package/dist/economy/unlockPoints.js +103 -0
- package/dist/editor/commandApply.d.ts +5 -0
- package/dist/editor/commandApply.js +755 -0
- package/dist/editor/commands.d.ts +48 -3
- package/dist/editor/commands.js +40 -754
- package/dist/editor/directives.d.ts +94 -0
- package/dist/editor/directives.js +206 -0
- package/dist/editor/document.d.ts +37 -2
- package/dist/editor/document.js +422 -6
- package/dist/editor/entityCatalog.d.ts +42 -0
- package/dist/editor/entityCatalog.js +101 -0
- package/dist/editor/environment.d.ts +74 -0
- package/dist/editor/environment.js +169 -0
- package/dist/editor/index.d.ts +26 -1
- package/dist/editor/index.js +8 -1
- package/dist/editor/minimap.d.ts +31 -0
- package/dist/editor/minimap.js +73 -0
- package/dist/editor/streamer.d.ts +66 -0
- package/dist/editor/streamer.js +68 -0
- package/dist/editor/types.d.ts +143 -0
- package/dist/editor/world.d.ts +136 -0
- package/dist/editor/world.js +303 -0
- package/dist/game/breeding.d.ts +161 -0
- package/dist/game/breeding.js +181 -0
- package/dist/game/cameraConfig.d.ts +451 -0
- package/dist/game/cameraConfig.js +1 -0
- package/dist/game/defineGame.d.ts +16 -9
- package/dist/game/defineGame.js +14 -6
- package/dist/game/defineSystem.d.ts +2 -0
- package/dist/game/defineSystem.js +2 -0
- package/dist/game/events.d.ts +28 -0
- package/dist/game/playableGame.d.ts +34 -435
- package/dist/game/playableGame.js +1 -1
- package/dist/game/progression.d.ts +35 -0
- package/dist/game/progression.js +19 -0
- package/dist/game/quest.js +1 -3
- package/dist/game/tribe.d.ts +184 -0
- package/dist/game/tribe.js +432 -0
- package/dist/gameplay.d.ts +3 -1
- package/dist/gameplay.js +3 -1
- package/dist/inventory/encumbrance.d.ts +69 -0
- package/dist/inventory/encumbrance.js +68 -0
- package/dist/item/durability.js +1 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +24 -1
- package/dist/movement/movementModel.d.ts +15 -1
- package/dist/movement/movementModel.js +33 -15
- package/dist/movement/playerMovement.js +102 -2
- package/dist/multiplayer/combatSnapshot.js +13 -8
- package/dist/multiplayer/presenceModel.d.ts +2 -0
- package/dist/multiplayer/presenceModel.js +8 -0
- package/dist/physics/drivableVehicle.d.ts +2 -0
- package/dist/physics/drivableVehicle.js +1 -1
- package/dist/physics/flightDynamics.d.ts +82 -0
- package/dist/physics/flightDynamics.js +165 -0
- package/dist/physics/gravityField.d.ts +25 -0
- package/dist/physics/gravityField.js +41 -0
- package/dist/physics/kinematicVehicle.d.ts +87 -0
- package/dist/physics/kinematicVehicle.js +193 -11
- package/dist/physics/physicsWorld.d.ts +26 -0
- package/dist/physics/physicsWorld.js +109 -117
- package/dist/physics/vehicleObstacles.d.ts +44 -0
- package/dist/physics/vehicleObstacles.js +115 -0
- package/dist/random/rng.d.ts +4 -1
- package/dist/random/rng.js +4 -1
- package/dist/runtime/adapter.d.ts +13 -0
- package/dist/runtime/adapter.js +13 -0
- package/dist/runtime/context/combat.d.ts +51 -0
- package/dist/runtime/context/combat.js +130 -0
- package/dist/runtime/context/deathLoot.d.ts +30 -0
- package/dist/runtime/context/deathLoot.js +39 -0
- package/dist/runtime/context/player.d.ts +48 -0
- package/dist/runtime/context/player.js +125 -0
- package/dist/runtime/context/scene.d.ts +59 -0
- package/dist/runtime/context/scene.js +291 -0
- package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
- package/dist/runtime/context/snapshotCodecs.js +26 -0
- package/dist/runtime/descriptors/baseline.js +26 -3
- package/dist/runtime/descriptors/features.d.ts +5 -0
- package/dist/runtime/descriptors/features.js +20 -2
- package/dist/runtime/gameContext.d.ts +2 -428
- package/dist/runtime/gameContext.js +88 -501
- package/dist/runtime/gameContextTypes.d.ts +467 -0
- package/dist/runtime/gameContextTypes.js +1 -0
- package/dist/runtime/headlessRunner.d.ts +4 -2
- package/dist/runtime/headlessRunner.js +1 -0
- package/dist/runtime/hostPolicy.d.ts +51 -0
- package/dist/runtime/hostPolicy.js +68 -0
- package/dist/runtime/hostedGameRunner.d.ts +3 -1
- package/dist/runtime/hostedGameRunner.js +1 -0
- package/dist/runtime/hostedWorldSession.d.ts +3 -1
- package/dist/runtime/hostedWorldSession.js +1 -0
- package/dist/runtime/worldSnapshot.d.ts +13 -1
- package/dist/runtime/worldSnapshot.js +15 -3
- package/dist/scene/assetCatalog.d.ts +5 -0
- package/dist/scene/assetSpace.js +1 -3
- package/dist/scene/authoredTriggers.d.ts +50 -0
- package/dist/scene/authoredTriggers.js +114 -0
- package/dist/scene/builtinSceneKinds.js +8 -3
- package/dist/scene/colliders.d.ts +58 -0
- package/dist/scene/colliders.js +108 -0
- package/dist/scene/collisionMesh.d.ts +94 -0
- package/dist/scene/collisionMesh.js +699 -0
- package/dist/scene/companion.d.ts +147 -0
- package/dist/scene/companion.js +193 -0
- package/dist/scene/entityStats.d.ts +7 -9
- package/dist/scene/entityStats.js +10 -14
- package/dist/scene/sceneKinds.d.ts +16 -0
- package/dist/scene/sceneKinds.js +4 -0
- package/dist/scene/sceneRaycast.js +15 -0
- package/dist/stats/spawnLevelStats.d.ts +110 -0
- package/dist/stats/spawnLevelStats.js +140 -0
- package/dist/stats/statPool.d.ts +70 -0
- package/dist/stats/statPool.js +59 -0
- package/dist/store/changeSignal.d.ts +3 -0
- package/dist/store/changeSignal.js +2 -0
- package/dist/store/observableKeyedStore.d.ts +2 -0
- package/dist/store/observableKeyedStore.js +1 -0
- package/dist/survival/decayMeter.js +1 -3
- package/dist/survival/regionHealth.js +1 -3
- package/dist/ui/hudLayout.d.ts +5 -0
- package/dist/ui/hudLayout.js +17 -1
- package/dist/world/authoredEntities.d.ts +29 -0
- package/dist/world/authoredEntities.js +29 -0
- package/dist/world/authoredObjects.d.ts +15 -1
- package/dist/world/authoredObjects.js +13 -1
- package/dist/world/authoredSpawn.d.ts +39 -0
- package/dist/world/authoredSpawn.js +37 -0
- package/dist/world/buildSockets.d.ts +152 -0
- package/dist/world/buildSockets.js +188 -0
- package/dist/world/cityBlocks.d.ts +152 -0
- package/dist/world/cityBlocks.js +670 -0
- package/dist/world/cityContent.d.ts +77 -0
- package/dist/world/cityContent.js +261 -0
- package/dist/world/cityGeometry.d.ts +114 -0
- package/dist/world/cityGeometry.js +573 -0
- package/dist/world/cityKind.d.ts +314 -0
- package/dist/world/cityKind.js +1578 -0
- package/dist/world/features.d.ts +18 -3
- package/dist/world/features.js +10 -1
- package/dist/world/grassKind.js +2 -2
- package/dist/world/markers.d.ts +50 -4
- package/dist/world/markers.js +49 -0
- package/dist/world/massing.js +1 -1
- package/dist/world/minimapBake.d.ts +78 -0
- package/dist/world/minimapBake.js +208 -0
- package/dist/world/minimapTrack.d.ts +17 -0
- package/dist/world/minimapTrack.js +22 -0
- package/dist/world/pathNetwork.d.ts +159 -0
- package/dist/world/pathNetwork.js +684 -0
- package/dist/world/place.d.ts +166 -0
- package/dist/world/place.js +96 -0
- package/dist/world/placeAsset.js +3 -1
- package/dist/world/poleLineKind.d.ts +40 -0
- package/dist/world/poleLineKind.js +97 -0
- package/dist/world/polyline.js +9 -2
- package/dist/world/resourceNode.d.ts +109 -0
- package/dist/world/resourceNode.js +140 -0
- package/dist/world/scatter.js +2 -20
- package/dist/world/streets.d.ts +29 -0
- package/dist/world/streets.js +55 -0
- package/dist/world/terraform.js +58 -88
- package/dist/world/terrain.d.ts +45 -0
- package/dist/world/terrain.js +78 -0
- package/dist/world.d.ts +14 -5
- package/dist/world.js +12 -3
- package/package.json +1 -1
- package/skills/game-design/SKILL.md +28 -0
- package/skills/game-design/agents/openai.yaml +4 -0
- package/skills/game-design/references/application-playbook.md +170 -0
- package/skills/game-design/references/field-guide.md +288 -0
- package/skills/jgengine/SKILL.md +7 -4
- package/skills/jgengine/api.md +172 -79
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-combat/SKILL.md +5 -1
- package/skills/jgengine-combat/api.md +36 -0
- package/skills/jgengine-combat/capabilities.md +44 -0
- package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
- package/skills/jgengine-combat/reference.md +12 -2
- package/skills/jgengine-gameplay/api.md +152 -26
- package/skills/jgengine-gameplay/capabilities.md +78 -2
- package/skills/jgengine-gameplay/reference-systems.md +1 -5
- package/skills/jgengine-world/SKILL.md +13 -0
- package/skills/jgengine-world/api.md +419 -15
- package/skills/jgengine-world/capabilities.md +108 -2
- package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
- package/skills/jgengine-world/reference.md +43 -4
- 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,11 @@ 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.
|
|
18
|
+
|
|
14
19
|
## Canonical pipeline
|
|
15
20
|
|
|
16
21
|
1. Acquire eligible candidates from a bounded spatial source.
|
|
@@ -31,4 +36,3 @@ Compose stages through registration/policy seams; adding a damage type, effect,
|
|
|
31
36
|
- Reward generation and allocation are different operations, especially in multiplayer.
|
|
32
37
|
- 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
38
|
- UI bars, reticles, and feedback route to `jgengine-ui`.
|
|
34
|
-
|
|
@@ -88,6 +88,12 @@
|
|
|
88
88
|
- `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
89
|
- `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
90
|
- `SpawnProviderContext` (interface): interface SpawnProviderContext — Evaluation context a spawn provider receives when its phase is entered.
|
|
91
|
+
- `StatPool` (interface): interface StatPool — Serializable state for any bounded named resource. The caller defines what the resource means.
|
|
92
|
+
- `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.
|
|
93
|
+
- `StatPoolAccessResult` (type): type StatPoolAccessResult = | ({ status: "ok" } & StatPoolChange) | { status: "rejected"; reason: string } — Outcome of changing a pool through {@link StatPoolAccess}.
|
|
94
|
+
- `StatPoolChange` (interface): interface StatPoolChange — Inspectable result of a bounded pool change.
|
|
95
|
+
- `StatPoolInput` (interface): interface StatPoolInput — Input used to create a normalized pool. `current` defaults to `max`; `min` defaults to zero.
|
|
96
|
+
- `StatPoolPatch` (interface): interface StatPoolPatch — Partial update applied without mutating the previous pool.
|
|
91
97
|
- `Stats` (interface): interface Stats<TStat extends string> — ⚠ undocumented
|
|
92
98
|
- `StatusApplicationOutcome` (interface): interface StatusApplicationOutcome — The result of one status application: the outcome, the resulting instance (if any), and the roll.
|
|
93
99
|
- `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.
|
|
@@ -109,7 +115,9 @@
|
|
|
109
115
|
- `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
116
|
- `applyRenderDeathEvent` (function): function applyRenderDeathEvent(cues: EntityRenderCues): EntityRenderCues — Marks the cue set dead after `entity.died`; sticky for the lifetime of the render component.
|
|
111
117
|
- `applyRenderHitEvent` (function): function applyRenderHitEvent(cues: EntityRenderCues): EntityRenderCues — Marks a `combat.hitReaction` pulse; the caller clears `hit` again after its own pulse window.
|
|
118
|
+
- `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
119
|
- `attackMeta` (function): function attackMeta(tags: readonly AttackTag[], extra?: Omit<AttackMeta, "tags">): AttackMeta — ⚠ undocumented
|
|
120
|
+
- `changeStatPool` (function): function changeStatPool(pool: StatPool, amount: number): StatPoolChange — Pure bounded delta transition with the exact applied amount and boundary flags.
|
|
113
121
|
- `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
122
|
- `counters` (function): function counters(meta: AttackMeta, move: CounterMove): boolean — ⚠ undocumented
|
|
115
123
|
- `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.
|
|
@@ -134,6 +142,7 @@
|
|
|
134
142
|
- `createRegenShield` (function): function createRegenShield(config: RegenShieldConfig): RegenShield — Builds a {@link RegenShield} that suppresses regen for `regenDelayMs` after each hit.
|
|
135
143
|
- `createResourcePool` (function): function createResourcePool(config: ResourcePoolConfig): ResourcePool — A regenerating resource pool — mana, stamina, energy — that actions spend from and that refills over time.
|
|
136
144
|
- `createStaggerMeter` (function): function createStaggerMeter(config: StaggerMeterConfig): StaggerMeter — Build a stagger/poise gauge from landed hits toward a break threshold that staggers the target.
|
|
145
|
+
- `createStatPool` (function): function createStatPool(input: StatPoolInput): StatPool — Normalize caller data into a complete plain-data pool.
|
|
137
146
|
- `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
147
|
- `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.
|
|
139
148
|
- `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.
|
|
@@ -145,6 +154,7 @@
|
|
|
145
154
|
- `isBlockable` (function): function isBlockable(meta: AttackMeta): boolean — ⚠ undocumented
|
|
146
155
|
- `isDodgeable` (function): function isDodgeable(meta: AttackMeta): boolean — ⚠ undocumented
|
|
147
156
|
- `isParryable` (function): function isParryable(meta: AttackMeta): boolean — ⚠ undocumented
|
|
157
|
+
- `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
158
|
- `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
159
|
- `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
160
|
- `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.
|
|
@@ -540,6 +550,19 @@
|
|
|
540
550
|
- `CheckResult` (interface): interface CheckResult — ⚠ undocumented
|
|
541
551
|
- `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
552
|
|
|
553
|
+
## @jgengine/core/stats/spawnLevelStats
|
|
554
|
+
|
|
555
|
+
- `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}.
|
|
556
|
+
- `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.
|
|
557
|
+
- `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)`.
|
|
558
|
+
- `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.
|
|
559
|
+
- `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.
|
|
560
|
+
- `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.
|
|
561
|
+
- `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}.
|
|
562
|
+
- `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.
|
|
563
|
+
- `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.
|
|
564
|
+
- `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.
|
|
565
|
+
|
|
543
566
|
## @jgengine/core/stats/statModifiers
|
|
544
567
|
|
|
545
568
|
- `CreateStatsOptions` (interface): interface CreateStatsOptions — ⚠ undocumented
|
|
@@ -547,3 +570,16 @@
|
|
|
547
570
|
- `StatModifierSet` (type): type StatModifierSet<TStat extends string> = Partial<Record<TStat, StatModifier>> — ⚠ undocumented
|
|
548
571
|
- `Stats` (interface): interface Stats<TStat extends string> — ⚠ undocumented
|
|
549
572
|
- `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.
|
|
573
|
+
|
|
574
|
+
## @jgengine/core/stats/statPool
|
|
575
|
+
|
|
576
|
+
- `StatPool` (interface): interface StatPool — Serializable state for any bounded named resource. The caller defines what the resource means.
|
|
577
|
+
- `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.
|
|
578
|
+
- `StatPoolAccessResult` (type): type StatPoolAccessResult = | ({ status: "ok" } & StatPoolChange) | { status: "rejected"; reason: string } — Outcome of changing a pool through {@link StatPoolAccess}.
|
|
579
|
+
- `StatPoolChange` (interface): interface StatPoolChange — Inspectable result of a bounded pool change.
|
|
580
|
+
- `StatPoolInput` (interface): interface StatPoolInput — Input used to create a normalized pool. `current` defaults to `max`; `min` defaults to zero.
|
|
581
|
+
- `StatPoolPatch` (interface): interface StatPoolPatch — Partial update applied without mutating the previous pool.
|
|
582
|
+
- `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.
|
|
583
|
+
- `changeStatPool` (function): function changeStatPool(pool: StatPool, amount: number): StatPoolChange — Pure bounded delta transition with the exact applied amount and boundary flags.
|
|
584
|
+
- `createStatPool` (function): function createStatPool(input: StatPoolInput): StatPool — Normalize caller data into a complete plain-data pool.
|
|
585
|
+
- `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"`
|
|
@@ -125,6 +137,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
125
137
|
|
|
126
138
|
- `createDefensiveWindow` (function) · `import { createDefensiveWindow } from "@jgengine/core/combat"`
|
|
127
139
|
|
|
140
|
+
## portable-stat-pool — apply deterministic bounded resource changes through a caller-owned stat adapter
|
|
141
|
+
|
|
142
|
+
- `applyStatPoolDelta` (function) · `import { applyStatPoolDelta } from "@jgengine/core/combat"`
|
|
143
|
+
|
|
128
144
|
## projectiles — spawn and advance projectiles with travel and hit resolution
|
|
129
145
|
|
|
130
146
|
- `createProjectileSystem` (function) · `import { createProjectileSystem } from "@jgengine/core/combat"`
|
|
@@ -147,6 +163,22 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
147
163
|
- `filterOutcomeFor` (function) · `import { filterOutcomeFor } from "@jgengine/core/combat"`
|
|
148
164
|
- `resolveClaim` (function) · `import { resolveClaim } from "@jgengine/core/combat"`
|
|
149
165
|
|
|
166
|
+
## spawn-instance-restore — rehydrate a creature stat instance from decoded snapshot data
|
|
167
|
+
|
|
168
|
+
- `restoreSpawnInstance` (function) · `import { restoreSpawnInstance } from "@jgengine/core/stats/spawnLevelStats"`
|
|
169
|
+
|
|
170
|
+
## spawn-instance-snapshot — deep-copy a creature stat instance into a serializable record
|
|
171
|
+
|
|
172
|
+
- `snapshotSpawnInstance` (function) · `import { snapshotSpawnInstance } from "@jgengine/core/stats/spawnLevelStats"`
|
|
173
|
+
|
|
174
|
+
## spawn-level-roll — distribute a wild creature's spawn level into random per-stat points
|
|
175
|
+
|
|
176
|
+
- `rollSpawnInstance` (function) · `import { rollSpawnInstance } from "@jgengine/core/stats/spawnLevelStats"`
|
|
177
|
+
|
|
178
|
+
## spawn-stat-values — resolve a creature's per-stat values from its distributed points
|
|
179
|
+
|
|
180
|
+
- `resolveSpawnStats` (function) · `import { resolveSpawnStats } from "@jgengine/core/stats/spawnLevelStats"`
|
|
181
|
+
|
|
150
182
|
## stagger-meter — accumulate an ailment buildup (bleed, freeze) until it procs
|
|
151
183
|
|
|
152
184
|
- `createBuildupMeter` (function) · `import { createBuildupMeter } from "@jgengine/core/combat"`
|
|
@@ -156,6 +188,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
156
188
|
|
|
157
189
|
- `createStats` (function) · `import { createStats } from "@jgengine/core/combat"`
|
|
158
190
|
|
|
191
|
+
## stat-pool — create a serializable current/min/max pool for any caller-named resource
|
|
192
|
+
|
|
193
|
+
- `createStatPool` (function) · `import { createStatPool } from "@jgengine/core/combat"`
|
|
194
|
+
|
|
195
|
+
## stat-pool-change — immutably increase or decrease any named bounded resource with clamp evidence
|
|
196
|
+
|
|
197
|
+
- `changeStatPool` (function) · `import { changeStatPool } from "@jgengine/core/combat"`
|
|
198
|
+
|
|
199
|
+
## stat-pool-patch — immutably retune a bounded resource's current value or bounds
|
|
200
|
+
|
|
201
|
+
- `patchStatPool` (function) · `import { patchStatPool } from "@jgengine/core/combat"`
|
|
202
|
+
|
|
159
203
|
## status-application — resolve a data-defined status application with chance, matchup scaling, and stacking policy
|
|
160
204
|
|
|
161
205
|
- `resolveStatusApplication` (function) · `import { resolveStatusApplication } from "@jgengine/core/combat"`
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Use JGengine stat pools with an existing save model
|
|
2
|
+
|
|
3
|
+
Use this recipe when a project already owns its entities and persistence but
|
|
4
|
+
needs deterministic bounded resources such as shields, energy, stamina, XP, or
|
|
5
|
+
durability. Resource names and meaning remain caller data; the portable
|
|
6
|
+
contract has no privileged health field.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
bun add @jgengine/core
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Import the focused, renderer-free module:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import {
|
|
18
|
+
applyStatPoolDelta,
|
|
19
|
+
createStatPool,
|
|
20
|
+
type StatPool,
|
|
21
|
+
type StatPoolAccess,
|
|
22
|
+
} from "@jgengine/core/stats/statPool";
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`@jgengine/core` has no runtime dependencies and does not require a JGengine
|
|
26
|
+
entity store, game loop, renderer, React provider, or backend.
|
|
27
|
+
|
|
28
|
+
## Minimal adapter
|
|
29
|
+
|
|
30
|
+
Suppose the existing project saves resources inline on its own units:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
interface UnitSave {
|
|
34
|
+
id: string;
|
|
35
|
+
resources: Record<string, StatPool>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface WorldSave {
|
|
39
|
+
units: Record<string, UnitSave>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const world: WorldSave = {
|
|
43
|
+
units: {
|
|
44
|
+
rover: {
|
|
45
|
+
id: "rover",
|
|
46
|
+
resources: {
|
|
47
|
+
energy: createStatPool({ current: 8, max: 10 }),
|
|
48
|
+
durability: createStatPool({ max: 40 }),
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const resources: StatPoolAccess = {
|
|
55
|
+
get(ownerId, statId) {
|
|
56
|
+
return world.units[ownerId]?.resources[statId] ?? null;
|
|
57
|
+
},
|
|
58
|
+
set(ownerId, statId, next) {
|
|
59
|
+
const unit = world.units[ownerId];
|
|
60
|
+
if (unit !== undefined) unit.resources[statId] = next;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The adapter performs structural reads and complete replacement writes. It does
|
|
66
|
+
not expose the caller's store implementation or hand mutation authority to the
|
|
67
|
+
engine.
|
|
68
|
+
|
|
69
|
+
## Apply a resource change
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const spent = applyStatPoolDelta(resources, "rover", "energy", -3);
|
|
73
|
+
|
|
74
|
+
if (spent.status === "ok") {
|
|
75
|
+
console.log(spent.pool.current); // 5
|
|
76
|
+
console.log(spent.applied); // -3 (after clamping)
|
|
77
|
+
console.log(spent.hitMin); // false
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Positive amounts increase a pool and negative amounts decrease it. The result
|
|
82
|
+
records the previous and next values, the exact clamped amount, and whether a
|
|
83
|
+
bound was reached. Missing owners or stat ids return a rejected result without
|
|
84
|
+
writing.
|
|
85
|
+
|
|
86
|
+
For reducer-style stores, call the pure `changeStatPool(pool, amount)` or
|
|
87
|
+
`patchStatPool(pool, patch)` transition inside the project's existing action
|
|
88
|
+
and commit the returned `pool` through that store's normal update path.
|
|
89
|
+
|
|
90
|
+
## Save and restore
|
|
91
|
+
|
|
92
|
+
`StatPool` is plain JSON data, so keep it in the project's existing save:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const serialized = JSON.stringify(world);
|
|
96
|
+
const restored = JSON.parse(serialized) as WorldSave;
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
After restore, point the same adapter at `restored`. There is no second JGengine
|
|
100
|
+
snapshot to reconcile, no registry to rebuild, and no required migration away
|
|
101
|
+
from the caller's schema. If the existing schema uses different field names,
|
|
102
|
+
translate only at `get`/`set`.
|
|
103
|
+
|
|
104
|
+
## Ownership
|
|
105
|
+
|
|
106
|
+
The existing project owns resource ids, entity ids, storage layout,
|
|
107
|
+
persistence, replication, authority, UI, scheduling, and the decision to apply
|
|
108
|
+
a change. JGengine owns normalization, clamping, pure transitions, and the
|
|
109
|
+
small structural access contract. Native JGengine entity stats implement the
|
|
110
|
+
same portable behavior through their existing API.
|
|
111
|
+
|
|
112
|
+
## Common traps
|
|
113
|
+
|
|
114
|
+
- Do not copy caller resources into a parallel `StatValueMap`; adapt the state
|
|
115
|
+
where it already lives.
|
|
116
|
+
- Do not mutate the object returned by `get`. Commit the complete returned
|
|
117
|
+
`pool` through `set` or the caller's reducer/action.
|
|
118
|
+
- Pool changes are signed: positive restores/fills, negative spends/drains.
|
|
119
|
+
- Keep formulas, regeneration schedules, damage meaning, and resource names in
|
|
120
|
+
caller code. A stat pool only enforces bounds.
|
|
121
|
+
- Persist `current`, `max`, and `min` together so a restored value is clamped
|
|
122
|
+
against the same bounds.
|
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Reference module for the [`jgengine-combat` API](SKILL.md) skill. Load this when you need the combat surface — effects, projectiles, death, feel, and abilities.
|
|
4
4
|
|
|
5
|
+
## Portable named resources
|
|
6
|
+
|
|
7
|
+
`StatPool` (`@jgengine/core/stats/statPool`) is the plain
|
|
8
|
+
`{ current, max, min }` model for any caller-named bounded resource. Existing
|
|
9
|
+
projects implement the two-method `StatPoolAccess` over their own ECS, reducer,
|
|
10
|
+
or save model, then use `applyStatPoolDelta`; reducer-style integrations use
|
|
11
|
+
the pure `createStatPool` / `patchStatPool` / `changeStatPool` transitions.
|
|
12
|
+
Native scene entity stats are a compatibility bridge over the same model. See
|
|
13
|
+
the [portable stat-pool recipe](recipes/portable-stat-pools.md) for complete
|
|
14
|
+
imports, adapter code, ownership, and JSON save/restore.
|
|
15
|
+
|
|
5
16
|
## Effects and projectiles
|
|
6
17
|
|
|
7
18
|
Effect ids are **game-defined strings**. Magnitudes **drain** stats: positive subtracts down `receive.<effect>.order` (spilling to the next stat in the order), negative restores. Heals pass a negative amount (`via: { amount: -flashHeal }`, typically read from a `weapon.heal` stat).
|
|
@@ -25,7 +36,7 @@ fireProjectile({ from, via, aim, effect }) // → shotId (pending)
|
|
|
25
36
|
settleProjectile(shotId) // authoritative → { at, hits } | rejection
|
|
26
37
|
```
|
|
27
38
|
|
|
28
|
-
`Aim = { origin, direction } | { yaw, pitch, spread? }`. A free `{ yaw, pitch }` aim defaults to `converge`: the bullet leaves the gun `muzzle` (so a tracer visibly comes from the barrel, not the camera) yet bends toward whatever the eye ray's crosshair covers, so the reticle stays truth. An explicit `{ origin, direction }` aim (enemy AI, turrets) passes through unchanged. Shot origin otherwise resolves via `ShotOriginPolicy` (`eye`: shooter position raised to eye height — 90% of the shooter's own hitbox top when colliders are known, ~1.6m otherwise — so shots trace the sightline and the crosshair is truth; `muzzle`/`camera`/`world` for custom rigs, `legacy` = raw entity position).
|
|
39
|
+
`Aim = { origin, direction } | { yaw, pitch, spread? }`. A free `{ yaw, pitch }` aim defaults to `converge`: the bullet leaves the gun `muzzle` (so a tracer visibly comes from the barrel, not the camera) yet bends toward whatever the eye ray's crosshair covers, so the reticle stays truth. An explicit `{ origin, direction }` aim (enemy AI, turrets) passes through unchanged. Shot origin otherwise resolves via `ShotOriginPolicy` (`eye`: shooter position raised to eye height — 90% of the shooter's own hitbox top when colliders are known, ~1.6m otherwise — so shots trace the sightline and the crosshair is truth; `muzzle`/`camera`/`world` for custom rigs, `legacy` = raw entity position). Hitboxes auto-fit the rendered model: an entity kind mapped in `entityModels` (or an object catalog id in `objectModels`) gets a grounded AABB matching its model's measured bounds — footprint x height composed with `scale`/`targetHeight` exactly like the renderer — so the damage box equals the visual without hand-tuning. Unmodeled entities keep the body-covering humanoid AABB default (feet to ~1.8m); unmodeled objects with a `visual.scale` get a matching grounded physical body. Catalog assets opted into `COLLISION_MESH_ASSET_IDS` (`@jgengine/assets`) go one step further: the fit raycasts the asset's actual triangles (BVH over a quantized mesh shipped in the index), so a shot through a torus hole or archway passes clean instead of hitting the fitted box — same shape on client and headless host. Explicit catalog `colliders`/`halfExtents` and per-instance `setColliders` always override the fit. Hitscan settles into per-hit effects; ballistic shots (`weapon.projectile` with `fuseTime`/`settleOn`) settle to a landing point — the handler then calls `effect({ at: settle.at, radius })`. Settling twice rejects. Prediction is never authority.
|
|
29
40
|
|
|
30
41
|
Raycasts are **object-aware**: the default raycast checks placed scene objects as well as entities, discriminated by `RaycastHit.kind` (`"entity" | "object"` — an `ObjectRaycastHit` also carries `catalogId`). A crate or wall between shooter and target blocks or absorbs the shot instead of every projectile passing through scenery; supply `ProjectileSystemDeps.objects` (`{ list(), halfExtents?(catalogId) }`, matching `ObjectStore.list()` structurally) to opt in, with a `[0.5, 0.5, 0.5]` half-extent default per object.
|
|
31
42
|
|
|
@@ -95,4 +106,3 @@ Genre systems layered over the same effects/projectiles/targeting/loot primitive
|
|
|
95
106
|
- **Spawn providers** — spawning is one optional node adapter: `spawn: { kind, params }` invoked on enter. `BUILTIN_SPAWN_PROVIDERS.points` reads a named authored scene point list from `ctx.spawnPoints` (coordinates stay in the scene, referenced by key — never embedded in the sequence); `list` echoes inline requests. Register catalog/director-backed providers via `config.spawnProviders`.
|
|
96
107
|
- **Injection & scripted control** — `injectPhase(state, phase, { after | before | childOf })` inserts a phase or branch at runtime (e.g. a boss after the final wave) into the serializable tree without disturbing active/completed nodes; `forceCompletePhase` completes the active phase regardless of its predicate for scripted skips and failure/retry, restoring the authored predicate afterward.
|
|
97
108
|
|
|
98
|
-
|