@jgengine/core 0.18.1 → 0.21.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 +504 -7
- package/dist/ai/decisionGraph.d.ts +50 -4
- package/dist/ai/decisionGraph.js +159 -23
- package/dist/ai/flock.d.ts +1 -1
- package/dist/ai/flock.js +7 -4
- package/dist/ai/groupAssist.d.ts +10 -0
- package/dist/ai/groupAssist.js +11 -0
- package/dist/ai/pursuit.d.ts +1 -1
- package/dist/ai/pursuit.js +1 -1
- package/dist/ai/threat.d.ts +8 -0
- package/dist/ai/threat.js +10 -0
- package/dist/anim/animGraph.d.ts +43 -1
- package/dist/anim/animGraph.js +326 -10
- package/dist/anim/boneTexture.d.ts +55 -0
- package/dist/anim/boneTexture.js +43 -0
- package/dist/anim/footPlacement.d.ts +57 -0
- package/dist/anim/footPlacement.js +67 -0
- package/dist/anim/locomotionGraph.d.ts +18 -1
- package/dist/anim/locomotionGraph.js +42 -3
- package/dist/audio/audioFalloff.d.ts +6 -0
- package/dist/audio/doppler.d.ts +21 -0
- package/dist/audio/doppler.js +32 -0
- package/dist/audio/engineLayers.d.ts +83 -0
- package/dist/audio/engineLayers.js +115 -0
- package/dist/audio/voiceAllocator.d.ts +46 -0
- package/dist/audio/voiceAllocator.js +76 -0
- package/dist/combat/abilityKit.d.ts +20 -0
- package/dist/combat/abilityKit.js +41 -4
- package/dist/combat/death.d.ts +11 -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/combat/shotOrigin.d.ts +6 -0
- package/dist/combat/shotOrigin.js +6 -0
- package/dist/combat/weaponFeedback.d.ts +36 -0
- package/dist/combat/weaponFeedback.js +42 -0
- package/dist/combat/weaponHandling.d.ts +161 -0
- package/dist/combat/weaponHandling.js +202 -0
- package/dist/combat/weaponPresentation.d.ts +115 -0
- package/dist/combat/weaponPresentation.js +96 -0
- package/dist/crafting/recipeCatalog.d.ts +16 -0
- package/dist/crafting/recipeCatalog.js +55 -0
- package/dist/devtools/warnOnce.d.ts +8 -0
- package/dist/devtools/warnOnce.js +18 -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 +119 -18
- package/dist/editor/environment.js +8 -0
- package/dist/editor/index.d.ts +2 -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/types.d.ts +15 -0
- package/dist/faction/reputation.d.ts +6 -0
- package/dist/faction/reputation.js +11 -0
- package/dist/game/cameraConfig.d.ts +54 -2
- 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 +11 -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/lootTable.d.ts +4 -0
- package/dist/game/lootTable.js +16 -0
- package/dist/game/narrativeAuthoring.d.ts +67 -0
- package/dist/game/narrativeAuthoring.js +122 -0
- package/dist/game/playableGame.d.ts +134 -35
- 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/game/toasts.d.ts +8 -0
- package/dist/game/toasts.js +7 -0
- package/dist/game/vfxInstance.d.ts +9 -0
- package/dist/game/vfxInstance.js +12 -1
- package/dist/game/viewports.d.ts +43 -0
- package/dist/game/viewports.js +46 -0
- package/dist/game/worldItem.d.ts +5 -1
- package/dist/game/worldItem.js +0 -1
- package/dist/gameplay.d.ts +3 -0
- package/dist/gameplay.js +3 -0
- package/dist/input/actionBindings.d.ts +15 -1
- package/dist/input/actionBindings.js +14 -3
- package/dist/input/actionContexts.d.ts +25 -1
- package/dist/input/actionContexts.js +82 -25
- package/dist/input/axisInput.d.ts +4 -0
- package/dist/input/axisInput.js +4 -0
- package/dist/input/axisShaper.d.ts +73 -0
- package/dist/input/axisShaper.js +105 -0
- package/dist/input/gamepadModel.d.ts +36 -2
- package/dist/input/gamepadModel.js +69 -30
- package/dist/input/haptics.d.ts +50 -0
- package/dist/input/haptics.js +74 -0
- package/dist/input/inputBuffer.d.ts +40 -0
- package/dist/input/inputBuffer.js +59 -0
- package/dist/input/lookChannel.d.ts +10 -0
- package/dist/input/lookChannel.js +6 -0
- package/dist/input/pointer.d.ts +2 -0
- package/dist/input/pointer.js +4 -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/affix.d.ts +6 -0
- package/dist/item/affix.js +6 -0
- 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 +444 -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/glideModel.d.ts +3 -0
- package/dist/movement/glideModel.js +6 -0
- package/dist/movement/grappleSwing.d.ts +2 -0
- package/dist/movement/grappleSwing.js +5 -1
- package/dist/movement/movementModel.d.ts +63 -1
- package/dist/movement/movementModel.js +136 -26
- package/dist/movement/movementProbe.d.ts +51 -0
- package/dist/movement/movementProbe.js +119 -0
- package/dist/movement/playerMovement.d.ts +48 -2
- package/dist/movement/playerMovement.js +493 -87
- package/dist/movement/poseState.d.ts +2 -0
- package/dist/movement/poseState.js +29 -1
- package/dist/movement/solidObstacles.d.ts +11 -1
- package/dist/movement/solidObstacles.js +48 -3
- 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 +10 -10
- package/dist/nav/navFromEnvironment.d.ts +9 -3
- package/dist/nav/navFromEnvironment.js +18 -12
- package/dist/nav/navGrid.d.ts +19 -1
- package/dist/nav/navGrid.js +26 -10
- package/dist/nav/navMesh.d.ts +43 -2
- package/dist/nav/navMesh.js +534 -128
- package/dist/nav/solidRoute.d.ts +50 -0
- package/dist/nav/solidRoute.js +144 -0
- package/dist/physics/aircraftDynamics.d.ts +390 -0
- package/dist/physics/aircraftDynamics.js +704 -0
- package/dist/physics/ballisticSweep.d.ts +10 -3
- package/dist/physics/ballisticSweep.js +53 -29
- package/dist/physics/boatDynamics.d.ts +116 -0
- package/dist/physics/boatDynamics.js +160 -0
- package/dist/physics/drivableVehicle.d.ts +22 -8
- package/dist/physics/drivableVehicle.js +4 -2
- package/dist/physics/flightDynamics.d.ts +21 -4
- package/dist/physics/flightDynamics.js +28 -5
- package/dist/physics/forceVolume.d.ts +41 -0
- package/dist/physics/forceVolume.js +88 -0
- package/dist/physics/handlingProbe.d.ts +249 -0
- package/dist/physics/handlingProbe.js +606 -0
- package/dist/physics/kinematicVehicle.d.ts +40 -5
- package/dist/physics/kinematicVehicle.js +17 -2
- package/dist/physics/physicsBackend.d.ts +12 -0
- package/dist/physics/physicsBackendConformance.js +34 -0
- package/dist/physics/physicsWorldBackend.d.ts +1 -0
- package/dist/physics/physicsWorldBackend.js +52 -3
- package/dist/physics/spatialGrid.d.ts +1 -1
- package/dist/physics/spatialGrid.js +8 -9
- package/dist/physics/vehicleBackendLink.d.ts +49 -0
- package/dist/physics/vehicleBackendLink.js +102 -0
- package/dist/physics/vehicleDynamics.d.ts +375 -0
- package/dist/physics/vehicleDynamics.js +737 -0
- package/dist/physics/vehicleObstacles.d.ts +3 -2
- package/dist/physics/vehicleObstacles.js +29 -5
- package/dist/physics/worldColliders.d.ts +1 -1
- package/dist/physics/worldColliders.js +35 -3
- package/dist/puzzle/cellGrid.d.ts +15 -9
- package/dist/puzzle/cellGrid.js +13 -9
- package/dist/render/lookPreset.d.ts +9 -2
- package/dist/render/lookPreset.js +29 -2
- package/dist/render/postProcessing.d.ts +30 -0
- package/dist/render/postProcessing.js +1 -1
- package/dist/runtime/cameraDirector.d.ts +54 -3
- package/dist/runtime/cameraDirector.js +187 -0
- package/dist/runtime/commandDispatch.d.ts +14 -0
- package/dist/runtime/commandDispatch.js +19 -0
- package/dist/runtime/commandInput.d.ts +37 -0
- package/dist/runtime/commandInput.js +58 -0
- package/dist/runtime/commandRunner.d.ts +27 -0
- package/dist/runtime/commandRunner.js +31 -3
- package/dist/runtime/context/combat.d.ts +12 -4
- package/dist/runtime/context/combat.js +179 -11
- package/dist/runtime/context/deathLoot.d.ts +11 -8
- package/dist/runtime/context/deathLoot.js +17 -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 +86 -13
- package/dist/runtime/gameContextTypes.d.ts +33 -3
- package/dist/runtime/gameRuntime.d.ts +3 -1
- package/dist/runtime/gameRuntime.js +4 -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 +22 -4
- package/dist/runtime/inputSnapshot.js +41 -3
- package/dist/runtime/localPlayers.d.ts +73 -0
- package/dist/runtime/localPlayers.js +119 -0
- 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/transport.d.ts +9 -0
- package/dist/runtime/transport.js +7 -1
- 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 +38 -7
- package/dist/scene/behaviorRuntime.js +293 -30
- package/dist/scene/behaviors.d.ts +35 -2
- 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/sceneKinds.d.ts +9 -0
- 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 +41 -3
- package/dist/scene/vehicleSeat.js +94 -3
- package/dist/sensor/concealment.d.ts +6 -0
- package/dist/sensor/concealment.js +8 -0
- package/dist/sensor/freezeMonitor.d.ts +6 -0
- package/dist/sensor/freezeMonitor.js +8 -0
- package/dist/sensor/perception.d.ts +11 -1
- package/dist/sensor/perception.js +57 -10
- package/dist/sensor/recordingBuffer.d.ts +3 -0
- package/dist/sensor/recordingBuffer.js +8 -0
- package/dist/session/roundState.d.ts +4 -0
- package/dist/session/roundState.js +11 -0
- package/dist/settings/frameRateLimit.d.ts +16 -0
- package/dist/settings/frameRateLimit.js +37 -0
- package/dist/settings/graphicsProfile.d.ts +2 -0
- package/dist/settings/graphicsProfile.js +12 -0
- package/dist/settings/graphicsSettings.d.ts +40 -0
- package/dist/settings/graphicsSettings.js +81 -0
- package/dist/settings/settingsModel.d.ts +9 -1
- package/dist/settings/settingsModel.js +10 -0
- package/dist/stats/accumulatorMeter.d.ts +10 -0
- package/dist/stats/accumulatorMeter.js +6 -0
- package/dist/stats/eventMeter.d.ts +10 -3
- package/dist/stats/eventMeter.js +6 -3
- 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/feedbackMixer.d.ts +61 -0
- package/dist/vfx/feedbackMixer.js +106 -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/simulationCulling.d.ts +8 -1
- package/dist/visibility/simulationCulling.js +7 -0
- package/dist/visibility/spatialIndex.d.ts +21 -0
- package/dist/visibility/spatialIndex.js +16 -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/authoredSolids.d.ts +46 -0
- package/dist/world/authoredSolids.js +75 -0
- package/dist/world/authoredWeather.d.ts +120 -0
- package/dist/world/authoredWeather.js +314 -0
- package/dist/world/buildingGenerator.js +2 -2
- package/dist/world/buildingKit.d.ts +11 -4
- package/dist/world/buildingKit.js +21 -8
- package/dist/world/buildings.d.ts +34 -2
- package/dist/world/buildings.js +11 -1
- package/dist/world/cityKind.d.ts +7 -0
- package/dist/world/cityKind.js +52 -1
- package/dist/world/environmentForces.d.ts +21 -0
- package/dist/world/environmentForces.js +42 -0
- package/dist/world/features.d.ts +32 -5
- package/dist/world/features.js +6 -0
- package/dist/world/footprintGrid.d.ts +62 -0
- package/dist/world/footprintGrid.js +217 -0
- package/dist/world/interiors.d.ts +1 -0
- package/dist/world/interiors.js +5 -3
- package/dist/world/lod.d.ts +6 -0
- package/dist/world/lod.js +8 -0
- package/dist/world/pathTerrain.d.ts +1 -1
- package/dist/world/terraform.d.ts +9 -1
- package/dist/world/terraform.js +53 -18
- package/dist/world/walls.d.ts +7 -0
- package/dist/world/walls.js +7 -0
- package/dist/world/weather.d.ts +17 -0
- package/dist/world/weather.js +116 -20
- package/dist/world/worldSolids.d.ts +95 -0
- package/dist/world/worldSolids.js +351 -0
- package/dist/world.d.ts +6 -6
- package/dist/world.js +5 -5
- package/package.json +1 -1
- package/skills/ce-handoff/SKILL.md +2 -2
- package/skills/game-design/SKILL.md +2 -2
- package/skills/jgengine/SKILL.md +28 -6
- package/skills/jgengine/api.md +139 -99
- package/skills/jgengine/capabilities.md +28 -0
- package/skills/jgengine/recipes/minimal-game.md +10 -2
- package/skills/jgengine-combat/SKILL.md +13 -2
- package/skills/jgengine-combat/api.md +150 -103
- package/skills/jgengine-combat/capabilities.md +29 -1
- 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/recipes/weapon-feel.md +50 -0
- package/skills/jgengine-combat/reference.md +24 -2
- package/skills/jgengine-gameplay/SKILL.md +63 -0
- package/skills/jgengine-gameplay/api.md +583 -463
- package/skills/jgengine-gameplay/capabilities.md +92 -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 +123 -0
- package/skills/jgengine-world/SKILL.md +25 -4
- package/skills/jgengine-world/api.md +1045 -655
- package/skills/jgengine-world/capabilities.md +246 -9
- package/skills/jgengine-world/recipes/character-animation.md +62 -0
- package/skills/jgengine-world/recipes/controls.md +48 -0
- package/skills/jgengine-world/recipes/flight-feel.md +76 -0
- package/skills/jgengine-world/recipes/npc-senses.md +55 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +87 -0
- package/skills/jgengine-world/reference.md +180 -4
- package/skills/jgengine-world/visibility.md +139 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { appendFeed } from "../game/feed.js";
|
|
2
|
+
import { hashString, randomSeedFrom, stepRandomSeed } from "../random/rng.js";
|
|
3
|
+
import { accrueSince } from "../time/accrueSince.js";
|
|
4
|
+
import { steppedCatchUp } from "../time/idleProgress.js";
|
|
5
|
+
function nonnegative(value, name) {
|
|
6
|
+
if (!Number.isFinite(value) || value < 0)
|
|
7
|
+
throw new RangeError(`${name} must be finite and nonnegative`);
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
function positiveInteger(value, name) {
|
|
11
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
12
|
+
throw new RangeError(`${name} must be a positive safe integer`);
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function validate(config, state, nowMs) {
|
|
16
|
+
if (!Number.isFinite(nowMs) || !Number.isFinite(state.startedAtMs) || !Number.isFinite(state.lastSettledMs))
|
|
17
|
+
throw new RangeError("Expedition timestamps must be finite");
|
|
18
|
+
if (state.lastSettledMs < state.startedAtMs || (state.returnUntilMs !== null && !Number.isFinite(state.returnUntilMs)))
|
|
19
|
+
throw new RangeError("Invalid expedition time frontier");
|
|
20
|
+
if (!Number.isSafeInteger(state.cursor))
|
|
21
|
+
throw new RangeError("Expedition RNG cursor must be a safe integer");
|
|
22
|
+
positiveInteger(config.tickMs, "tickMs");
|
|
23
|
+
positiveInteger(config.carryCap, "carryCap");
|
|
24
|
+
positiveInteger(config.maxTicksPerSettle ?? 10_000, "maxTicksPerSettle");
|
|
25
|
+
positiveInteger(config.logLimit ?? 200, "logLimit");
|
|
26
|
+
nonnegative(config.returnFraction, "returnFraction");
|
|
27
|
+
nonnegative(state.hp, "hp");
|
|
28
|
+
nonnegative(state.exposure, "exposure");
|
|
29
|
+
nonnegative(state.elapsedMs, "elapsedMs");
|
|
30
|
+
for (const count of Object.values(state.supplies))
|
|
31
|
+
if (!Number.isSafeInteger(count) || count < 0)
|
|
32
|
+
throw new RangeError("Supply counts must be nonnegative safe integers");
|
|
33
|
+
}
|
|
34
|
+
function record(config, state, event, text) {
|
|
35
|
+
return { ...state, log: appendFeed(state.log, { ...event, text: text ?? config.describe?.(event, state) ?? "" }, { limit: config.logLimit ?? 200 }) };
|
|
36
|
+
}
|
|
37
|
+
function itemUnits(drops) {
|
|
38
|
+
let units = 0;
|
|
39
|
+
for (const drop of drops)
|
|
40
|
+
if (drop.item !== undefined)
|
|
41
|
+
units += nonnegative(drop.count, "Item count");
|
|
42
|
+
return nonnegative(units, "Carried item units");
|
|
43
|
+
}
|
|
44
|
+
function acceptLoot(cap, carried, drops) {
|
|
45
|
+
let remaining = Math.max(0, cap - itemUnits(carried));
|
|
46
|
+
const next = carried.map(drop => ({ ...drop }));
|
|
47
|
+
const accepted = [];
|
|
48
|
+
for (const drop of drops) {
|
|
49
|
+
const count = nonnegative(drop.count, "Drop count");
|
|
50
|
+
if ((drop.item === undefined) === (drop.currency === undefined))
|
|
51
|
+
throw new RangeError("Expedition drops need exactly one item or currency id");
|
|
52
|
+
if (drop.item !== undefined && !Number.isSafeInteger(count))
|
|
53
|
+
throw new RangeError("Item counts must be nonnegative safe integers");
|
|
54
|
+
const admitted = drop.item === undefined ? count : Math.min(count, remaining);
|
|
55
|
+
if (drop.item !== undefined)
|
|
56
|
+
remaining -= admitted;
|
|
57
|
+
if (admitted === 0)
|
|
58
|
+
continue;
|
|
59
|
+
const incoming = { ...drop, count: admitted };
|
|
60
|
+
accepted.push(incoming);
|
|
61
|
+
const existing = next.find(entry => drop.item !== undefined ? entry.item === drop.item : entry.currency === drop.currency && entry.item === undefined);
|
|
62
|
+
if (existing === undefined)
|
|
63
|
+
next.push({ ...incoming });
|
|
64
|
+
else
|
|
65
|
+
existing.count = nonnegative(existing.count + admitted, "Carried count");
|
|
66
|
+
}
|
|
67
|
+
return { carried: next, accepted };
|
|
68
|
+
}
|
|
69
|
+
function arrive(config, state, nowMs) {
|
|
70
|
+
if (state.status !== "returning" || state.returnUntilMs === null || nowMs < state.returnUntilMs)
|
|
71
|
+
return state;
|
|
72
|
+
const at = state.returnUntilMs;
|
|
73
|
+
return record(config, { ...state, status: "home", lastSettledMs: at }, { at, kind: "return" });
|
|
74
|
+
}
|
|
75
|
+
function beginReturn(config, state, at) {
|
|
76
|
+
const elapsedMs = Math.max(state.elapsedMs, at - state.startedAtMs);
|
|
77
|
+
const returnUntilMs = at + elapsedMs * config.returnFraction;
|
|
78
|
+
if (!Number.isFinite(returnUntilMs))
|
|
79
|
+
throw new RangeError("Expedition return deadline overflowed");
|
|
80
|
+
return arrive(config, record(config, { ...state, status: "returning", elapsedMs, returnUntilMs, lastSettledMs: at }, { at, kind: "recall" }), at);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Dispatch plain expedition state with a persisted seeded cursor; no clock, scheduler or storage is owned.
|
|
84
|
+
*
|
|
85
|
+
* @capability expedition-dispatch dispatch a saveable timed away mission with finite supplies and a seeded RNG cursor
|
|
86
|
+
*/
|
|
87
|
+
export function dispatchExpedition(input) {
|
|
88
|
+
if (!Number.isFinite(input.startAtMs))
|
|
89
|
+
throw new RangeError("startAtMs must be finite");
|
|
90
|
+
nonnegative(input.hp, "hp");
|
|
91
|
+
nonnegative(input.exposure ?? 0, "exposure");
|
|
92
|
+
const supplies = { ...input.supplies };
|
|
93
|
+
for (const count of Object.values(supplies))
|
|
94
|
+
if (!Number.isSafeInteger(count) || count < 0)
|
|
95
|
+
throw new RangeError("Supply counts must be nonnegative safe integers");
|
|
96
|
+
return { id: input.id, status: input.hp === 0 ? "dead" : "exploring", startedAtMs: input.startAtMs, lastSettledMs: input.startAtMs,
|
|
97
|
+
elapsedMs: 0, returnUntilMs: null, hp: input.hp, exposure: input.exposure ?? 0, supplies, carried: [], log: [], seed: input.seed, cursor: randomSeedFrom(hashString(input.seed)) };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Replay due event ticks using the existing stepped catch-up primitive. Order is loot → hazard → ordered
|
|
101
|
+
* supplies → death → capacity recall. Unprocessed ticks retain their frontier; returning never rolls or
|
|
102
|
+
* takes damage. Auto-recall uses the triggering tick's time, including offline arrival in the same call.
|
|
103
|
+
*
|
|
104
|
+
* @capability expedition-settle settle bounded offline expedition ticks without losing supplies rewards or RNG continuity
|
|
105
|
+
*/
|
|
106
|
+
export function settleExpedition(config, state, nowMs) {
|
|
107
|
+
validate(config, state, nowMs);
|
|
108
|
+
if (nowMs < state.lastSettledMs || (state.status === "exploring" && nowMs < state.startedAtMs + state.elapsedMs) || state.status === "dead" || state.status === "home")
|
|
109
|
+
return state;
|
|
110
|
+
if (state.status === "returning")
|
|
111
|
+
return arrive(config, state, nowMs);
|
|
112
|
+
let next = state;
|
|
113
|
+
const step = config.randomStep ?? stepRandomSeed;
|
|
114
|
+
const window = accrueSince(state.lastSettledMs, nowMs);
|
|
115
|
+
nonnegative(window.elapsedMs, "Catch-up duration");
|
|
116
|
+
// Normalize to whole one-unit ticks so fractional-second division cannot lose a due millisecond.
|
|
117
|
+
const ticks = Math.floor(window.elapsedMs / config.tickMs);
|
|
118
|
+
steppedCatchUp(ticks, 1, () => {
|
|
119
|
+
if (next.status !== "exploring")
|
|
120
|
+
return;
|
|
121
|
+
const at = next.lastSettledMs + config.tickMs;
|
|
122
|
+
next = { ...next, lastSettledMs: at, elapsedMs: at - next.startedAtMs };
|
|
123
|
+
let cursor = next.cursor;
|
|
124
|
+
const rng = () => {
|
|
125
|
+
const [value, following] = step(cursor);
|
|
126
|
+
if (!Number.isFinite(value) || value < 0 || value >= 1 || !Number.isSafeInteger(following))
|
|
127
|
+
throw new RangeError("Expedition RNG must return a [0,1) value and safe integer cursor");
|
|
128
|
+
cursor = following;
|
|
129
|
+
return value;
|
|
130
|
+
};
|
|
131
|
+
const resolution = config.loot.resolve({ ctx: config.lootCtx(next), rng, seed: next.seed });
|
|
132
|
+
const accepted = acceptLoot(config.carryCap, next.carried, resolution.drops);
|
|
133
|
+
next = { ...next, carried: accepted.carried };
|
|
134
|
+
if (resolution.drops.length > 0)
|
|
135
|
+
next = record(config, next, { at, kind: "loot", drops: accepted.accepted, loot: resolution });
|
|
136
|
+
const hazard = config.hazard?.(next, rng) ?? {};
|
|
137
|
+
const exposure = nonnegative(next.exposure + nonnegative(hazard.exposure ?? 0, "Hazard exposure"), "Exposure");
|
|
138
|
+
const hp = Math.min(nonnegative(config.maxHp({ ...next, exposure }), "maxHp"), Math.max(0, next.hp - nonnegative(hazard.damage ?? 0, "Hazard damage")));
|
|
139
|
+
const hpDelta = hp - next.hp, exposureDelta = exposure - next.exposure;
|
|
140
|
+
next = { ...next, hp, exposure };
|
|
141
|
+
if (hpDelta !== 0 || exposureDelta !== 0)
|
|
142
|
+
next = record(config, next, { at, kind: "hazard", hpDelta, exposureDelta });
|
|
143
|
+
for (const notice of hazard.notices ?? [])
|
|
144
|
+
next = record(config, next, { at, kind: notice.kind }, notice.text);
|
|
145
|
+
for (const rule of config.supplies ?? []) {
|
|
146
|
+
const count = Object.hasOwn(next.supplies, rule.key) ? next.supplies[rule.key] : 0;
|
|
147
|
+
const restoreHp = nonnegative(rule.restoreHp ?? 0, "Supply restoration"), clearExposure = nonnegative(rule.clearExposure ?? 0, "Supply cleansing");
|
|
148
|
+
if (count <= 0 || !rule.when(next))
|
|
149
|
+
continue;
|
|
150
|
+
const exposure = Math.max(0, next.exposure - clearExposure);
|
|
151
|
+
const hp = Math.min(nonnegative(config.maxHp({ ...next, exposure }), "maxHp"), nonnegative(next.hp + restoreHp, "Restored hp"));
|
|
152
|
+
if (hp === next.hp && exposure === next.exposure)
|
|
153
|
+
continue;
|
|
154
|
+
const hpDelta = hp - next.hp, exposureDelta = exposure - next.exposure;
|
|
155
|
+
next = { ...next, hp, exposure, supplies: { ...next.supplies, [rule.key]: count - 1 } };
|
|
156
|
+
next = record(config, next, { at, kind: "supply", supply: rule.key, hpDelta, exposureDelta });
|
|
157
|
+
}
|
|
158
|
+
next = { ...next, cursor };
|
|
159
|
+
if (next.hp <= 0)
|
|
160
|
+
next = record(config, { ...next, status: "dead" }, { at, kind: "death" });
|
|
161
|
+
else if (itemUnits(next.carried) >= config.carryCap)
|
|
162
|
+
next = beginReturn(config, next, at);
|
|
163
|
+
}, config.maxTicksPerSettle ?? 10_000);
|
|
164
|
+
if (next.status === "exploring" && nowMs - next.lastSettledMs < config.tickMs) {
|
|
165
|
+
const elapsedMs = Math.max(next.elapsedMs, nowMs - next.startedAtMs);
|
|
166
|
+
nonnegative(elapsedMs, "Exploring duration");
|
|
167
|
+
if (elapsedMs !== next.elapsedMs)
|
|
168
|
+
next = { ...next, elapsedMs };
|
|
169
|
+
if (itemUnits(next.carried) >= config.carryCap)
|
|
170
|
+
next = beginReturn(config, next, nowMs);
|
|
171
|
+
}
|
|
172
|
+
return arrive(config, next, nowMs);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Settle first, then recall safely. If the catch-up budget leaves due exploring ticks, return that state
|
|
176
|
+
* without recalling; repeat at the same target until the frontier catches up. Dead state never revives.
|
|
177
|
+
*
|
|
178
|
+
* @capability expedition-recall begin a safe fractional-time return after settling the pending expedition frontier
|
|
179
|
+
*/
|
|
180
|
+
export function recallExpedition(config, state, nowMs) {
|
|
181
|
+
const next = settleExpedition(config, state, nowMs);
|
|
182
|
+
return next.status === "exploring" && nowMs >= next.startedAtMs + next.elapsedMs && nowMs >= next.lastSettledMs && nowMs - next.lastSettledMs < config.tickMs ? beginReturn(config, next, nowMs) : next;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Settle first, then complete a live return instantly. Caller owns payment/authorization; pending ticks
|
|
186
|
+
* remain unsettled when the budget is exhausted, and dead/home state is never changed.
|
|
187
|
+
*
|
|
188
|
+
* @capability expedition-return complete an authorized instant return without skipping unsettled ticks or reviving death
|
|
189
|
+
*/
|
|
190
|
+
export function returnExpeditionNow(config, state, nowMs) {
|
|
191
|
+
const next = settleExpedition(config, state, nowMs);
|
|
192
|
+
if (next.status === "dead" || next.status === "home" || nowMs < next.lastSettledMs || (next.status === "exploring" && nowMs < next.startedAtMs + next.elapsedMs) || (next.status === "exploring" && nowMs - next.lastSettledMs >= config.tickMs))
|
|
193
|
+
return next;
|
|
194
|
+
return record(config, { ...next, status: "home", returnUntilMs: nowMs, lastSettledMs: nowMs }, { at: nowMs, kind: "return" });
|
|
195
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** One assigned worker's caller-resolved contribution; stat names and modifiers stay caller-owned. */
|
|
2
|
+
export interface StationWorker {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly stat: number;
|
|
5
|
+
}
|
|
6
|
+
/** Serializable staffing snapshot. The caller owns assignment, unique worker ids, and stable order. */
|
|
7
|
+
export interface StaffedStation {
|
|
8
|
+
readonly stationId: string;
|
|
9
|
+
/** Nonnegative whole slot count; fractional values round down and nonfinite values admit no workers. */
|
|
10
|
+
readonly slots: number;
|
|
11
|
+
/** Only the first `slots` entries contribute; overflow assignments are not reordered or mutated. */
|
|
12
|
+
readonly workers: readonly StationWorker[];
|
|
13
|
+
}
|
|
14
|
+
/** Caller-owned output tuning in production cycles per game-second. */
|
|
15
|
+
export interface StationRateTuning {
|
|
16
|
+
readonly base: number;
|
|
17
|
+
readonly perStat: number;
|
|
18
|
+
/** Applied to the whole rate, including base; defaults to 1 and clamps to 0…1. */
|
|
19
|
+
readonly efficiency?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve `(base + perStat * sum(admitted worker stats)) * efficiency` without allocation.
|
|
23
|
+
* Empty staffing retains base production. Negative/nonfinite contributions are zero; a nonfinite
|
|
24
|
+
* efficiency or overflowing result stops production with rate zero. Invalid workers still occupy
|
|
25
|
+
* their ordered slots. Assign the result to a `ProductionBuildingDef.rate` for `tickProduction`.
|
|
26
|
+
*
|
|
27
|
+
* @capability staffed-station-rate turn bounded caller-owned worker stats and efficiency into a production rate
|
|
28
|
+
*/
|
|
29
|
+
export declare function stationOutputRate(station: StaffedStation, tuning: StationRateTuning): number;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function contribution(value) {
|
|
2
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Resolve `(base + perStat * sum(admitted worker stats)) * efficiency` without allocation.
|
|
6
|
+
* Empty staffing retains base production. Negative/nonfinite contributions are zero; a nonfinite
|
|
7
|
+
* efficiency or overflowing result stops production with rate zero. Invalid workers still occupy
|
|
8
|
+
* their ordered slots. Assign the result to a `ProductionBuildingDef.rate` for `tickProduction`.
|
|
9
|
+
*
|
|
10
|
+
* @capability staffed-station-rate turn bounded caller-owned worker stats and efficiency into a production rate
|
|
11
|
+
*/
|
|
12
|
+
export function stationOutputRate(station, tuning) {
|
|
13
|
+
const efficiency = Math.min(1, contribution(tuning.efficiency ?? 1));
|
|
14
|
+
if (efficiency === 0)
|
|
15
|
+
return 0;
|
|
16
|
+
const base = contribution(tuning.base);
|
|
17
|
+
const perStat = contribution(tuning.perStat);
|
|
18
|
+
if (perStat === 0)
|
|
19
|
+
return base * efficiency;
|
|
20
|
+
const slots = Number.isFinite(station.slots) ? Math.max(0, Math.floor(station.slots)) : 0;
|
|
21
|
+
const count = Math.min(slots, station.workers.length);
|
|
22
|
+
let total = 0;
|
|
23
|
+
for (let index = 0; index < count; index += 1)
|
|
24
|
+
total += contribution(station.workers[index].stat);
|
|
25
|
+
const rate = (base + perStat * total) * efficiency;
|
|
26
|
+
return Number.isFinite(rate) ? rate : 0;
|
|
27
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AuthoredAnimation } from "./authoredObjects.js";
|
|
2
|
+
/** Structural marker input; immutable editor scene documents satisfy it. */
|
|
3
|
+
export interface AuthoredAnimationDocumentLike {
|
|
4
|
+
readonly markers: readonly {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly meta?: Readonly<Record<string, unknown>>;
|
|
7
|
+
}[];
|
|
8
|
+
}
|
|
9
|
+
/** Rejected saved animation data with a document location and a repair. */
|
|
10
|
+
export interface AuthoredAnimationDiagnostic {
|
|
11
|
+
readonly path: string;
|
|
12
|
+
readonly message: string;
|
|
13
|
+
readonly repair: string;
|
|
14
|
+
}
|
|
15
|
+
/** Resolved marker animation or the caller's unchanged default, with rejected-value diagnostics. */
|
|
16
|
+
export interface AuthoredAnimationResult {
|
|
17
|
+
readonly animation: AuthoredAnimation | undefined;
|
|
18
|
+
readonly diagnostics: readonly AuthoredAnimationDiagnostic[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolves a caller-selected marker's animation. Authored configuration replaces game defaults;
|
|
22
|
+
* omitted or malformed data retains the exact default. Partial roles remain available for editing.
|
|
23
|
+
* Asset clip and rig compatibility is diagnosed by the animation renderer.
|
|
24
|
+
* @capability authored-animation bind saved character animation without copying game-side validation
|
|
25
|
+
*/
|
|
26
|
+
export declare function readAuthoredAnimation(document: AuthoredAnimationDocumentLike, markerId: string, defaults?: AuthoredAnimation): AuthoredAnimationResult;
|
|
27
|
+
/**
|
|
28
|
+
* Validates one saved animation value at the caller's document path without scanning markers.
|
|
29
|
+
* Omitted or malformed values retain the exact default; valid authored configuration replaces it whole.
|
|
30
|
+
* @capability authored-animation validate a located animation override in a single-pass document resolver
|
|
31
|
+
*/
|
|
32
|
+
export declare function readAuthoredAnimationValue(value: unknown, path: string, defaults?: AuthoredAnimation): AuthoredAnimationResult;
|
|
33
|
+
/**
|
|
34
|
+
* Reads the latest immutable document each call, validating only when document identity changes.
|
|
35
|
+
* The caller owns the marker-to-character association and its asset-specific animation defaults.
|
|
36
|
+
* @capability authored-animation consume live authored character animation with bounded per-frame work
|
|
37
|
+
*/
|
|
38
|
+
export declare function createAuthoredAnimationReader(getDocument: () => AuthoredAnimationDocumentLike, markerId: string, defaults?: AuthoredAnimation): () => AuthoredAnimationResult;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { validateAnimGraph } from "../anim/animGraph.js";
|
|
2
|
+
function record(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Resolves a caller-selected marker's animation. Authored configuration replaces game defaults;
|
|
7
|
+
* omitted or malformed data retains the exact default. Partial roles remain available for editing.
|
|
8
|
+
* Asset clip and rig compatibility is diagnosed by the animation renderer.
|
|
9
|
+
* @capability authored-animation bind saved character animation without copying game-side validation
|
|
10
|
+
*/
|
|
11
|
+
export function readAuthoredAnimation(document, markerId, defaults) {
|
|
12
|
+
const index = document.markers.findIndex((marker) => marker.id === markerId);
|
|
13
|
+
return readAuthoredAnimationValue(document.markers[index]?.meta?.animation, `markers[${index}].meta.animation`, defaults);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Validates one saved animation value at the caller's document path without scanning markers.
|
|
17
|
+
* Omitted or malformed values retain the exact default; valid authored configuration replaces it whole.
|
|
18
|
+
* @capability authored-animation validate a located animation override in a single-pass document resolver
|
|
19
|
+
*/
|
|
20
|
+
export function readAuthoredAnimationValue(value, path, defaults) {
|
|
21
|
+
const diagnostics = [];
|
|
22
|
+
if (value === undefined)
|
|
23
|
+
return { animation: defaults, diagnostics };
|
|
24
|
+
if (value === "auto" || value === "none")
|
|
25
|
+
return { animation: value, diagnostics };
|
|
26
|
+
const reject = (location, message, requirement) => {
|
|
27
|
+
diagnostics.push({ path: location, message, repair: `Use ${requirement}, or remove the animation override to inherit the game setting.` });
|
|
28
|
+
};
|
|
29
|
+
if (!record(value)) {
|
|
30
|
+
reject(path, "Animation must be an object, auto, or none", "an animation object, auto, or none");
|
|
31
|
+
return { animation: defaults, diagnostics };
|
|
32
|
+
}
|
|
33
|
+
if (value.clock !== undefined && value.clock !== "real" && value.clock !== "game")
|
|
34
|
+
reject(`${path}.clock`, "clock must be real or game", "real or game");
|
|
35
|
+
if (value.auto !== undefined && value.auto !== true)
|
|
36
|
+
reject(`${path}.auto`, "auto must be true when supplied", "true, or omit auto to use explicit configuration");
|
|
37
|
+
for (const key of ["clip"]) {
|
|
38
|
+
if (value[key] !== undefined && typeof value[key] !== "string")
|
|
39
|
+
reject(`${path}.${key}`, `${key} must be a string`, "a clip-name string");
|
|
40
|
+
}
|
|
41
|
+
for (const key of ["loop", "paused"]) {
|
|
42
|
+
if (value[key] !== undefined && typeof value[key] !== "boolean")
|
|
43
|
+
reject(`${path}.${key}`, `${key} must be a boolean`, "true or false");
|
|
44
|
+
}
|
|
45
|
+
for (const key of ["timeScale", "time"]) {
|
|
46
|
+
if (value[key] !== undefined && (typeof value[key] !== "number" || !Number.isFinite(value[key])))
|
|
47
|
+
reject(`${path}.${key}`, `${key} must be a finite number`, "a finite number");
|
|
48
|
+
}
|
|
49
|
+
if (value.states !== undefined) {
|
|
50
|
+
if (!record(value.states))
|
|
51
|
+
reject(`${path}.states`, "states must be an object", "a role-mapping object");
|
|
52
|
+
else {
|
|
53
|
+
for (const key of ["idle", "walk", "run"]) {
|
|
54
|
+
if (value.states[key] !== undefined && typeof value.states[key] !== "string")
|
|
55
|
+
reject(`${path}.states.${key}`, `${key} must be a string`, "a clip-name string");
|
|
56
|
+
}
|
|
57
|
+
for (const key of ["walkSpeed", "runSpeed", "fadeSec"]) {
|
|
58
|
+
const number = value.states[key];
|
|
59
|
+
if (number !== undefined && (typeof number !== "number" || !Number.isFinite(number)))
|
|
60
|
+
reject(`${path}.states.${key}`, `${key} must be a finite number`, "a finite number");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (value.oneShots !== undefined) {
|
|
65
|
+
if (!record(value.oneShots))
|
|
66
|
+
reject(`${path}.oneShots`, "oneShots must be an object", "an event-to-clip mapping object");
|
|
67
|
+
else
|
|
68
|
+
for (const [event, clips] of Object.entries(value.oneShots)) {
|
|
69
|
+
if (typeof clips !== "string" && !(Array.isArray(clips) && clips.every((clip) => typeof clip === "string")))
|
|
70
|
+
reject(`${path}.oneShots[${JSON.stringify(event)}]`, "One-shot clips must be a string or an array of strings", "a clip-name string or an array of clip-name strings");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
let animation = value;
|
|
74
|
+
if (value.graph !== undefined) {
|
|
75
|
+
const graph = validateAnimGraph(value.graph);
|
|
76
|
+
for (const diagnostic of graph.diagnostics)
|
|
77
|
+
diagnostics.push({ ...diagnostic, path: `${path}.graph${diagnostic.path === "" ? "" : `.${diagnostic.path}`}` });
|
|
78
|
+
if (graph.graph !== undefined)
|
|
79
|
+
animation = { ...value, graph: graph.graph };
|
|
80
|
+
}
|
|
81
|
+
return { animation: diagnostics.length === 0 ? animation : defaults, diagnostics };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Reads the latest immutable document each call, validating only when document identity changes.
|
|
85
|
+
* The caller owns the marker-to-character association and its asset-specific animation defaults.
|
|
86
|
+
* @capability authored-animation consume live authored character animation with bounded per-frame work
|
|
87
|
+
*/
|
|
88
|
+
export function createAuthoredAnimationReader(getDocument, markerId, defaults) {
|
|
89
|
+
let previous;
|
|
90
|
+
let result;
|
|
91
|
+
return () => {
|
|
92
|
+
const document = getDocument();
|
|
93
|
+
if (document !== previous) {
|
|
94
|
+
result = readAuthoredAnimation(document, markerId, defaults);
|
|
95
|
+
previous = document;
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AuthoredAnimationDiagnostic } from "./authoredAnimation.js";
|
|
1
2
|
import type { ModelAnimationConfig } from "../game/playableGame.js";
|
|
2
3
|
import type { SceneMarkerLike } from "./sceneShapes.js";
|
|
3
4
|
/** A placement's authored rig animation override — the stored `marker.meta.animation` contract (#1274). */
|
|
@@ -53,10 +54,8 @@ export interface PlaceAuthoredObjectsOptions {
|
|
|
53
54
|
*/
|
|
54
55
|
export declare function markerCatalogId(marker: AuthoredObjectMarkerLike): string | null;
|
|
55
56
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* object is a `ModelAnimationConfig` the shell applies verbatim (validated at author time in the
|
|
59
|
-
* editor). Runtime trusts the persisted document here exactly like it trusts position and rotation.
|
|
57
|
+
* Validated per-placement animation from `marker.meta.animation`. Missing or malformed overrides
|
|
58
|
+
* return undefined so the placed object retains its catalog animation; valid data replaces it whole.
|
|
60
59
|
* @capability authored-objects per-placement animation override from marker.meta.animation
|
|
61
60
|
*/
|
|
62
61
|
export declare function markerAnimation(marker: AuthoredObjectMarkerLike): AuthoredAnimation | undefined;
|
|
@@ -80,6 +79,18 @@ export interface ResolveAuthoredObjectsOptions {
|
|
|
80
79
|
* @capability authored-objects place catalog mesh props from an editor document
|
|
81
80
|
*/
|
|
82
81
|
export declare function resolveAuthoredObjects(document: AuthoredObjectsDocumentLike, options?: ResolveAuthoredObjectsOptions): AuthoredObject[];
|
|
82
|
+
/** Resolved catalog props and located repairs for rejected per-placement animation overrides. */
|
|
83
|
+
export interface AuthoredObjectsResult {
|
|
84
|
+
objects: AuthoredObject[];
|
|
85
|
+
diagnostics: readonly AuthoredAnimationDiagnostic[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolves catalog props and validates their animation in one document pass. Rejected overrides
|
|
89
|
+
* are omitted so each model inherits its original catalog setting. Excluded and unplaced markers
|
|
90
|
+
* remain the responsibility of their own consumers.
|
|
91
|
+
* @capability authored-objects resolve static placements with located animation repairs
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolveAuthoredObjectsWithDiagnostics(document: AuthoredObjectsDocumentLike, options?: ResolveAuthoredObjectsOptions): AuthoredObjectsResult;
|
|
83
94
|
/**
|
|
84
95
|
* Places resolved authored objects into an object store, grounding each on `sampleHeight(x,z)` plus
|
|
85
96
|
* per-object and options vertical offsets. Returns the instance ids that were placed (or kept).
|
|
@@ -88,3 +99,12 @@ export declare function resolveAuthoredObjects(document: AuthoredObjectsDocument
|
|
|
88
99
|
export declare function placeAuthoredObjects(store: AuthoredObjectPlaceTarget, objects: readonly AuthoredObject[], sampleHeight: (x: number, z: number) => number, options?: PlaceAuthoredObjectsOptions): string[];
|
|
89
100
|
/** Convenience: resolve a document then place every authored catalog prop. */
|
|
90
101
|
export declare function placeAuthoredObjectsFromDocument(store: AuthoredObjectPlaceTarget, document: AuthoredObjectsDocumentLike, sampleHeight: (x: number, z: number) => number, options?: PlaceAuthoredObjectsOptions): string[];
|
|
102
|
+
/**
|
|
103
|
+
* Update changed authored placements and remove previous authored ids absent from the next document.
|
|
104
|
+
* Initial placement keeps game onInit state; unrelated document edits preserve runtime prop state.
|
|
105
|
+
* Call on document changes, never on simulation ticks; runtime-created objects remain untouched.
|
|
106
|
+
* @capability sync-authored-objects synchronize edited catalog prop poses, elevation and removal through the existing object store
|
|
107
|
+
*/
|
|
108
|
+
export declare function syncAuthoredObjects(store: AuthoredObjectPlaceTarget & {
|
|
109
|
+
remove(instanceId: string): boolean;
|
|
110
|
+
}, objects: readonly AuthoredObject[], previousObjects: readonly AuthoredObject[], sampleHeight: (x: number, z: number) => number, options?: Pick<PlaceAuthoredObjectsOptions, "verticalOffset">): AuthoredObject[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readAuthoredAnimationValue } from "./authoredAnimation.js";
|
|
1
2
|
function metaString(meta, key) {
|
|
2
3
|
const value = meta?.[key];
|
|
3
4
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
@@ -16,23 +17,13 @@ export function markerCatalogId(marker) {
|
|
|
16
17
|
return marker.catalogId;
|
|
17
18
|
return metaString(marker.meta, "catalogId");
|
|
18
19
|
}
|
|
19
|
-
function isRecord(value) {
|
|
20
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
|
-
}
|
|
22
20
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* object is a `ModelAnimationConfig` the shell applies verbatim (validated at author time in the
|
|
26
|
-
* editor). Runtime trusts the persisted document here exactly like it trusts position and rotation.
|
|
21
|
+
* Validated per-placement animation from `marker.meta.animation`. Missing or malformed overrides
|
|
22
|
+
* return undefined so the placed object retains its catalog animation; valid data replaces it whole.
|
|
27
23
|
* @capability authored-objects per-placement animation override from marker.meta.animation
|
|
28
24
|
*/
|
|
29
25
|
export function markerAnimation(marker) {
|
|
30
|
-
|
|
31
|
-
if (value === "auto" || value === "none")
|
|
32
|
-
return value;
|
|
33
|
-
if (isRecord(value))
|
|
34
|
-
return value;
|
|
35
|
-
return undefined;
|
|
26
|
+
return readAuthoredAnimationValue(marker.meta?.["animation"], "meta.animation").animation;
|
|
36
27
|
}
|
|
37
28
|
function markerVerticalOffset(marker) {
|
|
38
29
|
const fromMeta = metaNumber(marker.meta, "verticalOffset") ??
|
|
@@ -55,15 +46,29 @@ export const ENTITY_MARKER_KINDS = ["mob", "boss"];
|
|
|
55
46
|
* @capability authored-objects place catalog mesh props from an editor document
|
|
56
47
|
*/
|
|
57
48
|
export function resolveAuthoredObjects(document, options = {}) {
|
|
49
|
+
return resolveAuthoredObjectsWithDiagnostics(document, options).objects;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolves catalog props and validates their animation in one document pass. Rejected overrides
|
|
53
|
+
* are omitted so each model inherits its original catalog setting. Excluded and unplaced markers
|
|
54
|
+
* remain the responsibility of their own consumers.
|
|
55
|
+
* @capability authored-objects resolve static placements with located animation repairs
|
|
56
|
+
*/
|
|
57
|
+
export function resolveAuthoredObjectsWithDiagnostics(document, options = {}) {
|
|
58
58
|
const excluded = new Set(options.excludeKinds ?? ENTITY_MARKER_KINDS);
|
|
59
59
|
const out = [];
|
|
60
|
-
|
|
60
|
+
const diagnostics = [];
|
|
61
|
+
for (let index = 0; index < document.markers.length; index += 1) {
|
|
62
|
+
const marker = document.markers[index];
|
|
61
63
|
if (excluded.has(marker.kind))
|
|
62
64
|
continue;
|
|
63
65
|
const catalogId = markerCatalogId(marker);
|
|
64
66
|
if (catalogId === null)
|
|
65
67
|
continue;
|
|
66
|
-
const
|
|
68
|
+
const result = readAuthoredAnimationValue(marker.meta?.["animation"], `markers[${index}].meta.animation`);
|
|
69
|
+
const animation = result.animation;
|
|
70
|
+
for (const diagnostic of result.diagnostics)
|
|
71
|
+
diagnostics.push(diagnostic);
|
|
67
72
|
out.push({
|
|
68
73
|
catalogId,
|
|
69
74
|
x: marker.position.x,
|
|
@@ -74,7 +79,7 @@ export function resolveAuthoredObjects(document, options = {}) {
|
|
|
74
79
|
...(animation === undefined ? {} : { animation }),
|
|
75
80
|
});
|
|
76
81
|
}
|
|
77
|
-
return out;
|
|
82
|
+
return { objects: out, diagnostics };
|
|
78
83
|
}
|
|
79
84
|
/**
|
|
80
85
|
* Places resolved authored objects into an object store, grounding each on `sampleHeight(x,z)` plus
|
|
@@ -100,3 +105,25 @@ export function placeAuthoredObjects(store, objects, sampleHeight, options = {})
|
|
|
100
105
|
export function placeAuthoredObjectsFromDocument(store, document, sampleHeight, options = {}) {
|
|
101
106
|
return placeAuthoredObjects(store, resolveAuthoredObjects(document), sampleHeight, options);
|
|
102
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Update changed authored placements and remove previous authored ids absent from the next document.
|
|
110
|
+
* Initial placement keeps game onInit state; unrelated document edits preserve runtime prop state.
|
|
111
|
+
* Call on document changes, never on simulation ticks; runtime-created objects remain untouched.
|
|
112
|
+
* @capability sync-authored-objects synchronize edited catalog prop poses, elevation and removal through the existing object store
|
|
113
|
+
*/
|
|
114
|
+
export function syncAuthoredObjects(store, objects, previousObjects, sampleHeight, options = {}) {
|
|
115
|
+
const nextIds = new Set(objects.map((object) => object.instanceId));
|
|
116
|
+
if (nextIds.size !== objects.length)
|
|
117
|
+
throw new Error("Authored object ids must be unique");
|
|
118
|
+
const previous = new Map(previousObjects.map((object) => [object.instanceId, object]));
|
|
119
|
+
for (const object of objects) {
|
|
120
|
+
const before = previous.get(object.instanceId);
|
|
121
|
+
if (before !== undefined && JSON.stringify(before) === JSON.stringify(object))
|
|
122
|
+
continue;
|
|
123
|
+
placeAuthoredObjects(store, [object], sampleHeight, { ...options, onExisting: before === undefined ? "keep" : "replace" });
|
|
124
|
+
}
|
|
125
|
+
for (const object of previousObjects)
|
|
126
|
+
if (!nextIds.has(object.instanceId))
|
|
127
|
+
store.remove(object.instanceId);
|
|
128
|
+
return objects.map((object) => ({ ...object, ...(typeof object.animation === "object" ? { animation: structuredClone(object.animation) } : {}) }));
|
|
129
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type FlockStepAgent } from "../ai/flock.js";
|
|
2
|
+
import type { EditorDocument } from "../editor/types.js";
|
|
3
|
+
import { type AuthoredWeatherSample, type WeatherExposure, type WeatherExposureSnapshot, type WeatherExposureSurface } from "./authoredWeather.js";
|
|
4
|
+
import { type FireCell, type FireGrid } from "./weather.js";
|
|
5
|
+
import type { ParticleDirector } from "../vfx/particleDirector.js";
|
|
6
|
+
import type { Vec3 } from "../vfx/particles.js";
|
|
7
|
+
/** A watched surface is sampled in metres. Its accumulation survives runtime save/restore. */
|
|
8
|
+
export interface AuthoredSurface extends WeatherExposureSurface {
|
|
9
|
+
y?: number;
|
|
10
|
+
}
|
|
11
|
+
/** Appearance adapters consume these units without owning weather state or its clock. */
|
|
12
|
+
export interface EnvironmentAppearanceSignals {
|
|
13
|
+
timeSeconds: number;
|
|
14
|
+
wind: Vec3;
|
|
15
|
+
wetness: number;
|
|
16
|
+
snow: number;
|
|
17
|
+
exposure: number;
|
|
18
|
+
/** Normalized local fire heat, independent of the temperature offset in degrees Celsius. */
|
|
19
|
+
heat: number;
|
|
20
|
+
temperatureOffsetC: number;
|
|
21
|
+
}
|
|
22
|
+
/** Runtime state excludes authored configuration and cosmetic particle pools. */
|
|
23
|
+
export interface AuthoredSimulationSnapshot {
|
|
24
|
+
surfaces: WeatherExposureSnapshot;
|
|
25
|
+
watched: AuthoredSurface[];
|
|
26
|
+
fires: {
|
|
27
|
+
id: string;
|
|
28
|
+
cells: FireCell[];
|
|
29
|
+
}[];
|
|
30
|
+
flocks: {
|
|
31
|
+
id: string;
|
|
32
|
+
agents: FlockStepAgent[];
|
|
33
|
+
routeIndex: number;
|
|
34
|
+
}[];
|
|
35
|
+
}
|
|
36
|
+
/** A habitat's live poses; game species determine appearance and gameplay actor policy. */
|
|
37
|
+
export interface AuthoredFlockState {
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly species: string;
|
|
40
|
+
readonly role: "cosmetic" | "gameplay";
|
|
41
|
+
readonly agents: readonly Readonly<FlockStepAgent>[];
|
|
42
|
+
readonly routeIndex: number;
|
|
43
|
+
}
|
|
44
|
+
/** Engine-owned authority uses the caller's simulation clock; render quality cannot change it. */
|
|
45
|
+
export interface AuthoredSimulationRuntime {
|
|
46
|
+
sample(x: number, z: number): AuthoredWeatherSample;
|
|
47
|
+
appearanceAt(surfaceId: string, position: Vec3): EnvironmentAppearanceSignals;
|
|
48
|
+
/** Fraction of open sky from bounded authored shelter volumes. World Y points up. */
|
|
49
|
+
exposureAt(x: number, y: number, z: number): number;
|
|
50
|
+
readonly surfaces: WeatherExposure;
|
|
51
|
+
watch(surface: AuthoredSurface): void;
|
|
52
|
+
forget(id: string): void;
|
|
53
|
+
/** XZ wind adapted to [x,0,z]; response is 1/second, cap is metres/second² (both 0..1e12). */
|
|
54
|
+
windAcceleration(position: Vec3, response: number, maxAcceleration: number): Vec3;
|
|
55
|
+
/** Sum authored fields and wind (response 1/second), capped in metres/second² by game policy. */
|
|
56
|
+
accelerationAt(position: Vec3, timeSeconds: number, windResponse: number, maxAcceleration: number, mask?: number, out?: [number, number, number]): Vec3;
|
|
57
|
+
fire(id: string): FireGrid | undefined;
|
|
58
|
+
/** Cosmetic poses render directly; gameplay habitats are consumed by game actor adapters. */
|
|
59
|
+
flocks(): readonly AuthoredFlockState[];
|
|
60
|
+
step(gameDt: number): void;
|
|
61
|
+
retune(document: EditorDocument): void;
|
|
62
|
+
snapshot(): AuthoredSimulationSnapshot;
|
|
63
|
+
restore(state: AuthoredSimulationSnapshot): void;
|
|
64
|
+
dispose(): void;
|
|
65
|
+
}
|
|
66
|
+
/** Compose authored weather, shelter, accumulation, named effect intents and fire with one clock.
|
|
67
|
+
* @capability authored-environment compose authored weather, shelter, accumulation, fire, flocks and effects using one simulation clock
|
|
68
|
+
*/
|
|
69
|
+
export declare function createAuthoredSimulation(options: {
|
|
70
|
+
document?: EditorDocument;
|
|
71
|
+
timeSeconds: () => number;
|
|
72
|
+
particles?: ParticleDirector;
|
|
73
|
+
maxSurfaces?: number;
|
|
74
|
+
}): AuthoredSimulationRuntime;
|