@jgengine/core 0.19.0 → 0.22.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 +364 -0
- package/dist/ai/decisionGraph.d.ts +2 -0
- package/dist/ai/decisionGraph.js +6 -0
- package/dist/ai/flock.d.ts +1 -1
- package/dist/ai/flock.js +7 -4
- package/dist/anim/animGraph.d.ts +33 -2
- package/dist/anim/animGraph.js +200 -12
- package/dist/anim/locomotionGraph.d.ts +11 -3
- package/dist/anim/locomotionGraph.js +25 -15
- package/dist/audio/audioFalloff.d.ts +4 -0
- package/dist/audio/voiceAllocator.d.ts +46 -0
- package/dist/audio/voiceAllocator.js +76 -0
- package/dist/combat/abilityKit.d.ts +19 -0
- package/dist/combat/abilityKit.js +40 -4
- package/dist/combat/death.d.ts +4 -3
- package/dist/combat/death.js +3 -3
- package/dist/combat/magazine.d.ts +9 -0
- package/dist/combat/magazine.js +24 -2
- package/dist/combat/projectiles.d.ts +94 -2
- package/dist/combat/projectiles.js +282 -61
- package/dist/combat/regenShield.d.ts +14 -0
- package/dist/combat/regenShield.js +23 -7
- package/dist/crafting/recipeCatalog.d.ts +16 -0
- package/dist/crafting/recipeCatalog.js +55 -0
- package/dist/economy/auctionBook.d.ts +1 -1
- package/dist/economy/auctionBook.js +11 -1
- package/dist/economy/listingBook.d.ts +1 -1
- package/dist/economy/listingBook.js +11 -1
- package/dist/economy/resourceLedger.js +3 -3
- package/dist/economy/shopStock.js +7 -6
- package/dist/economy/techTree.js +7 -6
- package/dist/economy/unlockPoints.js +3 -6
- package/dist/editor/commandApply.js +89 -16
- package/dist/editor/commands.d.ts +38 -1
- package/dist/editor/commands.js +320 -3
- package/dist/editor/creatorStorage.d.ts +59 -0
- package/dist/editor/creatorStorage.js +164 -0
- package/dist/editor/document.d.ts +4 -0
- package/dist/editor/document.js +116 -17
- package/dist/editor/index.d.ts +3 -0
- package/dist/editor/index.js +1 -0
- package/dist/editor/liveSync.d.ts +4 -1
- package/dist/editor/liveSync.js +26 -10
- package/dist/editor/materialAuthoring.d.ts +14 -0
- package/dist/editor/materialAuthoring.js +38 -0
- package/dist/editor/modelNodeVisibility.d.ts +11 -0
- package/dist/editor/modelNodeVisibility.js +29 -0
- package/dist/editor/movementCatalog.d.ts +90 -0
- package/dist/editor/movementCatalog.js +144 -0
- package/dist/editor/simulation.d.ts +60 -0
- package/dist/editor/simulation.js +202 -0
- package/dist/editor/staticPrefab.d.ts +17 -0
- package/dist/editor/staticPrefab.js +51 -0
- package/dist/editor/terrainMaterials.d.ts +10 -0
- package/dist/editor/terrainMaterials.js +13 -0
- package/dist/editor/types.d.ts +11 -0
- package/dist/game/cameraConfig.d.ts +3 -1
- package/dist/game/clipRoles.d.ts +2 -1
- package/dist/game/clipRoles.js +7 -3
- package/dist/game/contentValidation.d.ts +69 -0
- package/dist/game/contentValidation.js +118 -0
- package/dist/game/controlGate.d.ts +8 -0
- package/dist/game/controlGate.js +43 -6
- package/dist/game/defineGame.d.ts +32 -3
- package/dist/game/dialogueGraph.d.ts +20 -0
- package/dist/game/dialogueGraph.js +107 -0
- package/dist/game/events.d.ts +5 -0
- package/dist/game/keyValueStore.d.ts +17 -4
- package/dist/game/keyValueStore.js +35 -23
- package/dist/game/lootModifiers.d.ts +17 -0
- package/dist/game/lootModifiers.js +26 -0
- package/dist/game/narrativeAuthoring.d.ts +67 -0
- package/dist/game/narrativeAuthoring.js +122 -0
- package/dist/game/playableGame.d.ts +52 -20
- package/dist/game/quest.d.ts +15 -0
- package/dist/game/quest.js +209 -63
- package/dist/game/questCatalog.d.ts +50 -0
- package/dist/game/questCatalog.js +205 -0
- package/dist/game/saveStore.d.ts +6 -5
- package/dist/game/saveStore.js +57 -57
- package/dist/game/systemRuntime.d.ts +2 -2
- package/dist/game/systemRuntime.js +84 -33
- package/dist/gameplay.d.ts +3 -0
- package/dist/gameplay.js +3 -0
- package/dist/interaction/promptRegistry.d.ts +5 -4
- package/dist/interaction/promptRegistry.js +8 -2
- package/dist/inventory/inventoryModel.js +9 -6
- package/dist/inventory/shapedGrid.d.ts +48 -9
- package/dist/inventory/shapedGrid.js +69 -10
- package/dist/item/durability.js +4 -0
- package/dist/item/itemInstanceRegistry.d.ts +32 -1
- package/dist/item/itemInstanceRegistry.js +111 -7
- package/dist/item/itemgen.d.ts +1 -1
- package/dist/item/itemgen.js +1 -1
- package/dist/item/modularItem.js +7 -1
- package/dist/material/appearanceSignals.d.ts +53 -0
- package/dist/material/appearanceSignals.js +47 -0
- package/dist/material/hairCards.d.ts +47 -0
- package/dist/material/hairCards.js +136 -0
- package/dist/material/materialAsset.d.ts +162 -0
- package/dist/material/materialAsset.js +382 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +348 -1
- package/dist/movement/characterController.d.ts +5 -2
- package/dist/movement/characterController.js +50 -23
- package/dist/movement/freeFlight.d.ts +2 -2
- package/dist/movement/freeFlight.js +3 -3
- package/dist/movement/movementModel.d.ts +24 -1
- package/dist/movement/movementModel.js +74 -7
- package/dist/movement/playerMovement.d.ts +28 -1
- package/dist/movement/playerMovement.js +425 -102
- package/dist/movement/poseState.d.ts +2 -0
- package/dist/movement/poseState.js +29 -1
- package/dist/movement/solidObstacles.d.ts +3 -1
- package/dist/movement/solidObstacles.js +8 -5
- package/dist/movement/terrainGrade.d.ts +13 -0
- package/dist/movement/terrainGrade.js +33 -0
- package/dist/movement/voxelController.d.ts +2 -2
- package/dist/movement/voxelController.js +3 -3
- package/dist/nav/navGrid.d.ts +19 -1
- package/dist/nav/navGrid.js +26 -10
- package/dist/nav/solidRoute.d.ts +50 -0
- package/dist/nav/solidRoute.js +144 -0
- package/dist/physics/ballisticSweep.d.ts +10 -3
- package/dist/physics/ballisticSweep.js +53 -29
- package/dist/physics/forceVolume.d.ts +41 -0
- package/dist/physics/forceVolume.js +88 -0
- package/dist/physics/kinematicVehicle.d.ts +8 -3
- package/dist/physics/kinematicVehicle.js +2 -2
- package/dist/physics/physicsWorldBackend.js +19 -3
- package/dist/physics/spatialGrid.d.ts +1 -1
- package/dist/physics/spatialGrid.js +8 -9
- package/dist/physics/vehicleDynamics.d.ts +3 -2
- package/dist/physics/vehicleDynamics.js +2 -2
- package/dist/physics/vehicleObstacles.d.ts +3 -2
- package/dist/physics/vehicleObstacles.js +29 -5
- package/dist/runtime/cameraDirector.d.ts +39 -2
- package/dist/runtime/cameraDirector.js +162 -0
- package/dist/runtime/commandDispatch.d.ts +14 -0
- package/dist/runtime/commandDispatch.js +19 -0
- package/dist/runtime/context/combat.d.ts +11 -4
- package/dist/runtime/context/combat.js +178 -11
- package/dist/runtime/context/deathLoot.d.ts +9 -8
- package/dist/runtime/context/deathLoot.js +16 -16
- package/dist/runtime/context/scene.d.ts +2 -0
- package/dist/runtime/context/scene.js +13 -21
- package/dist/runtime/descriptors/baseline.js +34 -3
- package/dist/runtime/descriptors/features.d.ts +2 -1
- package/dist/runtime/descriptors/features.js +18 -0
- package/dist/runtime/gameContext.js +79 -13
- package/dist/runtime/gameContextTypes.d.ts +14 -0
- package/dist/runtime/headlessRunner.d.ts +3 -1
- package/dist/runtime/headlessRunner.js +11 -11
- package/dist/runtime/hostedGameRunner.d.ts +24 -1
- package/dist/runtime/hostedGameRunner.js +156 -10
- package/dist/runtime/hostedWorldSession.d.ts +17 -5
- package/dist/runtime/hostedWorldSession.js +154 -16
- package/dist/runtime/hostedWorldStore.d.ts +2 -2
- package/dist/runtime/inputRecorder.d.ts +1 -1
- package/dist/runtime/inputRecorder.js +21 -1
- package/dist/runtime/inputSnapshot.d.ts +16 -4
- package/dist/runtime/inputSnapshot.js +31 -3
- package/dist/runtime/runtimeSave.d.ts +10 -6
- package/dist/runtime/runtimeSave.js +11 -5
- package/dist/runtime/simContext.d.ts +8 -3
- package/dist/runtime/simContext.js +7 -3
- package/dist/runtime/simSnapshot.js +2 -0
- package/dist/runtime/worldReplication.d.ts +2 -0
- package/dist/runtime/worldReplication.js +1 -1
- package/dist/scene/assetCatalog.d.ts +2 -0
- package/dist/scene/behaviorRuntime.d.ts +26 -5
- package/dist/scene/behaviorRuntime.js +263 -16
- package/dist/scene/behaviors.d.ts +30 -1
- package/dist/scene/behaviors.js +29 -0
- package/dist/scene/entityStore.d.ts +14 -3
- package/dist/scene/entityStore.js +45 -9
- package/dist/scene/mount.d.ts +9 -0
- package/dist/scene/mount.js +35 -0
- package/dist/scene/possession.d.ts +2 -0
- package/dist/scene/possession.js +21 -1
- package/dist/scene/sceneRaycast.d.ts +7 -0
- package/dist/scene/sceneRaycast.js +55 -0
- package/dist/scene/spatial.d.ts +20 -1
- package/dist/scene/spatial.js +94 -2
- package/dist/scene/vehicleSeat.d.ts +36 -2
- package/dist/scene/vehicleSeat.js +89 -2
- package/dist/stats/accumulatorMeter.d.ts +10 -0
- package/dist/stats/accumulatorMeter.js +6 -0
- package/dist/stats/eventMeter.d.ts +9 -2
- package/dist/stats/eventMeter.js +5 -2
- package/dist/store/defineStore.d.ts +3 -1
- package/dist/store/defineStore.js +3 -1
- package/dist/store/observableKeyedStore.d.ts +1 -1
- package/dist/store/observableKeyedStore.js +8 -6
- package/dist/time/simClock.d.ts +6 -2
- package/dist/time/simClock.js +5 -0
- package/dist/vfx/particleDirector.d.ts +58 -6
- package/dist/vfx/particleDirector.js +113 -22
- package/dist/vfx/particles.d.ts +68 -5
- package/dist/vfx/particles.js +284 -32
- package/dist/visibility/assetStreaming.d.ts +21 -3
- package/dist/visibility/assetStreaming.js +108 -24
- package/dist/visibility/settings.d.ts +8 -2
- package/dist/visibility/settings.js +3 -0
- package/dist/visibility/visibilitySystem.js +1 -0
- package/dist/work/expedition.d.ts +110 -0
- package/dist/work/expedition.js +195 -0
- package/dist/work/staffedStation.d.ts +29 -0
- package/dist/work/staffedStation.js +27 -0
- package/dist/world/authoredAnimation.d.ts +38 -0
- package/dist/world/authoredAnimation.js +99 -0
- package/dist/world/authoredObjects.d.ts +24 -4
- package/dist/world/authoredObjects.js +43 -16
- package/dist/world/authoredSimulation.d.ts +74 -0
- package/dist/world/authoredSimulation.js +302 -0
- package/dist/world/authoredWeather.d.ts +120 -0
- package/dist/world/authoredWeather.js +314 -0
- package/dist/world/buildingKit.d.ts +8 -3
- package/dist/world/buildingKit.js +21 -8
- package/dist/world/environmentForces.d.ts +21 -0
- package/dist/world/environmentForces.js +42 -0
- package/dist/world/features.d.ts +3 -3
- package/dist/world/footprintGrid.d.ts +55 -0
- package/dist/world/footprintGrid.js +201 -0
- package/dist/world/grassKind.d.ts +10 -0
- package/dist/world/grassKind.js +22 -0
- package/dist/world/pathTerrain.d.ts +1 -1
- package/dist/world/terraform.d.ts +2 -2
- package/dist/world/terraform.js +46 -18
- package/dist/world/weather.d.ts +17 -0
- package/dist/world/weather.js +116 -20
- package/dist/world/worldSolids.d.ts +11 -0
- package/dist/world/worldSolids.js +112 -1
- package/dist/world.d.ts +5 -5
- package/dist/world.js +4 -4
- package/package.json +1 -1
- package/skills/ce-handoff/SKILL.md +1 -1
- package/skills/game-design/SKILL.md +2 -0
- package/skills/jgengine/SKILL.md +23 -2
- package/skills/jgengine/api.md +28 -17
- package/skills/jgengine/recipes/minimal-game.md +7 -2
- package/skills/jgengine-combat/SKILL.md +8 -2
- package/skills/jgengine-combat/api.md +22 -10
- package/skills/jgengine-combat/recipes/portable-runtime-state.md +60 -6
- package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +37 -3
- package/skills/jgengine-combat/reference.md +24 -2
- package/skills/jgengine-gameplay/SKILL.md +63 -0
- package/skills/jgengine-gameplay/api.md +131 -37
- package/skills/jgengine-gameplay/capabilities.md +68 -0
- package/skills/jgengine-gameplay/recipes/device-save-status.md +46 -0
- package/skills/jgengine-gameplay/recipes/expeditions.md +108 -0
- package/skills/jgengine-gameplay/recipes/narrative-authoring.md +76 -0
- package/skills/jgengine-gameplay/recipes/paged-item-storage.md +101 -0
- package/skills/jgengine-gameplay/recipes/quest-authoring.md +63 -0
- package/skills/jgengine-gameplay/recipes/shaped-inventory.md +50 -0
- package/skills/jgengine-gameplay/reference-content-validation.md +74 -0
- package/skills/jgengine-gameplay/reference-systems.md +119 -0
- package/skills/jgengine-world/SKILL.md +12 -3
- package/skills/jgengine-world/api.md +201 -58
- package/skills/jgengine-world/capabilities.md +116 -3
- package/skills/jgengine-world/recipes/character-animation.md +31 -8
- package/skills/jgengine-world/recipes/controls.md +4 -0
- package/skills/jgengine-world/recipes/npc-senses.md +12 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +3 -1
- package/skills/jgengine-world/reference.md +167 -5
- package/skills/jgengine-world/visibility.md +28 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## @jgengine/core/combat
|
|
6
6
|
|
|
7
|
-
- `AbilityKit` (interface): interface AbilityKit { slots(): readonly string[]; config(slotId: string): AbilitySlotConfig | null; state(slotId: string, resourceAvailable?: number): AbilitySlotSnapshot | null; snapshot(resourceAvailable?: number):
|
|
7
|
+
- `AbilityKit` (interface): interface AbilityKit { slots(): readonly string[]; config(slotId: string): AbilitySlotConfig | null; state(): AbilityKitState; state(slotId: string, resourceAvailable?: number): AbilitySlotSnapshot | null; snapshot(resourceAvailable?: number): AbilitySlotSnapsh… — ⚠ undocumented · used by `createAbilityKit`: A bar of cooldown-gated abilities the player fires by slot, tracking readiness and cooldown per ability.
|
|
8
8
|
- `AbilitySlotSnapshot` (interface): interface AbilitySlotSnapshot { id: string; state: AbilitySlotState; castType: AbilityCastType; resourceCost: number; charges: number; chargesMax: number; cooldownRemainingMs: number; cooldownFraction: number; groupRemainingMs: number; justCast: boolean; ready: boolean … — ⚠ undocumented · used by `actionFromAbilitySlot` (@jgengine/react): Composable React renderers over the headless action view model (`@jgengine/core/ui/actionModel`) — the data/renderer/chrome split for an RTS…
|
|
9
9
|
- `AbilitySlotState` (type): type AbilitySlotState = "ready" | "cooldown" | "no-resource" | "just-cast" — ⚠ undocumented
|
|
10
10
|
- `AllocationOutcome` (interface): interface AllocationOutcome — The serializable result of an allocation pass — immediate `grants` plus any deferred `pools`.
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
- `EncounterState` (interface): interface EncounterState<TData = unknown> — Full serializable encounter state: the working phase tree (including any injected phases), a per-node runtime map, the active path from root to the deepest open node, accumulated time, and whether the sequence has finished. Round-trips through JSON with no behavioral change; the registries live in {@link EncounterConfig}, not here.
|
|
56
56
|
- `EncounterStep` (interface): interface EncounterStep<TData = unknown> — Result of a step: the (mutated, returned) state and the events emitted this step.
|
|
57
57
|
- `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.
|
|
58
|
-
- `EventMeter` (interface): interface EventMeter { value(): number; fraction(): number; tier(): string | null; ready(): boolean; feed(tag: string, scale?: number): EventMeterFeedResult; consume(): boolean; drain(amount:
|
|
58
|
+
- `EventMeter` (interface): interface EventMeter { state(): EventMeterState; restore(next: EventMeterState): void; value(): number; fraction(): number; tier(): string | null; ready(): boolean; feed(tag: string, scale?: number): EventMeterFeedResult; consume(): boolean; drain(amount: numbe… — ⚠ undocumented · used by `createEventMeter`: A heat/hype gauge that rises as tagged events land and cools between them, firing when it fills or breaks — the streak/overdrive meter shoot…
|
|
59
59
|
- `EventMeterFeedResult` (interface): interface EventMeterFeedResult { tag: string; reset: boolean; amount: number; value: number; fraction: number; fired: boolean; ready: boolean; overflow: number; tier: string | null; tierChanged: boolean } — ⚠ undocumented
|
|
60
60
|
- `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}.
|
|
61
61
|
- `FireCadenceConfig` (interface): interface FireCadenceConfig — Tuning for {@link createFireCadence}: the minimum interval between actions and whether it starts ready.
|
|
@@ -207,8 +207,9 @@
|
|
|
207
207
|
- `AbilityCastResult` (type): type AbilityCastResult = | { ok: true; slot: AbilitySlotSnapshot } | { ok: false; reason: AbilityCastReason; slot: AbilitySlotSnapshot | null } — ⚠ undocumented
|
|
208
208
|
- `AbilityCastType` (type): type AbilityCastType = "instant" | "projectile" | "channel" | "targeted" — ⚠ undocumented
|
|
209
209
|
- `AbilityCooldownGroup` (interface): interface AbilityCooldownGroup — A cooldown shared across every member slot: casting any member starts it, and no member can cast while it runs. One group that every slot joins is an MMO global cooldown.
|
|
210
|
-
- `AbilityKit` (interface): interface AbilityKit { slots(): readonly string[]; config(slotId: string): AbilitySlotConfig | null; state(slotId: string, resourceAvailable?: number): AbilitySlotSnapshot | null; snapshot(resourceAvailable?: number):
|
|
210
|
+
- `AbilityKit` (interface): interface AbilityKit { slots(): readonly string[]; config(slotId: string): AbilitySlotConfig | null; state(): AbilityKitState; state(slotId: string, resourceAvailable?: number): AbilitySlotSnapshot | null; snapshot(resourceAvailable?: number): AbilitySlotSnapsh… — ⚠ undocumented · used by `createAbilityKit`: A bar of cooldown-gated abilities the player fires by slot, tracking readiness and cooldown per ability.
|
|
211
211
|
- `AbilityKitOptions` (interface): interface AbilityKitOptions { groups?: readonly AbilityCooldownGroup[]; resource?: AbilityResource } — ⚠ undocumented · used by `createAbilityKit`: A bar of cooldown-gated abilities the player fires by slot, tracking readiness and cooldown per ability.
|
|
212
|
+
- `AbilityKitState` (interface): interface AbilityKitState — Mutable kit state; slot/group definitions and any bound resource are saved by the caller.
|
|
212
213
|
- `AbilityResource` (interface): interface AbilityResource — A spendable resource pool (mana, energy, rage) bound to an ability kit. When bound, `cast` deducts a slot's `resourceCost` on success and `state`/`snapshot`/`canCast` reflect affordability without a per-call `resourceAvailable` argument — the cast path actually spends instead of only gating (#357).
|
|
213
214
|
- `AbilitySlotConfig` (interface): interface AbilitySlotConfig { id: string; cooldownMs: number; chargesMax?: number; resourceCost?: number; castType?: AbilityCastType; flashMs?: number; groups?: readonly string[] } — ⚠ undocumented · used by `createAbilityKit`: A bar of cooldown-gated abilities the player fires by slot, tracking readiness and cooldown per ability.
|
|
214
215
|
- `AbilitySlotRetune` (interface): interface AbilitySlotRetune { cooldownMs?: number; resourceCost?: number } — ⚠ undocumented
|
|
@@ -327,12 +328,12 @@
|
|
|
327
328
|
- `DeathReasonKind` (type): type DeathReasonKind = DeathReason["kind"] — ⚠ undocumented
|
|
328
329
|
- `DeathResolution` (type): type DeathResolution = | { status: "resolved"; drops: Drop[]; ranCommand: string | null } | { status: "rejected"; reason: string } — ⚠ undocumented
|
|
329
330
|
- `DeathSystem` (interface): interface DeathSystem { resolveDeath(instanceId: string, reason: DeathReason): DeathResolution; revive(instanceId: string): boolean } — ⚠ undocumented · used by `createDeathSystem`: Resolve entity death and the on-death consequences — drops, respawn eligibility, kill credit.
|
|
330
|
-
- `DeathSystemDeps` (interface): interface DeathSystemDeps { resolveOnDeath(instanceId: string): OnDeathSpec | null | undefined; resolveIdentity(instanceId: string): DeathIdentity | null; loot: {roll(tableId: string): Drop[]}; events: GameEvents; runCommand?(name: string, args
|
|
331
|
+
- `DeathSystemDeps` (interface): interface DeathSystemDeps { resolveOnDeath(instanceId: string, reason?: DeathReason): OnDeathSpec | null | undefined; resolveIdentity(instanceId: string): DeathIdentity | null; loot: {roll(tableId: string): Drop[]}; events: GameEvents; runCommand?(name: string, args… — ⚠ undocumented · used by `createDeathSystem`: Resolve entity death and the on-death consequences — drops, respawn eligibility, kill credit.
|
|
331
332
|
- `DropMode` (type): type DropMode = "grant" | "world" — ⚠ undocumented
|
|
332
333
|
- `EffectDeathContext` (interface): interface EffectDeathContext { from: string; via?: EffectVia; userIdOf?(instanceId: string): string | undefined } — ⚠ undocumented
|
|
333
334
|
- `NormalizedOnDeath` (interface): interface NormalizedOnDeath { drops: OnDeathDropRule[]; command: OnDeathCommandRule | null; dropMode: DropMode; scatter?: ScatterOptions } — ⚠ undocumented
|
|
334
335
|
- `OnDeathCommandRule` (interface): interface OnDeathCommandRule { name: string; args?: unknown; when?: {reason: DeathReasonKind} } — ⚠ undocumented
|
|
335
|
-
- `OnDeathDropRule` (interface): interface OnDeathDropRule { table: string; when?: {reason: DeathReasonKind} } — ⚠ undocumented
|
|
336
|
+
- `OnDeathDropRule` (interface): interface OnDeathDropRule { table: string; when?: {reason: DeathReasonKind | "any"} } — ⚠ undocumented
|
|
336
337
|
- `OnDeathSpec` (interface): interface OnDeathSpec — Catalog `onDeath` for an entity: loot tables to roll, `dropMode: "world"` to scatter drops on the ground (rendered with loot beams) instead of granting them into the killer's bag, and commands to run. Fires only when the entity dies through `ctx.scene.entity.effect`, not a raw `stats.delta`.
|
|
337
338
|
- `createDeathSystem` (function): function createDeathSystem(deps: DeathSystemDeps): DeathSystem — Resolve entity death and the on-death consequences — drops, respawn eligibility, kill credit.
|
|
338
339
|
- `deathReasonFromEffect` (function): function deathReasonFromEffect(ctx: EffectDeathContext): DeathReason — ⚠ undocumented
|
|
@@ -441,19 +442,27 @@
|
|
|
441
442
|
- `MagazineConfig` (interface): interface MagazineConfig — Tuning for `createMagazine`: mag capacity, reload delay, and where reserve ammo is drawn from.
|
|
442
443
|
- `MagazineReserve` (interface): interface MagazineReserve — Draws ammo for a `Magazine`'s reload from wherever the reserve pool actually lives.
|
|
443
444
|
- `MagazineSnapshot` (interface): interface MagazineSnapshot — Complete serializable state for a {@link Magazine}; `reserve: null` means infinite reserve.
|
|
445
|
+
- `MagazineTuning` (interface): interface MagazineTuning — Live magazine tuning; shrinking below loaded rounds rejects unless overflow is explicit.
|
|
444
446
|
- `createMagazine` (function): function createMagazine(config: MagazineConfig): Magazine — Builds a {@link Magazine}: discrete loaded ammo, a timed reload, and reserve-pool interaction.
|
|
445
447
|
|
|
446
448
|
## @jgengine/core/combat/projectiles
|
|
447
449
|
|
|
448
450
|
- `EntityRaycastHit` (interface): interface EntityRaycastHit { kind: "entity"; instanceId: string; distance: number; at: EntityPosition; colliderName?: string; damageEligible?: boolean; blocks?: boolean } — ⚠ undocumented
|
|
451
|
+
- `LiveProjectile` (interface): interface LiveProjectile — Detached pose of one active projectile pellet for presentation.
|
|
449
452
|
- `ObjectRaycastHit` (interface): interface ObjectRaycastHit { kind: "object"; instanceId: string; catalogId: string; distance: number; at: EntityPosition; colliderName?: string; damageEligible?: boolean; blocks?: boolean } — ⚠ undocumented
|
|
453
|
+
- `ProjectileFlightState` (interface): interface ProjectileFlightState — Serializable motion state of one projectile pellet.
|
|
450
454
|
- `ProjectileHit` (type): type ProjectileHit = | { kind: "entity"; instanceId: string; distance: number; colliderName?: string; damageEligible?: boolean; } | { kind: "object"; instanceId: string; catalogId: string; distance: number; colliderName?: string; damageEligible?: boolean; } — ⚠ undocumented
|
|
451
455
|
- `ProjectileObjectsDeps` (interface): interface ProjectileObjectsDeps { list(): readonly {instanceId: string; catalogId: string; position: readonly [number, number, number]; rotationY?: number;}[]; inBox?(min: EntityPosition, max: EntityPosition): readonly {instanceId: string; catalogId: string; position: rea… — ⚠ undocumented
|
|
452
456
|
- `ProjectilePrediction` (interface): interface ProjectilePrediction { hits: ProjectileHit[]; blocked?: boolean; origin?: EntityPosition; direction?: EntityPosition; firstImpact?: ProjectileHit | null } — ⚠ undocumented
|
|
453
|
-
- `ProjectileSettleReport` (interface): interface ProjectileSettleReport
|
|
454
|
-
- `ProjectileShotInput` (interface): interface ProjectileShotInput { from: string; via: EffectVia; aim: Aim; effect: string; originPolicy?: ShotOriginPolicy } — ⚠ undocumented
|
|
455
|
-
- `
|
|
457
|
+
- `ProjectileSettleReport` (interface): interface ProjectileSettleReport — Exactly-once settlement with launch identity and actual applied effects.
|
|
458
|
+
- `ProjectileShotInput` (interface): interface ProjectileShotInput { from: string; via: EffectVia; aim: Aim; effect: string; originPolicy?: ShotOriginPolicy; travel?: ProjectileTravelConfig } — ⚠ undocumented
|
|
459
|
+
- `ProjectileShotState` (interface): interface ProjectileShotState — Serializable captured launch and settlement state.
|
|
460
|
+
- `ProjectileSystem` (interface): interface ProjectileSystem { willHitProjectile(input: ProjectileShotInput): ProjectilePrediction; fireProjectile(input: ProjectileShotInput): string; settleProjectile(shotId: string): SettleResult; advanceProjectiles(dt: number, time: number): SettleResult[]; activeP… — ⚠ undocumented · used by `createProjectileSystem`: Spawn and advance projectiles each frame, resolving travel, lifetime, and hits.
|
|
456
461
|
- `ProjectileSystemDeps` (interface): interface ProjectileSystemDeps { effects: EffectSystem; spatial: CombatSpatialDeps; getStat(itemId: string, stat: string): number | null; raycast?: Raycast; sceneRaycast?: SceneRaycastApi; objects?: ProjectileObjectsDeps; entityCollidersOf?(instanceId: string): EntityCol… — ⚠ undocumented · used by `createProjectileSystem`: Spawn and advance projectiles each frame, resolving travel, lifetime, and hits.
|
|
462
|
+
- `ProjectileSystemState` (interface): interface ProjectileSystemState — Detached projectile owner state for replay and restore.
|
|
463
|
+
- `ProjectileTravelConfig` (interface): interface ProjectileTravelConfig — Captured launch settings for authoritative live travel.
|
|
464
|
+
- `ProjectileTravelDeps` (interface): interface ProjectileTravelDeps — Authoritative clock, collision and environmental acceleration providers.
|
|
465
|
+
- `ProjectileTravelImpact` (interface): interface ProjectileTravelImpact — Nearest contact returned by a live segment sweep.
|
|
457
466
|
- `Raycast` (type): type Raycast = (from: string, aim: Aim, range: number, originPolicy?: ShotOriginPolicy) => RaycastHit[] — ⚠ undocumented
|
|
458
467
|
- `RaycastHit` (type): type RaycastHit = EntityRaycastHit | ObjectRaycastHit — ⚠ undocumented
|
|
459
468
|
- `SettleResult` (type): type SettleResult = | { status: "settled"; shotId: string; at: [number, number, number]; hits: EffectResult[]; origin?: [number, number, number]; } | { status: "rejected"; shotId: string; reason: string } — ⚠ undocumented
|
|
@@ -477,6 +486,7 @@
|
|
|
477
486
|
- `RegenShieldConfig` (interface): interface RegenShieldConfig — Tuning for `createRegenShield`: pool size, refill rate, and the post-damage grace period.
|
|
478
487
|
- `RegenShieldPool` (interface): interface RegenShieldPool — External storage a {@link RegenShield} reads and writes instead of keeping the value itself — an entity stat, a replicated store, a save record. This is the same port `Magazine` exposes as `MagazineReserve`, and it exists for the same reason: in most games the shield value is already owned by the stat system that damage, the HUD, and replication all read, so a primitive that insists on owning the number privately cannot be adopted without creating a second source of truth.
|
|
479
488
|
- `RegenShieldRates` (interface): interface RegenShieldRates — Per-tick rate overrides, for games that derive regen from live state — a talent tree, a buff, an equipped mod — rather than a constant fixed when the shield was built. Omit to use the configured values; the grace period in effect is whichever `regenDelayMs` was last passed to `tick`.
|
|
489
|
+
- `RegenShieldState` (interface): interface RegenShieldState — Complete mutable shield state, including pending external damage detection.
|
|
480
490
|
- `createRegenShield` (function): function createRegenShield(config: RegenShieldConfig): RegenShield — Builds a {@link RegenShield} that suppresses regen for `regenDelayMs` after each hit.
|
|
481
491
|
|
|
482
492
|
## @jgengine/core/combat/renderCues
|
|
@@ -635,8 +645,9 @@
|
|
|
635
645
|
|
|
636
646
|
## @jgengine/core/stats/accumulatorMeter
|
|
637
647
|
|
|
638
|
-
- `AccumulatorMeter` (interface): interface AccumulatorMeter { value(): number; fraction(): number; tier(): string | null; broke(): boolean; add(amount: number): MeterAddResult; drain(amount: number): void; reset(): void;
|
|
648
|
+
- `AccumulatorMeter` (interface): interface AccumulatorMeter { state(): AccumulatorMeterState; restore(next: AccumulatorMeterState): void; value(): number; fraction(): number; tier(): string | null; broke(): boolean; add(amount: number): MeterAddResult; drain(amount: number): void; reset(): void; tic… — ⚠ undocumented · used by `createAccumulatorMeter`: A raw accumulating gauge that crosses named tier thresholds as a value builds, with optional decay — the primitive under charge, rage, and c…
|
|
639
649
|
- `AccumulatorMeterConfig` (interface): interface AccumulatorMeterConfig { max: number; mode?: MeterMode; decayPerSecond?: number; decayDelayMs?: number; tiers?: readonly MeterTier[] } — ⚠ undocumented · used by `createAccumulatorMeter`: A raw accumulating gauge that crosses named tier thresholds as a value builds, with optional decay — the primitive under charge, rage, and c…
|
|
650
|
+
- `AccumulatorMeterState` (interface): interface AccumulatorMeterState — Mutable meter value, hold latch, and time since the last gain.
|
|
640
651
|
- `MeterAddResult` (interface): interface MeterAddResult { value: number; fired: boolean; overflow: number; tier: string | null; tierChanged: boolean } — ⚠ undocumented
|
|
641
652
|
- `MeterMode` (type): type MeterMode = "hold" | "reset" — ⚠ undocumented
|
|
642
653
|
- `MeterTier` (interface): interface MeterTier { id: string; at: number } — ⚠ undocumented · used by `tierAt`: The highest tier id whose `at` threshold `value` has reached, or `null` below every tier — the pure lookup `createAccumulatorMeter`/`createE…
|
|
@@ -645,9 +656,10 @@
|
|
|
645
656
|
|
|
646
657
|
## @jgengine/core/stats/eventMeter
|
|
647
658
|
|
|
648
|
-
- `EventMeter` (interface): interface EventMeter { value(): number; fraction(): number; tier(): string | null; ready(): boolean; feed(tag: string, scale?: number): EventMeterFeedResult; consume(): boolean; drain(amount:
|
|
659
|
+
- `EventMeter` (interface): interface EventMeter { state(): EventMeterState; restore(next: EventMeterState): void; value(): number; fraction(): number; tier(): string | null; ready(): boolean; feed(tag: string, scale?: number): EventMeterFeedResult; consume(): boolean; drain(amount: numbe… — ⚠ undocumented · used by `createEventMeter`: A heat/hype gauge that rises as tagged events land and cools between them, firing when it fills or breaks — the streak/overdrive meter shoot…
|
|
649
660
|
- `EventMeterConfig` (interface): interface EventMeterConfig { max: number; mode?: MeterMode; decayPerSecond?: number; decayDelayMs?: number; tiers?: readonly MeterTier[]; gains: Record<string, number>; resets?: readonly string[] } — ⚠ undocumented · used by `createEventMeter`: A heat/hype gauge that rises as tagged events land and cools between them, firing when it fills or breaks — the streak/overdrive meter shoot…
|
|
650
661
|
- `EventMeterFeedResult` (interface): interface EventMeterFeedResult { tag: string; reset: boolean; amount: number; value: number; fraction: number; fired: boolean; ready: boolean; overflow: number; tier: string | null; tierChanged: boolean } — ⚠ undocumented
|
|
662
|
+
- `EventMeterState` (type): type EventMeterState = AccumulatorMeterState — Event meter persistence uses the underlying accumulator's complete state.
|
|
651
663
|
- `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.
|
|
652
664
|
|
|
653
665
|
## @jgengine/core/stats/rollCheck
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Save and restore closure-backed combat runtimes
|
|
2
2
|
|
|
3
|
-
Use this recipe when an existing project wants JGengine's
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Use this recipe when an existing project wants JGengine's combat runtimes but
|
|
4
|
+
keeps its own save system, clock, entities, and authority. The runtimes expose
|
|
5
|
+
detached plain state; no JGengine store or
|
|
6
6
|
`GameContext` is required.
|
|
7
7
|
|
|
8
8
|
## Install and focused imports
|
|
@@ -132,18 +132,72 @@ and restores that reserve directly. For a caller-owned `MagazineReserve`, save
|
|
|
132
132
|
and restore the caller's reserve first; `restore` returns `false` rather than
|
|
133
133
|
partially changing loaded/reload state when it cannot reconcile the reserve.
|
|
134
134
|
|
|
135
|
+
## Retune without reconstructing the magazine
|
|
136
|
+
|
|
137
|
+
Equipment or upgrades can change capacity and reload duration in place:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
if (!magazine.retune({ capacity: 8, reloadMs: 750 })) {
|
|
141
|
+
throw new Error("capacity would strand loaded rounds");
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Growing capacity never grants ammo. Retuning preserves loaded rounds, shared
|
|
146
|
+
reserve identity/value, and elapsed reload milliseconds. A shorter duration
|
|
147
|
+
can put `reloadFraction()` at 1; only the next positive `tick` completes the
|
|
148
|
+
reload and draws reserve. Save the current tuning in the caller's equipment
|
|
149
|
+
schema and supply it when recreating the runtime before restoring its snapshot.
|
|
150
|
+
|
|
151
|
+
Shrinking below the loaded count rejects the entire tuning by default. Choose
|
|
152
|
+
`overflow: "return-to-reserve"` to return exactly the excess to a finite reserve
|
|
153
|
+
with `gain`, or `overflow: "discard"` to explicitly destroy those rounds. Returning
|
|
154
|
+
to an infinite reserve or a read/spend-only adapter rejects without changing
|
|
155
|
+
capacity, duration, or ammo.
|
|
156
|
+
|
|
157
|
+
## Abilities, shields, and meters
|
|
158
|
+
|
|
159
|
+
Ability kits, shields, and meters use the same save boundary:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
const saved = JSON.stringify({
|
|
163
|
+
abilities: kit.state(),
|
|
164
|
+
shield: shield.state(),
|
|
165
|
+
heat: heat.state(),
|
|
166
|
+
});
|
|
167
|
+
const decoded = JSON.parse(saved);
|
|
168
|
+
if (!kit.restore(decoded.abilities)) throw new Error("ability slot/group ids differ");
|
|
169
|
+
if (!shield.restore(decoded.shield)) throw new Error("external shield pool max differs");
|
|
170
|
+
heat.restore(decoded.heat);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`AbilityKit.state()` contains charges, separate recharge/flash/group timers,
|
|
174
|
+
and retuned cooldowns/costs. `state(slotId, resourceAvailable?)` and `snapshot()`
|
|
175
|
+
remain HUD views. Rebuild with the same slot/group definitions; save any bound
|
|
176
|
+
resource separately. Restore never spends it.
|
|
177
|
+
|
|
178
|
+
`RegenShield.state()` includes mutable bounds, pool value, grace timing, the
|
|
179
|
+
active delay override, and the last external-pool observation. Restore writes
|
|
180
|
+
the pool value, preserving even damage waiting to be observed on the next tick;
|
|
181
|
+
the caller's pool must already have the saved max. Use `refill(amount)` for
|
|
182
|
+
pickups; numeric `restore(amount)` remains a deprecated compatibility alias.
|
|
183
|
+
|
|
184
|
+
`EventMeter.state()` and `AccumulatorMeter.state()` contain value, the hold
|
|
185
|
+
latch, and the decay-delay timer. Rebuild with the same meter configuration.
|
|
186
|
+
For caller-owned visibility policy, `heat.tick(dt, !sourceVisible)` pauses both
|
|
187
|
+
decay and its delay while seen; omitting the second argument advances normally.
|
|
188
|
+
|
|
135
189
|
## Ownership
|
|
136
190
|
|
|
137
191
|
The existing project owns save versioning, entity identity, clock value,
|
|
138
192
|
reserve state, networking/rollback policy, content, and when ticks occur.
|
|
139
|
-
JGengine owns the closure convenience methods,
|
|
193
|
+
JGengine owns the closure convenience methods, state encoding shape,
|
|
140
194
|
detached copies, and deterministic continuation once the caller supplies the
|
|
141
195
|
same config and clock.
|
|
142
196
|
|
|
143
197
|
## Common traps
|
|
144
198
|
|
|
145
|
-
- Re-supply
|
|
146
|
-
and the stat id set when rebuilding a runtime; snapshots contain mutable
|
|
199
|
+
- Re-supply current tuning such as magazine capacity/reload duration
|
|
200
|
+
and immutable configuration such as the stat id set when rebuilding a runtime; snapshots contain mutable
|
|
147
201
|
state, not content catalogs.
|
|
148
202
|
- Restore externally-owned reserve data before calling `Magazine.restore`.
|
|
149
203
|
- Persist the injected clock alongside expiring modifier snapshots or convert
|
|
@@ -123,14 +123,48 @@ exposes `elapsedMs()`/`restore()` for HUD and snapshots:
|
|
|
123
123
|
```ts
|
|
124
124
|
const snapshot = {
|
|
125
125
|
cadenceMs: rifle.cadence.elapsedMs(),
|
|
126
|
-
|
|
126
|
+
magazine: rifle.magazine?.snapshot() ?? null,
|
|
127
127
|
};
|
|
128
128
|
// After load: recreate the runtime from the same config, then:
|
|
129
129
|
rifle.cadence.restore(snapshot.cadenceMs);
|
|
130
|
+
if (snapshot.magazine !== null && !rifle.magazine?.restore(snapshot.magazine)) {
|
|
131
|
+
throw new Error("magazine reserve configuration differs");
|
|
132
|
+
}
|
|
130
133
|
```
|
|
131
134
|
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
Restore caller-owned reserve storage first. Recreate with the current equipment
|
|
136
|
+
capacity/reload duration, then restore the full magazine snapshot; see the
|
|
137
|
+
[portable runtime-state recipe](portable-runtime-state.md) for live retuning.
|
|
138
|
+
|
|
139
|
+
## Scene-backed projectiles and spread
|
|
140
|
+
|
|
141
|
+
When a project already uses `createProjectileSystem`, keep its
|
|
142
|
+
`fireProjectile(input)` → `settleProjectile(shotId)` call path. Pass authoritative
|
|
143
|
+
seeded randomness as `ProjectileSystemDeps.rng`. A nonzero `Aim.spread` (degrees)
|
|
144
|
+
or item `spread` samples an independent uniform solid-angle cone for each pellet
|
|
145
|
+
(the half angle is clamped to 90 degrees);
|
|
146
|
+
`pellets` chooses the count. Each sampled ray resolves its own nearest eligible
|
|
147
|
+
receiver and cover. An explicit `{ origin, direction }` aim remains an exact ray.
|
|
148
|
+
`willHitProjectile` stays a center-ray preview and consumes no randomness.
|
|
149
|
+
|
|
150
|
+
GameContext forwards its authoritative RNG to the projectile system. Standalone
|
|
151
|
+
callers must inject one for reproducible shots; the fallback is `Math.random`.
|
|
152
|
+
Aim fields, explicit ray vectors, and the chosen origin-policy vectors are copied
|
|
153
|
+
at fire time before RNG callbacks, so reusing caller input cannot redirect an
|
|
154
|
+
in-flight shot. The default policy is copied for each shot too. World entities
|
|
155
|
+
and colliders remain queried at settlement time.
|
|
156
|
+
Two RNG draws are captured at fire time per nonzero-spread pellet. Settlement
|
|
157
|
+
preserves the selected eye/muzzle/converge origin, named hitboxes, LOS, and
|
|
158
|
+
blocking colliders. Ballistic paths are unchanged. `maxPellets` defaults to 64
|
|
159
|
+
and clamps to [1, 256] to bound per-shot ray work. Zero-spread pellets each hit
|
|
160
|
+
the nearest receiver; they no longer cycle through receivers down one ray.
|
|
161
|
+
|
|
162
|
+
Scrap Signal is the first adopter: `items/use-handlers.ts` already calls these
|
|
163
|
+
projectile APIs, and its Breacher adds pellet/range/ammo tradeoffs. Its combat
|
|
164
|
+
adapter also needs `Magazine.retune` for live equipment/talent changes without
|
|
165
|
+
reconstructing ammo state. Games remain on published packages until the
|
|
166
|
+
coordinator verifies and unlocks the SDK release; these adoption points do not
|
|
167
|
+
claim native play against unpublished code.
|
|
134
168
|
|
|
135
169
|
## Ownership
|
|
136
170
|
|
|
@@ -34,6 +34,8 @@ interceptors). See the [portable damage/effects recipe](recipes/portable-damage-
|
|
|
34
34
|
|
|
35
35
|
## Effects and projectiles
|
|
36
36
|
|
|
37
|
+
Cold projectile restore requires the receiving owner's pellet limit and radius-aware sweep to support the captured launch. Incompatible or inconsistent pellet, cone-sample and flight counts reject atomically; never truncate saved flights or discard pending settlements.
|
|
38
|
+
|
|
37
39
|
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).
|
|
38
40
|
|
|
39
41
|
```ts
|
|
@@ -61,15 +63,35 @@ settleProjectile(shotId) // authoritative → { at, hits
|
|
|
61
63
|
|
|
62
64
|
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.
|
|
63
65
|
|
|
66
|
+
`defineGame({ projectileTravel })` accepts `GameProjectileTravelOptions` from
|
|
67
|
+
`@jgengine/core/game/defineGame`: the existing `maxActive`, `maxRetained` and
|
|
68
|
+
`maxTargets` budgets plus `sweep(ctx, from, to, step)`,
|
|
69
|
+
`acceleration(ctx, position, velocity, time, input)` and `targets(ctx)`.
|
|
70
|
+
Each callback receives the live context of that world, including when one
|
|
71
|
+
definition hosts multiple worlds; the authoritative clock remains engine-owned.
|
|
72
|
+
Supply a radius-aware `sweep` for finite-radius travel through authored cover.
|
|
73
|
+
The default cover query supports radius zero and rejects larger radii.
|
|
74
|
+
`targets` selects bounded entity IDs for the default moving-target query;
|
|
75
|
+
omission selects that world's entities, and exceeding `maxTargets` (default
|
|
76
|
+
2048) throws instead of dropping blockers. A custom sweep owns its complete
|
|
77
|
+
collision policy, including cover and receivers.
|
|
78
|
+
Supplied `acceleration` fully replaces the authored sampler; call
|
|
79
|
+
`ctx.environment.accelerationAt(position, time, windResponse, maxAcceleration,
|
|
80
|
+
forceMask)` explicitly when composing authored forces. Omitting it keeps the
|
|
81
|
+
authored sampler. Providers are unsaved world policy; projectile snapshot and
|
|
82
|
+
restore retain launch and motion state and use the receiving world's providers.
|
|
83
|
+
|
|
64
84
|
## Death
|
|
65
85
|
|
|
66
86
|
Resolved **once** by the engine when the last stat in the receive order hits min. No HP polling in `onTick`, ever.
|
|
67
87
|
|
|
68
88
|
- `entity.died` is emitted (before despawn — handlers can still read the victim's stats), then reason-matching `onDeath` entries run.
|
|
69
|
-
- `DeathReason = { kind: "player_kill", killerUserId, via? } | { kind: "environment", source } | { kind: "self", source }`.
|
|
70
|
-
- `onDeath.drops` tables are rolled and **granted to the killer** on player kills (emits `loot.granted`) when `onDeath.dropMode` is `"grant"` (default); `onDeath.command` runs through `ctx.game.commands
|
|
89
|
+
- `DeathReason = { kind: "player_kill", killerUserId, via? } | { kind: "environment", source } | { kind: "self", source }`. Local players, hosted members and uniquely owned pawns attribute automatically. `ctx.player.possession.ownerOf(entityId)` resolves recorded ownership without inventing an owner; shared ownership is ambiguous and returns `undefined`.
|
|
90
|
+
- `onDeath.drops` tables are rolled and **granted to the killer** on player kills (emits `loot.granted`) when `onDeath.dropMode` is `"grant"` (default); `onDeath.command` runs through `ctx.game.commands.runAs` with the resolved killer. XP commands, quest kill events and loot therefore use the same actor identity on a remote host.
|
|
71
91
|
- `onDeath.dropMode: "world"` routes item drops through a scatter impulse into ground `worldItem`s instead of straight to inventory (currency drops still grant directly) — tune the impulse with `onDeath.scatter: { radius, minRadius?, height? }` (defaults from `game/worldItem`'s `DEFAULT_SCATTER`).
|
|
92
|
+
- Non-player kills suppress runtime loot by default. To opt an item table into environmental or unowned deaths, author `onDeath: { dropMode: "world", drops: [{ table: "salvage", when: { reason: "any" } }] }`. Only explicit `"any"` tables spawn in this case; neighboring ordinary tables stay suppressed. Bag grants and currency still require a resolved player killer, including when the table uses `"any"`.
|
|
72
93
|
- Respawning under the same instance id revives it (it can die again). Same-id respawn must not happen synchronously inside the `entity.died` handler — defer a tick.
|
|
94
|
+
- `ctx.hydrate` and `ctx.restore` also revive restored entities whose terminal receive pools are above their minima. Snapshots captured inside `entity.died` still contain the victim before despawn; their depleted terminal pools retain the duplicate-death guard.
|
|
73
95
|
- `quest.bind("entity.died")` credits kill objectives from the same event; leaderboards and kill feeds hang off it too.
|
|
74
96
|
|
|
75
97
|
## Damage interception and threshold transitions
|
|
@@ -11,10 +11,47 @@ This skill owns non-combat rules and state: systems, catalogs, inventory/items,
|
|
|
11
11
|
|
|
12
12
|
Use [capabilities.md](capabilities.md) for intent discovery, [api.md](api.md) for signatures, and [reference-systems.md](reference-systems.md) for scheduling details before hand-rolling a subsystem.
|
|
13
13
|
|
|
14
|
+
For multi-cell inventory items, use `createShapedGrid`, `canPlace`, and
|
|
15
|
+
`findShapedPlacement` (`inventory/shapedGrid`). The caller owns item footprints,
|
|
16
|
+
rotation policy, saves, and UI; [shaped inventory placement](recipes/shaped-inventory.md)
|
|
17
|
+
shows bounded first-fit search. A stack-slot `InventoryGrid` does not replace
|
|
18
|
+
footprint rotation or spatial packing.
|
|
19
|
+
|
|
14
20
|
Existing projects can keep player state and saves behind `LevelingStatAccess`.
|
|
15
21
|
Follow the [portable XP/leveling recipe](recipes/portable-xp-leveling.md) for
|
|
16
22
|
custom stat ids, immutable store writes, multiple level events, and JSON resume.
|
|
17
23
|
|
|
24
|
+
Persist rolled items with `createItemInstanceRegistry` (`item/itemInstanceRegistry`):
|
|
25
|
+
`state()` detaches definitions, runtime ids, prefix, and allocation sequence;
|
|
26
|
+
`restore(state)` replaces the registry and rejects a different prefix. Keep
|
|
27
|
+
definitions structured-cloneable and JSON-compatible for JSON saves. Save the
|
|
28
|
+
inventory's returned runtime ids alongside this state; do not regenerate rolls
|
|
29
|
+
or infer the next id from the surviving items.
|
|
30
|
+
For large collections, inject caller-owned `ItemInstanceStorage` and use
|
|
31
|
+
`statePages(pageSize)` / `restorePages(pages)` to clone one batch at a time.
|
|
32
|
+
Follow the [paged item storage recipe](recipes/paged-item-storage.md) for
|
|
33
|
+
atomic replacement, allocator reattachment, and export consistency.
|
|
34
|
+
|
|
35
|
+
Generate coherent combinations with `generate` (`item/itemgen`): dependent
|
|
36
|
+
pools and `GenStep.accept` constrain later choices using earlier picks.
|
|
37
|
+
Pins consume no choice RNG and must satisfy the same pool and constraints;
|
|
38
|
+
incompatible pins backtrack earlier unpinned choices or return `unsatisfiable`
|
|
39
|
+
within the configured attempt and pick budgets.
|
|
40
|
+
|
|
41
|
+
Validate generated quest catalogs with `validateQuestCatalog` (`game/questCatalog`).
|
|
42
|
+
Follow the [quest authoring recipe](recipes/quest-authoring.md) for bounded batches,
|
|
43
|
+
canonical facts, external unlock declarations, and precise repair diagnostics.
|
|
44
|
+
|
|
45
|
+
For cross-catalog sources and skill gates, compose `validateContentReferences` and
|
|
46
|
+
`validateContentProgression` (`game/contentValidation`) with `validateRecipeCatalog`
|
|
47
|
+
(`crafting/recipeCatalog`). Follow [content validation](reference-content-validation.md).
|
|
48
|
+
Games declare complete sources before treating blocked routes as errors; costs,
|
|
49
|
+
consumption, exclusive choices, story and balance still need game-owned playtests.
|
|
50
|
+
|
|
51
|
+
For generated chapter dialogue, use the [narrative authoring recipe](recipes/narrative-authoring.md)
|
|
52
|
+
and `npx jgengine recipe narrative-authoring`: freeze reviewed canon, validate
|
|
53
|
+
bounded batches, repair located issues and require explicit semantic approval.
|
|
54
|
+
|
|
18
55
|
## Canonical workflow
|
|
19
56
|
|
|
20
57
|
1. Define plain serializable state and stable ids.
|
|
@@ -26,6 +63,16 @@ custom stat ids, immutable store writes, multiple level events, and JSON resume.
|
|
|
26
63
|
|
|
27
64
|
Prefer `defineSystem` and `defineGame({ systems })` for scheduled capabilities. Keep boot/join in the game loop only when it is truly lifecycle glue. System ordering, frequency, and serialization are explicit; avoid a giant per-frame callback.
|
|
28
65
|
|
|
66
|
+
For staffed production, use `stationOutputRate` (`work/staffedStation`) to resolve
|
|
67
|
+
caller-owned worker stats and efficiency, then feed the rate to `tickProduction`.
|
|
68
|
+
Keep staffing and production progress in the same serializable store; the
|
|
69
|
+
[staffed production example](reference-systems.md#staffed-production) composes
|
|
70
|
+
the existing scheduler and save/reset boundaries without a separate manager.
|
|
71
|
+
|
|
72
|
+
For whole-world saves, follow [authoritative state](reference-systems.md#authoritative-state); persistence and client replication use separate context methods.
|
|
73
|
+
Follow [truthful device saves](recipes/device-save-status.md) for denied storage,
|
|
74
|
+
quota errors, checkpoint retry, and save status UI.
|
|
75
|
+
|
|
29
76
|
## Design rules
|
|
30
77
|
|
|
31
78
|
- Caller data owns nouns, formulas, tables, costs, tiers, and content.
|
|
@@ -34,6 +81,15 @@ Prefer `defineSystem` and `defineGame({ systems })` for scheduled capabilities.
|
|
|
34
81
|
- Random selection accepts injected RNG and stable ordering.
|
|
35
82
|
- Large collections use indexes, queues, or bounded reconciliation rather than repeated scans.
|
|
36
83
|
|
|
84
|
+
Quest journals index active objectives by kill target and collected item. Bind
|
|
85
|
+
`entity.died` and `inventory.added` once and keep the returned unsubscribe handles;
|
|
86
|
+
credit follows catalog order, and quests activated by a credit callback join the
|
|
87
|
+
next event. Register replacement definitions after editing objective targets,
|
|
88
|
+
items, or party-sharing policy. Save hydration rebuilds the active indexes.
|
|
89
|
+
Shared kills query nearby party members for matching objectives active on any
|
|
90
|
+
user, so the killer need not have that quest; direct kills and collection only
|
|
91
|
+
visit the actor's matching active objectives.
|
|
92
|
+
|
|
37
93
|
## Part-composed character motion
|
|
38
94
|
|
|
39
95
|
A character kit-bashed from primitives/`ModelPart`s (no skeleton, no clips) animates procedurally: tag parts with `role` (`leg.l`, `arm.r`, `head`, `tail`, `wing.l`…) and the shell's part-motion rig drives counter-phase limb swing from the entity's live movement speed, idle breathe/sway, hit flinch on `combat.hitReaction`, and a death topple on `entity.died` — no game-side frame loops. Tune with `ModelConfig.partMotion`; curves are pure and deterministic (`@jgengine/core/game/partAnimation`), so crowds de-sync by instance-id phase and replays stay stable. Soft characters (blobs, slimes) opt into volume-conserving squash-and-stretch with `partMotion: { squashAmp }` — footfall squash, jelly idle breathe, flinch pulse — and `deathStyle: "splat"` flattens out on death instead of the default sideways topple. Rigged GLBs keep using `ModelConfig.animation` — roles are only for rig-less compositions.
|
|
@@ -55,3 +111,10 @@ A character kit-bashed from primitives/`ModelPart`s (no skeleton, no clips) anim
|
|
|
55
111
|
- Reset a profile with `initialPlayerState(runtime, userId)` from the same `onNewPlayer` hook used at join; insolvency never deletes a world.
|
|
56
112
|
- `ctx.game.chat.send(userId, text)` targets global chat; `recent({ limit })` returns at most 100 messages. The default is 240 characters and one message per author per channel every two seconds. Rejected messages need visible UI feedback.
|
|
57
113
|
- Hosts reuse `validateChatMessage` and `decideRateWindow`, storing the rate window transactionally with each accepted operation. Preserve chat rate windows when saving/restoring chat state.
|
|
114
|
+
|
|
115
|
+
For timed away missions, compose the pure transitions in `work/expedition` with
|
|
116
|
+
your existing `defineStore` and clock. The [expedition recipe](recipes/expeditions.md)
|
|
117
|
+
keeps bounded offline catch-up, finite supplies, return/death, capacity and RNG
|
|
118
|
+
continuity in one plain saveable state. Authored encounters, hazard/supply policy,
|
|
119
|
+
loot tables, quality ramps and text remain caller-owned; use `timeScaledRarity`
|
|
120
|
+
(`game/lootModifiers`) for elapsed-time odds with pipeline provenance.
|