@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
|
@@ -65,6 +65,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
65
65
|
|
|
66
66
|
- `createItemUse` (function) · `import { createItemUse } from "@jgengine/core/gameplay"`
|
|
67
67
|
|
|
68
|
+
## content-progression-validation — diagnose unavailable sources and blocked cross-content progression under explicit game-owned rules without rejecting intentional seeded cycles
|
|
69
|
+
|
|
70
|
+
- `validateContentProgression` (function) · `import { validateContentProgression } from "@jgengine/core/game/contentValidation"`
|
|
71
|
+
|
|
72
|
+
## content-reference-validation — locate duplicate content ids and broken references across game-owned catalog namespaces with repair guidance
|
|
73
|
+
|
|
74
|
+
- `validateContentReferences` (function) · `import { validateContentReferences } from "@jgengine/core/game/contentValidation"`
|
|
75
|
+
|
|
76
|
+
## control-suspension — Suspend player input across overlapping menus without stopping shared simulation.
|
|
77
|
+
|
|
78
|
+
- `suspendPlayControls` (function) · `import { suspendPlayControls } from "@jgengine/core/game/controlGate"`
|
|
79
|
+
|
|
68
80
|
## cosmetics — equip cosmetic skins and customizations by slot
|
|
69
81
|
|
|
70
82
|
- `createCosmetics` (function) · `import { createCosmetics } from "@jgengine/core/gameplay"`
|
|
@@ -112,6 +124,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
112
124
|
|
|
113
125
|
- `createGameDialogue` (function) · `import { createGameDialogue } from "@jgengine/core/gameplay"`
|
|
114
126
|
|
|
127
|
+
## dialogue-graph-validation — diagnose duplicate ids, broken branches, unreachable dialogue, and nodes without a route to an ending before play
|
|
128
|
+
|
|
129
|
+
- `validateDialogueGraph` (function) · `import { validateDialogueGraph } from "@jgengine/core/game/dialogueGraph"`
|
|
130
|
+
|
|
115
131
|
## dialogue-graph-view — select a conversation node's render-ready view (speaker, line, branching choices) from a serializable dialogue graph
|
|
116
132
|
|
|
117
133
|
- `selectDialogueView` (function) · `import { selectDialogueView } from "@jgengine/core/gameplay"`
|
|
@@ -144,6 +160,22 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
144
160
|
|
|
145
161
|
- `createEventTicker` (function) · `import { createEventTicker } from "@jgengine/core/gameplay"`
|
|
146
162
|
|
|
163
|
+
## expedition-dispatch — dispatch a saveable timed away mission with finite supplies and a seeded RNG cursor
|
|
164
|
+
|
|
165
|
+
- `dispatchExpedition` (function) · `import { dispatchExpedition } from "@jgengine/core/gameplay"`
|
|
166
|
+
|
|
167
|
+
## expedition-recall — begin a safe fractional-time return after settling the pending expedition frontier
|
|
168
|
+
|
|
169
|
+
- `recallExpedition` (function) · `import { recallExpedition } from "@jgengine/core/gameplay"`
|
|
170
|
+
|
|
171
|
+
## expedition-return — complete an authorized instant return without skipping unsettled ticks or reviving death
|
|
172
|
+
|
|
173
|
+
- `returnExpeditionNow` (function) · `import { returnExpeditionNow } from "@jgengine/core/gameplay"`
|
|
174
|
+
|
|
175
|
+
## expedition-settle — settle bounded offline expedition ticks without losing supplies rewards or RNG continuity
|
|
176
|
+
|
|
177
|
+
- `settleExpedition` (function) · `import { settleExpedition } from "@jgengine/core/gameplay"`
|
|
178
|
+
|
|
147
179
|
## fire-input — bind a key or mouse button to a named action that runs the same-named command with the aim; repeatMs auto-fires while held
|
|
148
180
|
|
|
149
181
|
- `ActionCodesMap` (type) · `import { ActionCodesMap } from "@jgengine/core/gameplay"`
|
|
@@ -289,6 +321,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
289
321
|
|
|
290
322
|
- `createNameGenerator` (function) · `import { createNameGenerator } from "@jgengine/core/gameplay"`
|
|
291
323
|
|
|
324
|
+
## narrative-batch-validation — locate stale canon, oversized batches, explicit character/fact/chronology contradictions and dialogue graph issues for bounded repair
|
|
325
|
+
|
|
326
|
+
- `validateNarrativeBatch` (function) · `import { validateNarrativeBatch } from "@jgengine/core/game/narrativeAuthoring"`
|
|
327
|
+
|
|
328
|
+
## narrative-canon — freeze caller-reviewed character ids, facts and chronology for a narrative authoring revision
|
|
329
|
+
|
|
330
|
+
- `freezeNarrativeCanon` (function) · `import { freezeNarrativeCanon } from "@jgengine/core/game/narrativeAuthoring"`
|
|
331
|
+
|
|
292
332
|
## notice-feed — serializable tone-tagged notice/event feed bounded by count and age (toasts, life events, killfeed)
|
|
293
333
|
|
|
294
334
|
- `appendFeed` (function) · `import { appendFeed } from "@jgengine/core/gameplay"`
|
|
@@ -372,6 +412,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
372
412
|
|
|
373
413
|
- `evalCurve` (function) · `import { evalCurve } from "@jgengine/core/gameplay"`
|
|
374
414
|
|
|
415
|
+
## quest-catalog-validation — diagnose authored quest ids, prerequisite and reward references, quantities, and blocked dependencies with caller-declared external unlocks
|
|
416
|
+
|
|
417
|
+
- `validateQuestCatalog` (function) · `import { validateQuestCatalog } from "@jgengine/core/game/questCatalog"`
|
|
418
|
+
|
|
375
419
|
## quest-log — track accepted quests and their per-objective progress
|
|
376
420
|
|
|
377
421
|
- `createQuestJournal` (function) · `import { createQuestJournal } from "@jgengine/core/gameplay"`
|
|
@@ -393,6 +437,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
393
437
|
|
|
394
438
|
- `createRaceState` (function) · `import { createRaceState } from "@jgengine/core/gameplay"`
|
|
395
439
|
|
|
440
|
+
## recipe-catalog-validation — locate broken crafting item/station/unlock references, duplicate inputs and invalid authored quantities with repair guidance
|
|
441
|
+
|
|
442
|
+
- `validateRecipeCatalog` (function) · `import { validateRecipeCatalog } from "@jgengine/core/crafting/recipeCatalog"`
|
|
443
|
+
|
|
396
444
|
## resolve — the current tier/band label for a keyed or scalar value
|
|
397
445
|
|
|
398
446
|
- `tierAt` (function) · `import { tierAt } from "@jgengine/core/gameplay"`
|
|
@@ -443,6 +491,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
443
491
|
|
|
444
492
|
- `setValue` (function) · `import { setValue } from "@jgengine/core/gameplay"`
|
|
445
493
|
|
|
494
|
+
## shaped-grid — a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
|
|
495
|
+
|
|
496
|
+
- `canPlace` (function) · `import { canPlace } from "@jgengine/core/inventory/shapedGrid"`
|
|
497
|
+
- `createShapedGrid` (function) · `import { createShapedGrid } from "@jgengine/core/inventory/shapedGrid"`
|
|
498
|
+
- `moveShaped` (function) · `import { moveShaped } from "@jgengine/core/inventory/shapedGrid"`
|
|
499
|
+
- `placeShaped` (function) · `import { placeShaped } from "@jgengine/core/inventory/shapedGrid"`
|
|
500
|
+
- `removeShaped` (function) · `import { removeShaped } from "@jgengine/core/inventory/shapedGrid"`
|
|
501
|
+
|
|
502
|
+
## shaped-placement — find a shaped inventory item's first fit with ordered rotations, row-major placement, and an explicit search budget
|
|
503
|
+
|
|
504
|
+
- `findShapedPlacement` (function) · `import { findShapedPlacement } from "@jgengine/core/inventory/shapedGrid"`
|
|
505
|
+
|
|
446
506
|
## shop-grid — serializable vendor/shop stock with buy/sell over a caller-owned wallet — finite/unlimited qty, sell-back, restock/setPrice, snapshot/restore
|
|
447
507
|
|
|
448
508
|
- `createShopStock` (function) · `import { createShopStock } from "@jgengine/core/gameplay"`
|
|
@@ -459,6 +519,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
459
519
|
|
|
460
520
|
- `splitViewports` (function) · `import { splitViewports } from "@jgengine/core/game/viewports"`
|
|
461
521
|
|
|
522
|
+
## staffed-station-rate — turn bounded caller-owned worker stats and efficiency into a production rate
|
|
523
|
+
|
|
524
|
+
- `stationOutputRate` (function) · `import { stationOutputRate } from "@jgengine/core/gameplay"`
|
|
525
|
+
|
|
462
526
|
## stat-graph — derive game-defined stats from named inputs with provenance and preview
|
|
463
527
|
|
|
464
528
|
- `createStatGraph` (function) · `import { createStatGraph } from "@jgengine/core/gameplay"`
|
|
@@ -483,6 +547,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
483
547
|
|
|
484
548
|
- `tick` (function) · `import { tick } from "@jgengine/core/gameplay"`
|
|
485
549
|
|
|
550
|
+
## time-scaled-loot — shape loot odds by elapsed hours with caller-authored curves and pipeline provenance
|
|
551
|
+
|
|
552
|
+
- `timeScaledRarity` (function) · `import { timeScaledRarity } from "@jgengine/core/gameplay"`
|
|
553
|
+
|
|
486
554
|
## toast-feed — queue of transient self-expiring on-screen messages (toasts, announcer, kill-feed)
|
|
487
555
|
|
|
488
556
|
- `appendToast` (function) · `import { appendToast } from "@jgengine/core/gameplay"`
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Truthful device saves
|
|
2
|
+
|
|
3
|
+
Use `ctx.game.save` for the authoritative world or `createSaveStore` for a caller-owned,
|
|
4
|
+
serializable checkpoint. Default offline context saves use strict local storage:
|
|
5
|
+
denied reads, quota errors, unavailable storage, and denied removals report
|
|
6
|
+
`status() === "error"`. Explicitly injected backends retain their own policy.
|
|
7
|
+
|
|
8
|
+
For an existing Wayfarer/OddOrbit-style run checkpoint, keep the game's checkpoint
|
|
9
|
+
schema and validation, and replace only its persistence adapter:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { createSaveStore, localSaveBackend } from "@jgengine/core/game/saveStore";
|
|
13
|
+
|
|
14
|
+
const checkpoint = createSaveStore({
|
|
15
|
+
key: "my-game:run",
|
|
16
|
+
initial: { roomId: "start", cleared: [] as string[] },
|
|
17
|
+
backend: localSaveBackend(undefined, { errorMode: "throw" }),
|
|
18
|
+
onError: (error) => console.error("Checkpoint storage failed", error),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
checkpoint.set({ roomId: "harbor", cleared: ["intro"] });
|
|
22
|
+
await checkpoint.save();
|
|
23
|
+
const message = checkpoint.status() === "saved"
|
|
24
|
+
? "Checkpoint saved"
|
|
25
|
+
: "Checkpoint could not be saved. Keep playing and retry.";
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`save()` handles backend rejection; awaiting it alone does not prove success.
|
|
29
|
+
Subscribe to status changes for autosave UI. A rejected payload write preserves
|
|
30
|
+
the previous stored checkpoint while live edits remain available for retry.
|
|
31
|
+
Denied load/remove leaves the current value intact. Whole-world load/hasSave/slot
|
|
32
|
+
switches return false after a denied payload read and do not restore cached state. Slot metadata is a separate
|
|
33
|
+
storage operation: a payload can be written before its slot index fails. That
|
|
34
|
+
reports `error` too; there is no cross-key rollback or transaction guarantee.
|
|
35
|
+
Async index updates after load also report through status and `onError`.
|
|
36
|
+
|
|
37
|
+
`createKeyValueStore` and `localSaveBackend` preserve their legacy silent fallback
|
|
38
|
+
by default. Set `errorMode: "throw"` when persistence must succeed; optionally
|
|
39
|
+
provide `onError({ operation, key, error })` to observe reads, writes and removals.
|
|
40
|
+
Strict key-value writes/removals preserve the prior cell on rejection. Observers
|
|
41
|
+
must not throw. Shape validation and corruption recovery still belong to the
|
|
42
|
+
game's checkpoint schema or a supplied deserializer.
|
|
43
|
+
|
|
44
|
+
For intentional session-only/headless success, use `memorySaveBackend()`,
|
|
45
|
+
`persist: { storage: "memory" }`, or a key-value cell with `storage: null`.
|
|
46
|
+
Memory success does not promise persistence across reloads.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Timed away missions
|
|
2
|
+
|
|
3
|
+
Use the pure transitions in `@jgengine/core/work/expedition` to orchestrate a timed
|
|
4
|
+
away mission. The caller owns its clock, store, identities, authored content and
|
|
5
|
+
policy. Core owns event ordering and the exploring → returning → home / dead
|
|
6
|
+
lifecycle, not a scheduler, genre preset or persistence backend.
|
|
7
|
+
|
|
8
|
+
All `*Ms` fields and log `at` timestamps are epoch milliseconds. Persist the entire
|
|
9
|
+
`ExpeditionState`, including `cursor`, supplies, carried rewards and the completed
|
|
10
|
+
tick frontier. `settleExpedition(config, state, nowMs)` uses `accrueSince` and
|
|
11
|
+
`steppedCatchUp`; a multi-day gap follows the same loot → hazard → ordered supplies
|
|
12
|
+
→ death → capacity recall sequence as incremental calls. Each callback is pure
|
|
13
|
+
and receives the state at that event's time, with the persisted RNG stream.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createLootPipeline } from "@jgengine/core/game/lootPipeline";
|
|
17
|
+
import { timeScaledRarity } from "@jgengine/core/game/lootModifiers";
|
|
18
|
+
import {
|
|
19
|
+
dispatchExpedition, settleExpedition, recallExpedition, returnExpeditionNow,
|
|
20
|
+
type ExpeditionConfig, type ExpeditionState,
|
|
21
|
+
} from "@jgengine/core/work/expedition";
|
|
22
|
+
import { defineStore } from "@jgengine/core/store/defineStore";
|
|
23
|
+
import { defineSystem } from "@jgengine/core/game/defineSystem";
|
|
24
|
+
|
|
25
|
+
const rewards = createLootPipeline<{ elapsedMs: number }>({
|
|
26
|
+
id: "survey-rewards",
|
|
27
|
+
stages: [{ id: "survey", table: {
|
|
28
|
+
id: "survey-pool", entries: [
|
|
29
|
+
{ item: "sample", count: 1, weight: 9 },
|
|
30
|
+
{ item: "artifact", count: 1, weight: 1 },
|
|
31
|
+
],
|
|
32
|
+
}, modifiers: [timeScaledRarity({
|
|
33
|
+
id: "survey-depth",
|
|
34
|
+
ramp: (entry, hours) => entry.item === "artifact" ? 1 + hours : 1,
|
|
35
|
+
})] }],
|
|
36
|
+
});
|
|
37
|
+
const survey: ExpeditionConfig<{ elapsedMs: number }> = {
|
|
38
|
+
tickMs: 20 * 60_000,
|
|
39
|
+
loot: rewards,
|
|
40
|
+
lootCtx: state => ({ elapsedMs: state.elapsedMs }),
|
|
41
|
+
maxHp: state => Math.max(1, 100 - state.exposure),
|
|
42
|
+
hazard: (_state, rng) => ({ damage: rng() < 0.25 ? 4 : 1, exposure: 1 }),
|
|
43
|
+
supplies: [
|
|
44
|
+
{ key: "filter", when: state => state.exposure >= 10, clearExposure: 10 },
|
|
45
|
+
{ key: "medicine", when: state => state.hp < 30, restoreHp: 25 },
|
|
46
|
+
],
|
|
47
|
+
returnFraction: 0.5,
|
|
48
|
+
carryCap: 100,
|
|
49
|
+
maxTicksPerSettle: 1000,
|
|
50
|
+
logLimit: 200,
|
|
51
|
+
describe: event => `Survey: ${event.kind}`, // Replace with authored/localized prose.
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export function awayMission(nowMs: () => number) {
|
|
55
|
+
const mission = defineStore<ExpeditionState | null>("away-mission", null);
|
|
56
|
+
const dispatch = () => dispatchExpedition({
|
|
57
|
+
id: "surveyor", startAtMs: nowMs(), hp: 100,
|
|
58
|
+
supplies: { medicine: 5, filter: 2 }, seed: "caller-campaign:surveyor",
|
|
59
|
+
});
|
|
60
|
+
const system = defineSystem({
|
|
61
|
+
id: "away-mission", tick: { type: "frame" },
|
|
62
|
+
create(ctx) { mission.write(ctx, dispatch()); },
|
|
63
|
+
update(ctx) { mission.update(ctx, state => state === null ? null : settleExpedition(survey, state, nowMs())); },
|
|
64
|
+
reset(ctx) { mission.write(ctx, dispatch()); },
|
|
65
|
+
});
|
|
66
|
+
return { mission, system, recallExpedition, returnExpeditionNow };
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Install the returned system with `defineGame({ systems: [system] })`. Store writes
|
|
71
|
+
and `ctx.state()` / `ctx.restore()` keep the ordinary native save boundary; no
|
|
72
|
+
expedition-specific backend is required. A caller-owned store may instead call
|
|
73
|
+
the same pure transitions and atomically persist each returned value. Decode and
|
|
74
|
+
validate your saved schema at that boundary. Keep policy callbacks free of side
|
|
75
|
+
effects; a rejected policy transition does not write through the pure API.
|
|
76
|
+
|
|
77
|
+
A courier composition can use a parcel-only loot pipeline, no exposure,
|
|
78
|
+
`maxHp: () => 20`, rations restoring health, and `returnFraction: 0.25`. Both
|
|
79
|
+
compositions use the same orchestration. Item names, encounter notices, quality
|
|
80
|
+
curves, hazard magnitudes, immunity and supply thresholds are authored data and
|
|
81
|
+
callbacks; they are not SDK content presets. `timeScaledRarity` multiplies weighted
|
|
82
|
+
odds or independent chances (clamped to one) and preserves the loot pipeline's
|
|
83
|
+
original/effective odds and modifier ids. There is no built-in rarity taxonomy.
|
|
84
|
+
|
|
85
|
+
`carryCap` counts **item units**, not drop records. Rolled item quantities are
|
|
86
|
+
admitted in pipeline order up to the remaining cap; currency does not consume
|
|
87
|
+
item capacity and is carried in full. Carried counts merge by identity. Each loot
|
|
88
|
+
event's `drops` contains accepted quantities, while `loot` contains the full rolled
|
|
89
|
+
resolution and provenance, including quantities excluded by capacity. Core starts
|
|
90
|
+
auto-return at the event time that filled capacity. Returning never rolls rewards,
|
|
91
|
+
consumes supplies or takes hazard damage. Arrival is recorded at its exact deadline
|
|
92
|
+
even when one offline call observes it much later. Dead missions never revive.
|
|
93
|
+
|
|
94
|
+
`maxTicksPerSettle` bounds work without dropping elapsed ticks. If the budget runs
|
|
95
|
+
out, the completed frontier remains behind `nowMs`. Recall and instant return use
|
|
96
|
+
that same settlement pipeline and postpone the transition while due ticks remain;
|
|
97
|
+
repeat at the same target until caught up. The caller owns instant-return payment
|
|
98
|
+
and authorization. Count-bound `appendFeed` keeps the newest log events without
|
|
99
|
+
discarding carried rewards or RNG progress. Supply rules run in array order and
|
|
100
|
+
spend one unit only when restoration/cleansing changes a vital; health can recover
|
|
101
|
+
from zero during that tick before the death decision.
|
|
102
|
+
|
|
103
|
+
Configuration is runtime policy, never saved into state. Retuning applies the
|
|
104
|
+
current policy to unsettled ticks; a changed cadence anchors at the last completed
|
|
105
|
+
tick. Persist a policy/version id alongside your state if historical policy must
|
|
106
|
+
be replayed exactly. A lower carry cap preserves already-owned rewards and recalls
|
|
107
|
+
after the next due event (or immediately when no tick is due). Monotonic settlement
|
|
108
|
+
does not replay time when the caller's clock moves backward.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Repair dialogue batches against reviewed canon
|
|
2
|
+
|
|
3
|
+
`npx jgengine recipe narrative-authoring` prints an SDK-typechecked function with
|
|
4
|
+
injected `generate` and `review` callbacks. Use it for a bounded chapter's
|
|
5
|
+
conversations, then stage the accepted batch for playtesting. It selects no
|
|
6
|
+
model, network service, assets or genre.
|
|
7
|
+
|
|
8
|
+
The author supplies a chapter brief: intended experience, voices, prerequisites,
|
|
9
|
+
player choices and their consequences. Keep reviewed canon separate from draft
|
|
10
|
+
prose. `freezeNarrativeCanon` detaches and freezes a revision, character ids,
|
|
11
|
+
exact string facts and a strict event order. Include only events whose order is
|
|
12
|
+
settled; branching or uncertain chronology belongs in semantic review. A new
|
|
13
|
+
fact or changed fact requires a separately reviewed canon revision.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import {
|
|
17
|
+
freezeNarrativeCanon,
|
|
18
|
+
validateNarrativeBatch,
|
|
19
|
+
type NarrativeBatch,
|
|
20
|
+
} from "@jgengine/core/game/narrativeAuthoring";
|
|
21
|
+
|
|
22
|
+
const canon = freezeNarrativeCanon({
|
|
23
|
+
revision: "inquiry-r7",
|
|
24
|
+
characterIds: ["diver"],
|
|
25
|
+
facts: { hatch: "sealed" },
|
|
26
|
+
eventIds: ["descent", "alarm"],
|
|
27
|
+
});
|
|
28
|
+
const candidate: NarrativeBatch = {
|
|
29
|
+
canonRevision: canon.revision,
|
|
30
|
+
dialogues: [{
|
|
31
|
+
id: "deposition",
|
|
32
|
+
graph: {
|
|
33
|
+
start: "witness",
|
|
34
|
+
nodes: [{ id: "witness", speaker: "Witness 4", text: "The hatch stayed sealed." }],
|
|
35
|
+
},
|
|
36
|
+
claims: [{ nodeId: "witness", kind: "fact", factId: "hatch", value: "sealed" }],
|
|
37
|
+
}],
|
|
38
|
+
};
|
|
39
|
+
const issues = validateNarrativeBatch(canon, candidate, {
|
|
40
|
+
maxDialogues: 1, maxNodes: 20, maxChoices: 40, maxClaims: 30, maxTextCharacters: 6000,
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
These numbers and content demonstrate caller choices. A procedural undersea
|
|
45
|
+
inquiry can hinge on preserving a witness's anonymity versus publishing evidence.
|
|
46
|
+
An absurd rooftop tea negotiation can hinge on saluting a pigeon's ceremonial
|
|
47
|
+
hat versus offering biscuits. Each game writes its own canon, voices, style and
|
|
48
|
+
consequences; neither slice supplies a reusable story preset.
|
|
49
|
+
|
|
50
|
+
The typed batch contains graphs plus explicit node-attached `character`, `fact`
|
|
51
|
+
and `before` claims. `validateNarrativeBatch` checks those claims and reuses
|
|
52
|
+
`validateDialogueGraph` for topology. Diagnostics include a JSON-pointer path,
|
|
53
|
+
for example `/dialogues/0/claims/1/value` or
|
|
54
|
+
`/dialogues/0/graph/nodes/2/choices/0/to`. Speaker labels and prose remain display
|
|
55
|
+
data. Omitted or misleading claims cannot prove prose is coherent.
|
|
56
|
+
|
|
57
|
+
The CLI function snapshots canon, limits, brief and callback selection before
|
|
58
|
+
awaiting generation. It checks revision and budgets before copying a returned
|
|
59
|
+
candidate; oversized and stale candidates are discarded with diagnostics.
|
|
60
|
+
Within-budget drafts and repair diagnostics are detached and frozen across
|
|
61
|
+
callbacks. Fix the located candidate, preserving the reviewed canon. Attempts
|
|
62
|
+
stop at the caller's positive `maxAttempts`; exhausting it returns `needs-review`.
|
|
63
|
+
|
|
64
|
+
Generators return typed `NarrativeBatch` data. Decode external JSON before this
|
|
65
|
+
boundary and enforce response byte/token budgets in the generator adapter; the
|
|
66
|
+
SDK's node/choice/claim/text limits are not a transport-size limiter. Callback
|
|
67
|
+
failures propagate. A pending reviewer stays pending, and rejection never implies
|
|
68
|
+
approval. A reviewer must return `approved: true` and no error diagnostics for
|
|
69
|
+
acceptance. Review topology warnings explicitly against intended play.
|
|
70
|
+
|
|
71
|
+
Semantic review compares the actual prose with canon and the chapter brief,
|
|
72
|
+
including unannotated statements, character motivations, causality, chronology,
|
|
73
|
+
lasting consequences and meaningful playable choices. Return located issues to
|
|
74
|
+
the next repair attempt. Play representative branches before promoting accepted
|
|
75
|
+
drafts to the game's authored catalog. Acceptance stages data; this function
|
|
76
|
+
never publishes content or replaces canon.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Save generated items in bounded pages
|
|
2
|
+
|
|
3
|
+
Use this recipe when an existing project owns generated item storage and wants
|
|
4
|
+
runtime ids and incremental save export without mirroring its items into another
|
|
5
|
+
registry. The interface is synchronous; it does not provide a database adapter.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import {
|
|
9
|
+
createItemInstanceRegistry,
|
|
10
|
+
type ItemInstanceStorage,
|
|
11
|
+
type ItemInstanceRegistryState,
|
|
12
|
+
} from "@jgengine/core/item/itemInstanceRegistry";
|
|
13
|
+
|
|
14
|
+
type RolledItem = { damage: number; affixes: string[] };
|
|
15
|
+
|
|
16
|
+
let existingItems = new Map<string, RolledItem>();
|
|
17
|
+
const storage: ItemInstanceStorage<RolledItem> = {
|
|
18
|
+
get: (id) => existingItems.get(id),
|
|
19
|
+
has: (id) => existingItems.has(id),
|
|
20
|
+
set: (id, definition) => void existingItems.set(id, definition),
|
|
21
|
+
delete: (id) => void existingItems.delete(id),
|
|
22
|
+
count: () => existingItems.size,
|
|
23
|
+
entries: () => existingItems.entries(),
|
|
24
|
+
replace(entries) {
|
|
25
|
+
const staged = new Map(entries);
|
|
26
|
+
existingItems = staged;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const registry = createItemInstanceRegistry("rolled", { storage });
|
|
31
|
+
const runtimeId = registry.register("blade", { damage: 17, affixes: ["keen"] });
|
|
32
|
+
const item = registry.get(runtimeId);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The example adapter stages replacement in another Map. A larger caller-owned
|
|
36
|
+
store can stage in its own transaction or generation and switch on success.
|
|
37
|
+
`replace` must consume the entire iterable synchronously and preserve old data
|
|
38
|
+
when iteration, cloning, validation, or commit fails. Do not clear live data and
|
|
39
|
+
then populate it. Stream `entries()` in stable order; materializing all entries
|
|
40
|
+
inside the adapter defeats bounded export.
|
|
41
|
+
|
|
42
|
+
## Export and restore
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
function saveItems(writePage: (json: string) => void): void {
|
|
46
|
+
for (const page of registry.statePages(256)) {
|
|
47
|
+
writePage(JSON.stringify(page));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function restoreItems(readPages: Iterable<string>): boolean {
|
|
52
|
+
function* decodedPages(): IterableIterator<ItemInstanceRegistryState<RolledItem>> {
|
|
53
|
+
for (const json of readPages) {
|
|
54
|
+
yield JSON.parse(json) as ItemInstanceRegistryState<RolledItem>;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return registry.restorePages(decodedPages());
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Each page retains the existing `{ prefix, sequence, entries }` save shape.
|
|
62
|
+
An empty registry exports one empty page so released ids still retain their
|
|
63
|
+
allocation sequence. Every restored page must have the same prefix and sequence.
|
|
64
|
+
Each page's definitions are detached independently; cross-page object identity
|
|
65
|
+
is not preserved. Keep definitions JSON-compatible for JSON saves.
|
|
66
|
+
|
|
67
|
+
`statePages` captures the allocator when called, reads at most `pageSize` entries
|
|
68
|
+
per step, and clones only that batch. Registry mutations invalidate the export
|
|
69
|
+
and the next step throws. Abandoning an iterator never blocks writes; call its
|
|
70
|
+
`return()` when stopping early to release adapter resources. Keep direct writes
|
|
71
|
+
to injected storage and definition objects quiescent during export: the registry
|
|
72
|
+
cannot detect them. The caller owns immutable capture or staging if saves must
|
|
73
|
+
overlap live changes. Publish a set of pages only after the complete export
|
|
74
|
+
succeeds; discard partial saves on failure.
|
|
75
|
+
|
|
76
|
+
`restorePages` returns false for an empty iterable or an invalid first header.
|
|
77
|
+
Later inconsistent headers, clone failures, and storage failures throw. A
|
|
78
|
+
conforming adapter keeps both the old collection and allocator intact on failure.
|
|
79
|
+
Restore consumes and clones one input page at a time; storage staging cost belongs
|
|
80
|
+
to the adapter. Existing `state()` and `restore(state)` remain whole-collection
|
|
81
|
+
operations and retain their original save shape.
|
|
82
|
+
|
|
83
|
+
## Reattach existing storage
|
|
84
|
+
|
|
85
|
+
If the caller restores storage separately, persist the page's allocator header
|
|
86
|
+
alongside it and attach with its original prefix and sequence:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
function reattach(savedHeader: { prefix: string; sequence: number }) {
|
|
90
|
+
return createItemInstanceRegistry(savedHeader.prefix, {
|
|
91
|
+
storage,
|
|
92
|
+
sequence: savedHeader.sequence,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Never derive sequence from count: released ids must remain consumed. Allocation
|
|
98
|
+
checks collisions against attached storage, but saving the real sequence avoids
|
|
99
|
+
searching through old ids. Allocation stops at `Number.MAX_SAFE_INTEGER` instead
|
|
100
|
+
of reusing ids through numeric rounding. The default registry remains in memory;
|
|
101
|
+
injected storage owns retention, indexing, transaction policy, and persistence.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Validate generated quest batches against reviewed facts
|
|
2
|
+
|
|
3
|
+
Run `npx jgengine recipe quest-authoring` for the SDK-typechecked composition.
|
|
4
|
+
Supply your reviewed canonical facts, established quests, catalog ids, external
|
|
5
|
+
unlock sources, and authoring limits. Inject your generator as the async callback;
|
|
6
|
+
the recipe does not choose a model, story, genre, or assets.
|
|
7
|
+
|
|
8
|
+
The generator receives detached, deeply frozen snapshots of the facts, established
|
|
9
|
+
definitions, and limits for each bounded batch. Mutating caller inputs while the
|
|
10
|
+
generator awaits cannot change the reviewed revision or reference lookups. It returns only new quest definitions. Decode untrusted JSON
|
|
11
|
+
into `QuestDef` before returning it; this validator checks authored structure, not
|
|
12
|
+
arbitrary JSON shape. The recipe rejects oversized quest/objective/reference
|
|
13
|
+
batches before structural validation. Keep the existing catalog bounded too;
|
|
14
|
+
partition larger worlds into reviewed dependency-complete catalogs.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { validateQuestCatalog } from "@jgengine/core/game/questCatalog";
|
|
18
|
+
import type { QuestDef } from "@jgengine/core/game/quest";
|
|
19
|
+
|
|
20
|
+
function reviewBatch(existing: readonly QuestDef[], generated: readonly QuestDef[]) {
|
|
21
|
+
return validateQuestCatalog([...existing, ...generated], {
|
|
22
|
+
externalUnlocks: ["caller-declared-world-entry"],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The CLI recipe returns `factsRevision`, `candidate`, `diagnostics`, and `repair`.
|
|
28
|
+
`candidate` is null when structural errors exist. Each issue includes a JSON
|
|
29
|
+
Pointer into the combined catalog, such as `/4/rewards/quests/0`, plus a stable
|
|
30
|
+
code, severity, and referenced id when relevant. Send `repair` with the same
|
|
31
|
+
canonical facts and definitions to your generator for a bounded correction;
|
|
32
|
+
correct the identified fields and validate the whole candidate again. Never
|
|
33
|
+
replace reviewed facts with a model's remembered version. The recipe leaves all
|
|
34
|
+
warnings visible for review before registering the candidate with the journal.
|
|
35
|
+
|
|
36
|
+
Declare unlock ids that other game systems may grant in `externalUnlocks`.
|
|
37
|
+
Unlocks produced by catalog quest rewards are recognized automatically.
|
|
38
|
+
`externallyStartedQuests` declares catalog quests your game may grant active,
|
|
39
|
+
bypassing acceptance prerequisites. These declarations describe possible entry
|
|
40
|
+
points, without asserting when they happen. A requirement may be fulfilled by
|
|
41
|
+
completion of that quest id **or** an unlock with the same id; every requirement
|
|
42
|
+
must be met. Any eligible quest may be accepted directly, so a quest does not
|
|
43
|
+
need an incoming reward follow-up to be reachable.
|
|
44
|
+
|
|
45
|
+
Blocked prerequisites and cycles are warnings under this potential-progression
|
|
46
|
+
model. Objectives are assumed completable; external grants, custom acceptance
|
|
47
|
+
rules, or unavailable objectives can change actual play. Unknown requirement and
|
|
48
|
+
follow-up ids are errors: declare legitimate external unlock ids rather than
|
|
49
|
+
silencing missing references. Use `hasReference` for item, target, inventory, and
|
|
50
|
+
currency namespaces. Its objective context lets a game route custom objective
|
|
51
|
+
kinds to different catalogs. Omitted lookup policy leaves those ids unchecked.
|
|
52
|
+
|
|
53
|
+
Objective and item counts may be fractional or zero; zero can mean an already
|
|
54
|
+
complete objective or a no-op reward. Negative/nonfinite counts and nonfinite
|
|
55
|
+
reward amounts reject. Discrete quantity rules, XP loss, economy balance, story
|
|
56
|
+
contradictions, character motivation, chronology, and consequences remain
|
|
57
|
+
caller-owned review. Play representative paths before treating a valid catalog
|
|
58
|
+
as a coherent world.
|
|
59
|
+
|
|
60
|
+
For item supply and skill gates outside the quest journal, compose the
|
|
61
|
+
[content validation](../reference-content-validation.md) checks. Declare source
|
|
62
|
+
rules explicitly rather than assuming every defined item is obtainable or that
|
|
63
|
+
catalog order proves an item reward can precede a collection objective.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Place shaped inventory items
|
|
2
|
+
|
|
3
|
+
Use `@jgengine/core/inventory/shapedGrid` for discrete cell footprints, including
|
|
4
|
+
rectangles and irregular shapes. Item definitions, values, authored dimensions,
|
|
5
|
+
save schemas, drag controls, and presentation stay in the game.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import {
|
|
9
|
+
createShapedGrid,
|
|
10
|
+
findShapedPlacement,
|
|
11
|
+
moveShaped,
|
|
12
|
+
placeShaped,
|
|
13
|
+
type ShapedItem,
|
|
14
|
+
} from "@jgengine/core/inventory/shapedGrid";
|
|
15
|
+
|
|
16
|
+
const item: ShapedItem<{ name: string }> = {
|
|
17
|
+
id: "probe-7",
|
|
18
|
+
value: { name: "Survey probe" },
|
|
19
|
+
footprint: [[0, 0], [0, 1], [1, 1]],
|
|
20
|
+
};
|
|
21
|
+
const board = createShapedGrid<{ name: string }>(4, 3);
|
|
22
|
+
const fit = findShapedPlacement(board, item.footprint, {
|
|
23
|
+
rotations: [0, 1],
|
|
24
|
+
maxChecks: 24,
|
|
25
|
+
});
|
|
26
|
+
if (fit.status === "found") {
|
|
27
|
+
const placed = placeShaped(board, item, fit.origin, fit.rotation);
|
|
28
|
+
if (placed.status === "ok") moveShaped(placed.grid, item.id, [1, 0], 1);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Rotations are quarter turns. Search visits the supplied rotation order, then
|
|
33
|
+
rows and columns; the default rotation order is `[0, 1, 2, 3]`. Coordinates and
|
|
34
|
+
dimensions must be finite safe integers; dimensions are positive and footprints
|
|
35
|
+
are nonempty. Footprints normalize their minimum cell to `[0, 0]` after rotation.
|
|
36
|
+
`canPlace` reports invalid cells before bounds and overlap checks. Pass `ignoreId`
|
|
37
|
+
to either check or search when repositioning an existing item.
|
|
38
|
+
|
|
39
|
+
Search indexes current occupancy once and limits candidate origins to
|
|
40
|
+
`maxChecks` (default 4096). `budget-exceeded` means the search is incomplete;
|
|
41
|
+
`no-space` means every requested origin and rotation was checked. Do not report
|
|
42
|
+
a full bag for an exhausted budget. Indexing and footprint checks scale with
|
|
43
|
+
the caller's board data; the budget does not limit their cell count.
|
|
44
|
+
|
|
45
|
+
Placement operations return new board data. Serialize that data in the caller's
|
|
46
|
+
save format; the engine does not keep a parallel bag or impose item nouns.
|
|
47
|
+
Deepward's prepared adopter maps its existing rectangular item sizes and saved
|
|
48
|
+
`x`, `y`, `rotated` fields into this contract, preserving its `[0, 1]` rotation
|
|
49
|
+
preference and row-major packing. Its UI and extraction rules remain local.
|
|
50
|
+
The prepared patch waits for a coordinator-verified containing package release.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Validate references and declared progression
|
|
2
|
+
|
|
3
|
+
Run these pure validators during authoring, build or CI, never per frame. Decode
|
|
4
|
+
external JSON into typed definitions first. Preserve authored array order and
|
|
5
|
+
stable ids so repeated validation returns identical located diagnostics. Each
|
|
6
|
+
`ContentIssue` carries `contentId`, a caller document `path`, and a `repair` action.
|
|
7
|
+
For a bounded generated batch, validate its dependency-complete reviewed catalog,
|
|
8
|
+
repair the located fields, then validate again before registration.
|
|
9
|
+
|
|
10
|
+
`validateContentReferences` (`@jgengine/core/game/contentValidation`) indexes
|
|
11
|
+
`{ kind, id, path }` definitions once and checks located
|
|
12
|
+
`{ kind, id, path, contentId }` references. A namespace can describe a game-owned
|
|
13
|
+
role: referencing a `questgiver` checks the role rather than merely the existence
|
|
14
|
+
of an NPC. Do not derive roles from names or parse dialogue prose. Validate explicit
|
|
15
|
+
dialogue command arguments against the appropriate namespace.
|
|
16
|
+
For relationships stricter than existence, set a reference's `allowedIds` from
|
|
17
|
+
the game's declared rule, such as this giver's quests or this record's key.
|
|
18
|
+
An existing but disallowed id reports `inconsistent-reference`; omitted policy
|
|
19
|
+
does not infer restrictions from names, prose or ordering.
|
|
20
|
+
|
|
21
|
+
`validateRecipeCatalog` (`@jgengine/core/crafting/recipeCatalog`) checks native
|
|
22
|
+
`RecipeDef` rows and optional item, station and unlock lookups. Build lookup Sets
|
|
23
|
+
once in the adapter. Empty outputs are valid for benefits applied by game code.
|
|
24
|
+
Zero and fractional counts are valid; enforce discrete units in game policy.
|
|
25
|
+
Duplicate input rows reject because crafting checks each row separately; merge
|
|
26
|
+
their counts. Prices, rarity, ingredient scarcity and recipe cycles are game-owned.
|
|
27
|
+
Compose existing `validateQuestCatalog` and `validateDialogueGraph` for their native
|
|
28
|
+
structures; do not replace the quest journal or conversation model.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { validateContentProgression } from "@jgengine/core/game/contentValidation";
|
|
32
|
+
|
|
33
|
+
const result = validateContentProgression({
|
|
34
|
+
initial: ["material:wire", "material:seals", "place:home"],
|
|
35
|
+
rules: [{
|
|
36
|
+
id: "sealed-tank",
|
|
37
|
+
path: "/refits/tank",
|
|
38
|
+
requires: ["material:wire", "material:seals", "place:home"],
|
|
39
|
+
provides: ["benefit:sealed-tank"],
|
|
40
|
+
}],
|
|
41
|
+
required: [{ id: "benefit:sealed-tank", contentId: "tank", path: "/refits/tank" }],
|
|
42
|
+
closedWorld: true,
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Facts are opaque game-owned strings. All facts in one rule are necessary;
|
|
47
|
+
alternative sources are separate rules. An initial fact or a reachable source
|
|
48
|
+
can seed an intentional cycle. Missing seeds block cycles; cycles themselves
|
|
49
|
+
are never forbidden. `closedWorld: true` asserts all possible sources have been
|
|
50
|
+
declared and makes blocked rules/required facts errors. The default leaves them
|
|
51
|
+
warnings. Legitimate external suppliers belong in `initial` or a source rule,
|
|
52
|
+
not a blanket declaration of every catalog item as available.
|
|
53
|
+
|
|
54
|
+
Translate skill gates into milestone facts in the game adapter. A repeatable
|
|
55
|
+
action requiring skill 0 and training to 50 can provide milestones through 50;
|
|
56
|
+
it cannot provide the next source's skill-100 fact. Derive milestones from actual
|
|
57
|
+
gates and training caps rather than inventing thousands of skill nodes. Bound the
|
|
58
|
+
analysis to the reviewed content and dependency edges. The engine visits each
|
|
59
|
+
rule and dependency edge with an iterative queue rather than rescanning a catalog
|
|
60
|
+
until convergence.
|
|
61
|
+
|
|
62
|
+
This proves potential availability only. It does not prove affordable quantities,
|
|
63
|
+
repeatability, inventory capacity, consumed prerequisites, exclusive choices,
|
|
64
|
+
chronology, station proximity, story coherence or obtainable rewards in actual play.
|
|
65
|
+
Declare distinct route/branch facts when combining branches would be misleading,
|
|
66
|
+
and drive representative choices through the actual commands with save/reload.
|
|
67
|
+
Keep story facts, terms, assets and economy decisions in the game. Remove older
|
|
68
|
+
generic reference/topology algorithms when adopting these checks; retain custom
|
|
69
|
+
semantic and durable-save policy validators.
|
|
70
|
+
|
|
71
|
+
Verify built tarball imports and the actual adopter in an isolated scratch copy.
|
|
72
|
+
Keep the candidate on a prepared branch until a verified published package contains
|
|
73
|
+
these APIs, then adopt through the games workspace catalog and lockfile together.
|
|
74
|
+
Never route game imports to engine source or add per-game version pins.
|