@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
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { createAuthoredSimulation } from "../world/authoredSimulation.js";
|
|
1
2
|
import { createCommandRegistry } from "../commands/commandRegistry.js";
|
|
2
3
|
import {} from "../game/cosmetics.js";
|
|
3
4
|
import { groundFieldFor } from "../world/terrain.js";
|
|
5
|
+
import { createWorldSolids, structureSolids } from "../world/worldSolids.js";
|
|
4
6
|
import { createGameEvents } from "../game/events.js";
|
|
5
7
|
import { createGameFeed } from "../game/feed.js";
|
|
6
8
|
import { setGamePhase } from "../game/gamePhase.js";
|
|
@@ -11,6 +13,7 @@ import { createItemUse } from "../item/use.js";
|
|
|
11
13
|
import { createWeaponStats } from "../item/weapon.js";
|
|
12
14
|
import {} from "../scene/entityStats.js";
|
|
13
15
|
import {} from "../scene/entityStore.js";
|
|
16
|
+
import { installPursuitPersistence } from "../scene/behaviorRuntime.js";
|
|
14
17
|
import { createChangeSignal, notifyAfter } from "../store/changeSignal.js";
|
|
15
18
|
import { createObservableKeyedStore } from "../store/observableKeyedStore.js";
|
|
16
19
|
import { applyWorldSnapshot, composeWorldSnapshot, } from "./worldSnapshot.js";
|
|
@@ -21,7 +24,7 @@ import { localSaveBackend, memorySaveBackend } from "../game/saveStore.js";
|
|
|
21
24
|
import { createSimClock } from "../time/simClock.js";
|
|
22
25
|
import { createSimContext } from "./simContext.js";
|
|
23
26
|
import { seededRng } from "../random/rng.js";
|
|
24
|
-
import { createCameraDirector } from "./cameraDirector.js";
|
|
27
|
+
import { createCameraDirector, installCameraPersistence } from "./cameraDirector.js";
|
|
25
28
|
import { createParticleDirector } from "../vfx/particleDirector.js";
|
|
26
29
|
import { createInputSnapshot } from "./inputSnapshot.js";
|
|
27
30
|
import { baselineDescriptors } from "./descriptors/baseline.js";
|
|
@@ -43,6 +46,10 @@ export function createGameContext(options) {
|
|
|
43
46
|
const activeUserId = () => actingUserId ?? player.userId;
|
|
44
47
|
const time = createSimClock({ config: definition.time, onChange: signal.notify });
|
|
45
48
|
const ground = groundFieldFor(definition.world);
|
|
49
|
+
const solids = createWorldSolids();
|
|
50
|
+
if (definition.world?.kind === "environment") {
|
|
51
|
+
solids.set("environment:structures", structureSolids(definition.world, ground.sampleHeight));
|
|
52
|
+
}
|
|
46
53
|
const rawEvents = createGameEvents();
|
|
47
54
|
const events = {
|
|
48
55
|
on: rawEvents.on,
|
|
@@ -57,6 +64,7 @@ export function createGameContext(options) {
|
|
|
57
64
|
content,
|
|
58
65
|
signalNotify: signal.notify,
|
|
59
66
|
ground,
|
|
67
|
+
solids,
|
|
60
68
|
events,
|
|
61
69
|
time,
|
|
62
70
|
...(occluder !== undefined ? { occluder } : {}),
|
|
@@ -190,12 +198,26 @@ export function createGameContext(options) {
|
|
|
190
198
|
// --- Combat installer (effects, projectiles, death, combat FX) ---
|
|
191
199
|
// Command runner closes over `ctx` once assembled (same late-bind pattern as before).
|
|
192
200
|
let ctxRef = null;
|
|
201
|
+
const { sweep, acceleration, targets, ...projectileBudgets } = definition.projectileTravel ?? {};
|
|
202
|
+
function projectileContext() {
|
|
203
|
+
if (ctxRef === null)
|
|
204
|
+
throw new Error("Projectile providers require an assembled GameContext");
|
|
205
|
+
return ctxRef;
|
|
206
|
+
}
|
|
193
207
|
const combat = createCombatSubsystem({
|
|
194
208
|
content,
|
|
195
209
|
signalNotify: signal.notify,
|
|
196
210
|
now,
|
|
197
211
|
events,
|
|
198
212
|
time,
|
|
213
|
+
projectileTravel: {
|
|
214
|
+
...projectileBudgets,
|
|
215
|
+
...(sweep === undefined ? {} : { sweep: (from, to, step) => sweep(projectileContext(), from, to, step) }),
|
|
216
|
+
...(targets === undefined ? {} : { targets: () => targets(projectileContext()) }),
|
|
217
|
+
acceleration: acceleration === undefined
|
|
218
|
+
? (position, _velocity, at, input) => authoredEnvironment.accelerationAt(position, at, input.travel?.windResponse ?? 0, input.travel?.maxAcceleration ?? 0, input.travel?.forceMask ?? 0)
|
|
219
|
+
: (position, velocity, at, input) => acceleration(projectileContext(), position, velocity, at, input),
|
|
220
|
+
},
|
|
199
221
|
entities,
|
|
200
222
|
objects,
|
|
201
223
|
combatSpatial,
|
|
@@ -209,20 +231,26 @@ export function createGameContext(options) {
|
|
|
209
231
|
lootRegistry,
|
|
210
232
|
spawnWorldItem,
|
|
211
233
|
despawnEntity,
|
|
212
|
-
runCommand(name, args) {
|
|
234
|
+
runCommand(name, args, actorUserId) {
|
|
213
235
|
if (ctxRef === null)
|
|
214
236
|
return;
|
|
215
|
-
|
|
237
|
+
if (actorUserId === undefined)
|
|
238
|
+
ctxRef.game.commands.run(name, args);
|
|
239
|
+
else
|
|
240
|
+
ctxRef.game.commands.runAs(actorUserId, name, args);
|
|
216
241
|
},
|
|
217
|
-
|
|
242
|
+
userIdOf: (instanceId) => instanceId === player.userId || featureValue("players")?.has(instanceId)
|
|
243
|
+
? instanceId : possession.ownerOf(instanceId),
|
|
244
|
+
rng,
|
|
218
245
|
...(definition.physics !== undefined ? { physics: definition.physics } : {}),
|
|
219
246
|
});
|
|
220
247
|
scene.setOnAfterSpawn((instanceId) => combat.death.revive(instanceId));
|
|
221
248
|
const { projectiles, combatFx, vfxInstances, effects } = combat;
|
|
222
249
|
const store = notifyAfter(createObservableKeyedStore(), ["set", "delete", "hydrate"], signal.notify);
|
|
223
250
|
const { pile, loop, raceState, cardPiles, turnLoops } = createContextRegistries(signal.notify);
|
|
224
|
-
const camera = notifyAfter(createCameraDirector(), ["follow", "setCinematic", "setChaseTuning"], signal.notify);
|
|
251
|
+
const camera = notifyAfter(createCameraDirector(), ["follow", "setCinematic", "setChaseTuning", "setRig", "restore", "reset"], signal.notify);
|
|
225
252
|
const particles = createParticleDirector();
|
|
253
|
+
const authoredEnvironment = createAuthoredSimulation({ document: definition.authoredDocument, timeSeconds: time.now, particles });
|
|
226
254
|
const input = createInputSnapshot();
|
|
227
255
|
// --- Descriptor install (single feature enable path) ---
|
|
228
256
|
const featureDeps = {
|
|
@@ -240,6 +268,7 @@ export function createGameContext(options) {
|
|
|
240
268
|
walletOf,
|
|
241
269
|
setWallet: (userId, state) => wallets.set(userId, state),
|
|
242
270
|
layouts,
|
|
271
|
+
itemTraits: playerSys.itemTraits,
|
|
243
272
|
inventoryFor,
|
|
244
273
|
ensureInstanceStats,
|
|
245
274
|
seedUserPool,
|
|
@@ -336,6 +365,20 @@ export function createGameContext(options) {
|
|
|
336
365
|
});
|
|
337
366
|
saveModules.push(module);
|
|
338
367
|
}
|
|
368
|
+
function reviveRestoredEntities(snapshot) {
|
|
369
|
+
if (!Object.hasOwn(snapshot, "entities") || !Array.isArray(snapshot["entities"]))
|
|
370
|
+
return;
|
|
371
|
+
for (const entity of entities.list()) {
|
|
372
|
+
const receive = content.entityById?.(entity.name)?.receive;
|
|
373
|
+
const alive = Object.values(receive ?? {}).every((rule) => {
|
|
374
|
+
const terminal = rule.order[rule.order.length - 1];
|
|
375
|
+
const pool = terminal === undefined ? undefined : statsByInstance.get(entity.id)?.[terminal];
|
|
376
|
+
return pool === undefined || pool.current > pool.min;
|
|
377
|
+
});
|
|
378
|
+
if (alive)
|
|
379
|
+
combat.death.revive(entity.id);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
339
382
|
// --- Assemble public GameContext ---
|
|
340
383
|
const ctx = {
|
|
341
384
|
rng,
|
|
@@ -346,11 +389,13 @@ export function createGameContext(options) {
|
|
|
346
389
|
despawn: despawnEntity,
|
|
347
390
|
update: entities.update,
|
|
348
391
|
setPose: entities.setPose,
|
|
392
|
+
setVelocity: entities.setVelocity,
|
|
349
393
|
setPoseConstraint: entities.setPoseConstraint,
|
|
350
394
|
get: entities.get,
|
|
351
395
|
list: entities.list,
|
|
352
396
|
ids: entities.ids,
|
|
353
397
|
subscribeMembership: entities.subscribeMembership,
|
|
398
|
+
subscribeBehaviors: entities.subscribeBehaviors,
|
|
354
399
|
spawnPoseOf: entities.spawnPoseOf,
|
|
355
400
|
resetToSpawn: entities.resetToSpawn,
|
|
356
401
|
resetAllToSpawn,
|
|
@@ -370,6 +415,9 @@ export function createGameContext(options) {
|
|
|
370
415
|
willHitProjectile: projectiles.willHitProjectile,
|
|
371
416
|
fireProjectile: projectiles.fireProjectile,
|
|
372
417
|
settleProjectile: projectiles.settleProjectile,
|
|
418
|
+
activeProjectiles: projectiles.activeProjectiles,
|
|
419
|
+
projectileState: projectiles.snapshot,
|
|
420
|
+
restoreProjectiles: projectiles.restore,
|
|
373
421
|
distance: spatial.distance,
|
|
374
422
|
inRadius: spatial.inRadius,
|
|
375
423
|
hasLineOfSight: spatial.hasLineOfSight,
|
|
@@ -403,9 +451,11 @@ export function createGameContext(options) {
|
|
|
403
451
|
raycast: (input) => sceneRaycast.raycast(input),
|
|
404
452
|
raycastAll: (input) => sceneRaycast.raycastAll(input),
|
|
405
453
|
},
|
|
454
|
+
environment: authoredEnvironment,
|
|
406
455
|
world: {
|
|
407
456
|
ground,
|
|
408
457
|
groundHeightAt: ground.sampleHeight,
|
|
458
|
+
solids,
|
|
409
459
|
},
|
|
410
460
|
game: {
|
|
411
461
|
territory: options.territory,
|
|
@@ -500,7 +550,7 @@ export function createGameContext(options) {
|
|
|
500
550
|
weapon,
|
|
501
551
|
},
|
|
502
552
|
time,
|
|
503
|
-
sim: createSimContext({ config: definition.simulation, entities }),
|
|
553
|
+
sim: createSimContext({ config: definition.simulation, entities, time }),
|
|
504
554
|
camera,
|
|
505
555
|
particles,
|
|
506
556
|
input,
|
|
@@ -510,24 +560,46 @@ export function createGameContext(options) {
|
|
|
510
560
|
snapshot: (viewer) => composeWorldSnapshot(replicationModules, viewer),
|
|
511
561
|
hydrate(snapshot) {
|
|
512
562
|
applyWorldSnapshot(replicationModules, snapshot);
|
|
563
|
+
reviveRestoredEntities(snapshot);
|
|
564
|
+
signal.notify();
|
|
565
|
+
},
|
|
566
|
+
state: () => structuredClone(composeWorldSnapshot(saveModules)),
|
|
567
|
+
restore(state) {
|
|
568
|
+
applyWorldSnapshot(saveModules, structuredClone(state));
|
|
569
|
+
reviveRestoredEntities(state);
|
|
513
570
|
signal.notify();
|
|
514
571
|
},
|
|
515
572
|
replicationVersion,
|
|
516
573
|
replicatesPerViewer: () => projectsViewers,
|
|
517
574
|
};
|
|
518
575
|
ctxRef = ctx;
|
|
576
|
+
ctx.sim.addStage({ id: "authored-environment", phase: "afterTick", run: (_dt, _tick, gameDt) => authoredEnvironment.step(gameDt) });
|
|
577
|
+
ctx.sim.addStage({ id: "projectile-targets", phase: "beforeMovement", run: (_dt, _tick, gameDt) => combat.captureProjectileTargets(gameDt) });
|
|
578
|
+
ctx.sim.addStage({ id: "projectile-travel", phase: "afterTick", run: (_dt, _tick, gameDt) => projectiles.advanceProjectiles(gameDt, time.now()) });
|
|
579
|
+
registerSave({ key: "environment", snapshot: authoredEnvironment.snapshot, hydrate: (state) => authoredEnvironment.restore(state) });
|
|
580
|
+
registerSave({ key: "projectiles", snapshot: projectiles.snapshot, hydrate: (state) => projectiles.restore(state) });
|
|
581
|
+
registerSave({
|
|
582
|
+
key: "sim",
|
|
583
|
+
snapshot: () => ctx.sim.snapshot(),
|
|
584
|
+
hydrate: (state) => ctx.sim.restore(state),
|
|
585
|
+
decode: (raw) => {
|
|
586
|
+
if (raw === null || typeof raw !== "object" || !("loop" in raw) || !("poses" in raw))
|
|
587
|
+
return null;
|
|
588
|
+
return raw;
|
|
589
|
+
},
|
|
590
|
+
});
|
|
519
591
|
const physicsBackend = definition.physics?.backend;
|
|
520
592
|
if (physicsBackend !== undefined) {
|
|
521
|
-
ctx.sim.addStage({ id: "physics", phase: "afterMovement", run: (
|
|
593
|
+
ctx.sim.addStage({ id: "physics", phase: "afterMovement", run: (_dt, _tick, gameDt) => { if (gameDt > 0)
|
|
594
|
+
physicsBackend.step(gameDt); } });
|
|
522
595
|
}
|
|
596
|
+
installPursuitPersistence(ctx, aoiRadius, definition.pursuit?.eligible);
|
|
597
|
+
installCameraPersistence(ctx);
|
|
523
598
|
const saveOptions = resolveSaveOptions(definition, options);
|
|
524
599
|
if (saveOptions !== undefined) {
|
|
525
600
|
const saveTarget = {
|
|
526
|
-
snapshot:
|
|
527
|
-
hydrate:
|
|
528
|
-
applyWorldSnapshot(saveModules, snapshot);
|
|
529
|
-
signal.notify();
|
|
530
|
-
},
|
|
601
|
+
snapshot: ctx.state,
|
|
602
|
+
hydrate: ctx.restore,
|
|
531
603
|
subscribe: signal.subscribe,
|
|
532
604
|
};
|
|
533
605
|
ctx.game.save = createRuntimeSave({ target: saveTarget, ...saveOptions });
|
|
@@ -554,10 +626,11 @@ function resolveSaveOptions(definition, options) {
|
|
|
554
626
|
return undefined;
|
|
555
627
|
const config = persist === undefined || persist === true ? {} : persist;
|
|
556
628
|
return {
|
|
557
|
-
backend: config.storage === "memory" ? memorySaveBackend() : localSaveBackend(),
|
|
629
|
+
backend: config.storage === "memory" ? memorySaveBackend() : localSaveBackend(undefined, { errorMode: "throw" }),
|
|
558
630
|
mode: config.mode,
|
|
559
631
|
key: saveKey(definition.name),
|
|
560
632
|
version: config.version,
|
|
633
|
+
migrate: config.migrate,
|
|
561
634
|
autosaveMs: config.autosaveMs,
|
|
562
635
|
now: options.now,
|
|
563
636
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AuthoredSimulationRuntime } from "../world/authoredSimulation.js";
|
|
1
2
|
import type { Territory } from "../world/territory.js";
|
|
2
3
|
import type { CurrencyDefinition } from "../economy/currency.js";
|
|
3
4
|
import type { CardPile, CardPileConfig } from "../cards/cardPile.js";
|
|
@@ -11,6 +12,7 @@ import type { ChargeOptions as WalletChargeOptions } from "../economy/wallet.js"
|
|
|
11
12
|
import type { Cosmetics } from "../game/cosmetics.js";
|
|
12
13
|
import type { GameDefinition } from "../game/defineGame.js";
|
|
13
14
|
import type { TerrainField } from "../world/terrain.js";
|
|
15
|
+
import type { WorldSolids } from "../world/worldSolids.js";
|
|
14
16
|
import type { GameEventMap, GameEvents, VfxKind } from "../game/events.js";
|
|
15
17
|
import type { VfxInstanceStore } from "../game/vfxInstance.js";
|
|
16
18
|
import type { GameFeed } from "../game/feed.js";
|
|
@@ -256,11 +258,14 @@ export interface SceneEntityContext {
|
|
|
256
258
|
despawn(instanceId: string): boolean;
|
|
257
259
|
update: EntityStore["update"];
|
|
258
260
|
setPose(instanceId: string, pose: EntityPose): boolean;
|
|
261
|
+
/** Set finite world velocity without changing pose; invalid tuples and missing entities return false unchanged. */
|
|
262
|
+
setVelocity: EntityStore["setVelocity"];
|
|
259
263
|
setPoseConstraint: EntityStore["setPoseConstraint"];
|
|
260
264
|
get(instanceId: string): SceneEntity | null;
|
|
261
265
|
list(): readonly SceneEntity[];
|
|
262
266
|
ids: EntityStore["ids"];
|
|
263
267
|
subscribeMembership: EntityStore["subscribeMembership"];
|
|
268
|
+
subscribeBehaviors: EntityStore["subscribeBehaviors"];
|
|
264
269
|
spawnPoseOf(instanceId: string): SpawnPose | null;
|
|
265
270
|
resetToSpawn(instanceId: string): boolean;
|
|
266
271
|
resetAllToSpawn(filter?: (entity: SceneEntity) => boolean): number;
|
|
@@ -286,6 +291,10 @@ export interface SceneEntityContext {
|
|
|
286
291
|
willHitProjectile: ProjectileSystem["willHitProjectile"];
|
|
287
292
|
fireProjectile: ProjectileSystem["fireProjectile"];
|
|
288
293
|
settleProjectile: ProjectileSystem["settleProjectile"];
|
|
294
|
+
/** Live travel poses for game-owned arrow/projectile appearance. */
|
|
295
|
+
activeProjectiles: ProjectileSystem["activeProjectiles"];
|
|
296
|
+
projectileState: ProjectileSystem["snapshot"];
|
|
297
|
+
restoreProjectiles: ProjectileSystem["restore"];
|
|
289
298
|
distance: SpatialApi["distance"];
|
|
290
299
|
inRadius: SpatialApi["inRadius"];
|
|
291
300
|
hasLineOfSight: SpatialApi["hasLineOfSight"];
|
|
@@ -344,6 +353,11 @@ export type WorldItemPickupResult = {
|
|
|
344
353
|
status: "rejected";
|
|
345
354
|
reason: string;
|
|
346
355
|
};
|
|
356
|
+
/**
|
|
357
|
+
* `ctx.scene.worldItem`: items lying in the world — spawn, list, find the nearest, and pick up into a player's bag.
|
|
358
|
+
*
|
|
359
|
+
* @capability world-item-pickup pick up dropped items: ctx.scene.worldItem nearestInRadius → pickup into the player's inventory
|
|
360
|
+
*/
|
|
347
361
|
export interface SceneWorldItemContext {
|
|
348
362
|
spawn(input: WorldItemSpawnInput): WorldItemRecord;
|
|
349
363
|
get(instanceId: string): WorldItemRecord | null;
|
|
@@ -390,6 +404,11 @@ export interface GameContextItemUse {
|
|
|
390
404
|
export interface GameContextWorld {
|
|
391
405
|
ground: TerrainField;
|
|
392
406
|
groundHeightAt(x: number, z: number): number;
|
|
407
|
+
/**
|
|
408
|
+
* Static collision for world geometry that is not a scene object. `environment({ structures })`
|
|
409
|
+
* fills the `environment:structures` layer; `syncAuthoredSolids` fills one layer per studio object.
|
|
410
|
+
*/
|
|
411
|
+
solids: WorldSolids;
|
|
393
412
|
}
|
|
394
413
|
export interface GameContextCards {
|
|
395
414
|
/** Lazily creates (on first call, `config` required) or returns the existing notify-wrapped pile for `id`. */
|
|
@@ -420,14 +439,19 @@ export interface GameAudio {
|
|
|
420
439
|
}): void;
|
|
421
440
|
/**
|
|
422
441
|
* Live-update retained loop `id` via `audio.loopSet`: `rate` re-pitches it (1 = authored pitch, clamped
|
|
423
|
-
* 0.25–4 by the shell), `gain` rescales volume (0–1), `at` repositions its emitter.
|
|
424
|
-
*
|
|
425
|
-
*
|
|
442
|
+
* 0.25–4 by the shell), `gain` rescales volume (0–1), `at` repositions its emitter. `lowpass`/`highpass`
|
|
443
|
+
* set filter cutoffs in Hz (muffle an engine off-throttle or behind a wall); `velocity` is the emitter's
|
|
444
|
+
* world velocity, which pitches the loop by doppler when its sound declares `doppler`. Cheap to call every
|
|
445
|
+
* tick (~60 Hz) — the shell ramps rate/gain/cutoffs over ~20 ms to avoid zipper noise. A no-op when `id`
|
|
446
|
+
* is not a live loop (an update may race a stop) (#1051).
|
|
426
447
|
*/
|
|
427
448
|
setLoop(id: string, params: {
|
|
428
449
|
rate?: number;
|
|
429
450
|
gain?: number;
|
|
430
451
|
at?: readonly [number, number, number];
|
|
452
|
+
velocity?: readonly [number, number, number];
|
|
453
|
+
lowpass?: number;
|
|
454
|
+
highpass?: number;
|
|
431
455
|
}): void;
|
|
432
456
|
/** Stop and dispose retained loop `id` (emits `audio.loopStop`); an unknown `id` is ignored (#1051). */
|
|
433
457
|
stopLoop(id: string): void;
|
|
@@ -442,6 +466,8 @@ export interface GameContext {
|
|
|
442
466
|
raycastAll(input: SceneRaycastInput): readonly SceneRaycastHit[];
|
|
443
467
|
};
|
|
444
468
|
world: GameContextWorld;
|
|
469
|
+
/** Authored weather, shelter, accumulation and fire sampled from ctx.time. */
|
|
470
|
+
environment: AuthoredSimulationRuntime;
|
|
445
471
|
/**
|
|
446
472
|
* Deterministic `[0,1)` stream for this world — loot, AI, combat rolls, and any sim path that
|
|
447
473
|
* needs randomness. Same seed → same sequence. Never fall back to `Math.random` in systems;
|
|
@@ -555,6 +581,10 @@ export interface GameContext {
|
|
|
555
581
|
snapshot(viewer?: SnapshotViewer): WorldSnapshot;
|
|
556
582
|
/** Apply a {@link WorldSnapshot} from an authoritative host, hydrating each subsystem key present in it. */
|
|
557
583
|
hydrate(snapshot: WorldSnapshot): void;
|
|
584
|
+
/** Detached authoritative state, including save-only modules. Use for persistence, never client replication; available without `persist` or a save backend. */
|
|
585
|
+
state(): WorldSnapshot;
|
|
586
|
+
/** Restore authoritative state after initialization. Missing keys retain their initialized values, including in legacy replication-only saves. */
|
|
587
|
+
restore(state: WorldSnapshot): void;
|
|
558
588
|
/** Aggregate world-dirty version summed across replicated modules; the host replicator skips an unchanged commit. @internal */
|
|
559
589
|
replicationVersion(): number;
|
|
560
590
|
/** True when a {@link GameContextOptions.replication} policy makes {@link snapshot} viewer-dependent (private/AOI projection is active). @internal */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SaveConfig } from "./save.js";
|
|
2
|
-
import type { CommandDef, CommandScope } from "./commandRunner.js";
|
|
2
|
+
import type { CommandAccess, CommandDef, CommandScope } from "./commandRunner.js";
|
|
3
3
|
import { runCommand } from "./commandRunner.js";
|
|
4
4
|
import { type GameRuntimeSnapshot, type RuntimeChunkRow, type RuntimePlayerRow, type RuntimeProfileRow, type RuntimeServerRow } from "./snapshot.js";
|
|
5
5
|
export type ServerLoopHooks = {
|
|
@@ -69,6 +69,8 @@ export type GameRuntime = {
|
|
|
69
69
|
* and nothing else. Missing scopes load only the actor and no chunks; `{}` explicitly requests all state.
|
|
70
70
|
*/
|
|
71
71
|
commandScope: (commandName: string, input: unknown, actorUserId: string) => CommandScope | undefined;
|
|
72
|
+
/** Who may send a command, or `undefined` when the runtime has no command by that name. */
|
|
73
|
+
commandAccess: (commandName: string) => CommandAccess | undefined;
|
|
72
74
|
/** What a join has to hydrate. `undefined` means the whole world, as when `onNewPlayer` is unscoped. */
|
|
73
75
|
joinScope: (userId: string, isNew: boolean) => CommandScope | undefined;
|
|
74
76
|
/** `nowMs` is the host wall clock at the start of the tick, in ms; defaults to `Date.now()`. */
|
|
@@ -38,6 +38,10 @@ export function createGameRuntime(definition) {
|
|
|
38
38
|
commandScope(commandName, input, actorUserId) {
|
|
39
39
|
return resolveCommandScope(definition.commands, commandName, input, actorUserId);
|
|
40
40
|
},
|
|
41
|
+
commandAccess(commandName) {
|
|
42
|
+
const command = definition.commands[commandName];
|
|
43
|
+
return command === undefined ? undefined : (command.access ?? "client");
|
|
44
|
+
},
|
|
41
45
|
joinScope(userId, isNew) {
|
|
42
46
|
if (!loop?.onNewPlayer)
|
|
43
47
|
return { players: [userId], chunkKeys: [] };
|
|
@@ -2,10 +2,12 @@ import type { GameDefinition, GameLoop } from "../game/defineGame.js";
|
|
|
2
2
|
import type { PointerAxisState } from "../input/pointerAxis.js";
|
|
3
3
|
import type { ModelAssetRef } from "../scene/assetCatalog.js";
|
|
4
4
|
import { type GameContext, type GameContextContent, type GameContextModels } from "./gameContext.js";
|
|
5
|
-
import type { InputSnapshot } from "./inputSnapshot.js";
|
|
5
|
+
import type { InputFrame, InputSnapshot } from "./inputSnapshot.js";
|
|
6
6
|
import { type SimSnapshot } from "./simSnapshot.js";
|
|
7
7
|
/** One step's worth of player intent handed to {@link HeadlessRunner.step} — the held-action set and pointer state the shell would otherwise publish from the browser each frame. */
|
|
8
8
|
export interface HeadlessInput {
|
|
9
|
+
/** Discrete presses on this recorded step, including taps released before its held-state sample. */
|
|
10
|
+
presses?: InputFrame["presses"];
|
|
9
11
|
/** Replaces the held-action set for this step; edge detection (`justPressed`/`justReleased`) rolls the previous set forward. Omit to keep the last published set. */
|
|
10
12
|
held?: readonly string[];
|
|
11
13
|
/** Replaces the normalized pointer state; omit to leave the last pointer unchanged (pass `null` to clear it). */
|
|
@@ -18,15 +18,14 @@ export function createHeadlessRunner(options) {
|
|
|
18
18
|
loop?.onNewPlayer?.(ctx);
|
|
19
19
|
const simSnapshot = createContextSimSnapshot(ctx);
|
|
20
20
|
const tuning = options.playerMovement === true
|
|
21
|
-
? resolvePlayerMovementTuning({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
? { ...resolvePlayerMovementTuning({
|
|
22
|
+
physics: definition.physics,
|
|
23
|
+
world: definition.world,
|
|
24
|
+
movement: options.movement,
|
|
25
|
+
}), authoritativeStep: true }
|
|
26
26
|
: null;
|
|
27
27
|
function publishInput(input) {
|
|
28
|
-
|
|
29
|
-
ctx.input.publish(input.held);
|
|
28
|
+
ctx.input.publish(input.held ?? ctx.input.held(), { pressed: input.presses?.map(press => press.action) ?? [] });
|
|
30
29
|
if (input.pointer !== undefined)
|
|
31
30
|
ctx.input.publishPointer(input.pointer);
|
|
32
31
|
}
|
|
@@ -54,12 +53,12 @@ export function createHeadlessRunner(options) {
|
|
|
54
53
|
publishInput(input);
|
|
55
54
|
const dt = Math.min(dtSeconds, maxStep);
|
|
56
55
|
let total = 0;
|
|
57
|
-
ctx.sim.advance(dt, (stepDt) => {
|
|
58
|
-
|
|
56
|
+
ctx.sim.advance(dt, (stepDt, _tick, gameDt) => {
|
|
57
|
+
ctx.input.beginStep();
|
|
59
58
|
total += gameDt;
|
|
60
59
|
ctx.sim.runStages("beforeMovement", stepDt);
|
|
61
|
-
if (tuning !== null) {
|
|
62
|
-
stepPlayerMovement(ctx, player.userId, { held: ctx.input.held(), pointer: ctx.input.pointer() },
|
|
60
|
+
if (tuning !== null && gameDt > 0) {
|
|
61
|
+
stepPlayerMovement(ctx, player.userId, { held: ctx.input.held(), pointer: ctx.input.pointer() }, gameDt, tuning, options.heading, options.pitch);
|
|
63
62
|
}
|
|
64
63
|
ctx.sim.runStages("afterMovement", stepDt);
|
|
65
64
|
loop?.onTick?.(ctx, gameDt);
|
|
@@ -71,6 +70,7 @@ export function createHeadlessRunner(options) {
|
|
|
71
70
|
snapshot: () => simSnapshot.capture(),
|
|
72
71
|
restore(snapshot) {
|
|
73
72
|
simSnapshot.restore(snapshot);
|
|
73
|
+
ctx.input.publish([], { reset: true });
|
|
74
74
|
},
|
|
75
75
|
tick: () => ctx.sim.tick(),
|
|
76
76
|
};
|
|
@@ -21,8 +21,17 @@ export interface HostedGameRunnerOptions<TAssetRef extends ModelAssetRef, TMulti
|
|
|
21
21
|
* snapshot overlays the world state it seeded. Omit for a long-lived stateful host (ws) that keeps one runner.
|
|
22
22
|
*/
|
|
23
23
|
restore?: WorldSnapshot;
|
|
24
|
+
/** Persisted replication cursor; restored hosts continue above it. */
|
|
25
|
+
revision?: number;
|
|
24
26
|
/** Render-model lookup for collider auto-fit — pass the same lookup the shell derives from `entityModels`/`objectModels` so host and clients resolve identical hitboxes. */
|
|
25
27
|
models?: GameContextModels;
|
|
28
|
+
/** Process-local reconnect protection. Departed admission marks expire after 5 minutes or the oldest is evicted above 256 users; zero disables retention. Live members and staged replay ticks are never evicted. */
|
|
29
|
+
inputRetention?: {
|
|
30
|
+
maxDepartedUsers?: number;
|
|
31
|
+
departedTtlMs?: number;
|
|
32
|
+
/** Wall-clock milliseconds; injectable for deterministic hosts and tests. */
|
|
33
|
+
nowMs?: () => number;
|
|
34
|
+
};
|
|
26
35
|
}
|
|
27
36
|
/**
|
|
28
37
|
* The GameContext-loop equivalent of the pure-reducer `createGameHost`: one authoritative `createGameContext`
|
|
@@ -36,8 +45,18 @@ export interface HostedGameRunner {
|
|
|
36
45
|
/** Re-attach an already-joined member on a reconstructed runner — restores membership and `ctx.game.players` without re-firing `onNewPlayer`. Stateless hosts call it once per persisted member after `restore`. */
|
|
37
46
|
resume(userId: string): void;
|
|
38
47
|
leave(userId: string): void;
|
|
39
|
-
/** Record a client's latest input frame
|
|
48
|
+
/** Record a joined/resumed client's latest input frame; nonmember input is ignored. Stashed for {@link heldInput} and sampled onto `ctx.game.players` in `onTick`. */
|
|
40
49
|
input(userId: string, frame: InputFrame): void;
|
|
50
|
+
/** Counts actual retained input storage for host health checks; elapsed recorder history is excluded and staged future replay ticks remain. */
|
|
51
|
+
inputStats(): {
|
|
52
|
+
members: number;
|
|
53
|
+
recorders: number;
|
|
54
|
+
recordedFrames: number;
|
|
55
|
+
latestInputs: number;
|
|
56
|
+
wireSequences: number;
|
|
57
|
+
pressSequences: number;
|
|
58
|
+
departedUsers: number;
|
|
59
|
+
};
|
|
41
60
|
heldInput(userId: string): InputFrame | null;
|
|
42
61
|
command(userId: string, name: string, input: unknown): CommandResult<GameContext>;
|
|
43
62
|
tick(dt: number): number;
|
|
@@ -45,6 +64,10 @@ export interface HostedGameRunner {
|
|
|
45
64
|
revision(): number;
|
|
46
65
|
/** The full world baseline — projected to only what `viewer` may see when the context carries a replication policy, else the whole world. */
|
|
47
66
|
snapshot(viewer?: SnapshotViewer): WorldSnapshot;
|
|
67
|
+
/** Complete detached authoritative state for persistence, including save-only modules. */
|
|
68
|
+
state(): WorldSnapshot;
|
|
69
|
+
/** Commit replication without advancing simulation or running game ticks. */
|
|
70
|
+
commit(): number;
|
|
48
71
|
/** True when {@link snapshot} is viewer-dependent (a replication policy projects private/AOI state); a host must then serve each viewer its own frame. */
|
|
49
72
|
projectsViewers(): boolean;
|
|
50
73
|
members(): readonly string[];
|