@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,144 @@
|
|
|
1
|
+
const FIELDS = [
|
|
2
|
+
{ key: "walkSpeed", label: "Walk speed", group: "movement", min: 0, step: 0.1 },
|
|
3
|
+
{ key: "gravity", label: "Gravity", group: "jump", step: 0.5 },
|
|
4
|
+
{ key: "jumpVelocity", label: "Jump velocity", group: "jump", min: 0, step: 0.1 },
|
|
5
|
+
{ key: "stepHeight", label: "Step height", group: "movement", min: 0, step: 0.05 },
|
|
6
|
+
{ key: "collisionHeight", label: "Collision height", group: "movement", min: Number.MIN_VALUE, step: 0.1 },
|
|
7
|
+
{ key: "maxClimbGrade", label: "Max climb grade", group: "movement", min: 0, step: 0.05 },
|
|
8
|
+
{ key: "groundAcceleration", label: "Ground acceleration", group: "response", min: 0, step: 1 },
|
|
9
|
+
{ key: "airAcceleration", label: "Air control", group: "response", min: 0, step: 1 },
|
|
10
|
+
{ key: "groundFriction", label: "Braking", group: "response", min: 0, step: 1 },
|
|
11
|
+
{ key: "runMultiplier", label: "Sprint multiplier", group: "movement", min: 0, step: 0.1 },
|
|
12
|
+
{ key: "crouchMultiplier", label: "Crouch multiplier", group: "movement", min: 0, step: 0.05 },
|
|
13
|
+
{ key: "jumpBufferMs", label: "Jump buffer (ms)", group: "jump-response", min: 0, step: 10 },
|
|
14
|
+
{ key: "coyoteMs", label: "Coyote time (ms)", group: "jump-response", min: 0, step: 10 },
|
|
15
|
+
{ key: "jumpCutFactor", label: "Jump release factor", group: "jump-response", min: 0, max: 1, step: 0.05 },
|
|
16
|
+
{ key: "apexGravityScale", label: "Apex gravity multiplier", group: "jump-response", min: 0, step: 0.05 },
|
|
17
|
+
{ key: "apexSpeed", label: "Apex speed threshold (m/s)", group: "jump-response", min: 0, step: 0.1 },
|
|
18
|
+
{ key: "fallGravityScale", label: "Fall gravity multiplier", group: "jump-response", min: 0, step: 0.05 },
|
|
19
|
+
{ key: "landingRecoveryMs", label: "Landing recovery (ms)", group: "jump-response", min: 0, step: 10 },
|
|
20
|
+
{ key: "landingSpeedScale", label: "Landing speed multiplier", group: "jump-response", min: 0, step: 0.05 },
|
|
21
|
+
];
|
|
22
|
+
function validValue(field, value) {
|
|
23
|
+
return typeof value === "number" && Number.isFinite(value) && (!("min" in field) || value >= field.min) && (!("max" in field) || value <= field.max);
|
|
24
|
+
}
|
|
25
|
+
function numberRequirement(field) {
|
|
26
|
+
if (field.key === "jumpCutFactor")
|
|
27
|
+
return "finite number between 0 and 1";
|
|
28
|
+
return `finite${field.key === "collisionHeight" ? " positive" : "min" in field ? " nonnegative" : ""} number`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates editor/RPC controls only for the values a game supplies, with those game's defaults.
|
|
32
|
+
* Gravity retains the game's sign convention; collision height is positive, other controls nonnegative.
|
|
33
|
+
* Jump release is bounded to 0..1; supplied deeper jump controls share a collapsed response group.
|
|
34
|
+
* @capability editor-movement author game-chosen character movement controls through ordinary catalog rows
|
|
35
|
+
*/
|
|
36
|
+
export function createMovementSchema(defaults) {
|
|
37
|
+
const fields = [];
|
|
38
|
+
for (const field of FIELDS) {
|
|
39
|
+
const value = defaults[field.key];
|
|
40
|
+
if (value === undefined)
|
|
41
|
+
continue;
|
|
42
|
+
if (!validValue(field, value))
|
|
43
|
+
throw new Error(`Invalid movement default ${field.key}: use a ${numberRequirement(field)}`);
|
|
44
|
+
fields.push({ ...field, type: "number", default: value });
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
fields,
|
|
48
|
+
groups: [
|
|
49
|
+
{ id: "movement", label: "Movement" },
|
|
50
|
+
{ id: "jump", label: "Jump" },
|
|
51
|
+
{ id: "response", label: "Response", collapsed: true },
|
|
52
|
+
{ id: "jump-response", label: "Jump response", collapsed: true },
|
|
53
|
+
].filter((group) => fields.some((field) => field.group === group.id)),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Reads one game-named row as partial runtime tuning, retaining game defaults for omitted or invalid
|
|
58
|
+
* values. Rejected values include their document path and repair; no policy callbacks are replaced.
|
|
59
|
+
* @capability editor-movement resolve saved movement controls without replacing a game's character rules
|
|
60
|
+
*/
|
|
61
|
+
export function readAuthoredMovement(document, catalogId, entryId) {
|
|
62
|
+
const catalogIndex = document.catalogs.findIndex((catalog) => catalog.id === catalogId);
|
|
63
|
+
const catalog = document.catalogs[catalogIndex];
|
|
64
|
+
const entryIndex = catalog?.entries.findIndex((entry) => entry.id === entryId) ?? -1;
|
|
65
|
+
const meta = catalog?.entries[entryIndex]?.meta;
|
|
66
|
+
const config = {};
|
|
67
|
+
const diagnostics = [];
|
|
68
|
+
if (meta === undefined)
|
|
69
|
+
return { config, diagnostics };
|
|
70
|
+
for (const field of FIELDS) {
|
|
71
|
+
const value = meta[field.key];
|
|
72
|
+
if (value === undefined)
|
|
73
|
+
continue;
|
|
74
|
+
if (!validValue(field, value)) {
|
|
75
|
+
diagnostics.push({
|
|
76
|
+
path: `catalogs[${catalogIndex}].entries[${entryIndex}].meta.${field.key}`,
|
|
77
|
+
message: `${field.key} must be a ${numberRequirement(field)}`,
|
|
78
|
+
repair: `Set ${field.key} to a valid number or remove it to inherit the game setting.`,
|
|
79
|
+
});
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (field.key === "walkSpeed")
|
|
83
|
+
config.walkSpeed = value;
|
|
84
|
+
else if (field.key === "gravity" || field.key === "jumpVelocity")
|
|
85
|
+
(config.physics ??= {})[field.key] = value;
|
|
86
|
+
else if (field.key === "stepHeight" || field.key === "collisionHeight" || field.key === "maxClimbGrade")
|
|
87
|
+
(config.movement ??= {})[field.key] = value;
|
|
88
|
+
else
|
|
89
|
+
((config.movement ??= {}).feel ??= {})[field.key] = value;
|
|
90
|
+
}
|
|
91
|
+
return { config, diagnostics };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Memoizes movement resolution by immutable document identity; reads the latest document on every
|
|
95
|
+
* call while catalog lookup and diagnostics run only after authoring changes.
|
|
96
|
+
* @capability editor-movement consume live saved movement tuning with bounded per-frame work
|
|
97
|
+
*/
|
|
98
|
+
export function createAuthoredMovementReader(getDocument, catalogId, entryId) {
|
|
99
|
+
let previous;
|
|
100
|
+
let result;
|
|
101
|
+
return () => {
|
|
102
|
+
const document = getDocument();
|
|
103
|
+
if (document !== previous) {
|
|
104
|
+
result = readAuthoredMovement(document, catalogId, entryId);
|
|
105
|
+
previous = document;
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Binds live authored numeric controls onto stable runtime objects, preserving the game's callbacks,
|
|
112
|
+
* collision, backend and other policy. The movement controller reads these getters each step.
|
|
113
|
+
* @capability editor-movement bind live character tuning while preserving distinctive game policy
|
|
114
|
+
*/
|
|
115
|
+
export function bindAuthoredMovement(read, defaults) {
|
|
116
|
+
let previous;
|
|
117
|
+
let previousBase;
|
|
118
|
+
let feel;
|
|
119
|
+
return {
|
|
120
|
+
movement: {
|
|
121
|
+
...defaults.movement,
|
|
122
|
+
get stepHeight() { return read().config.movement?.stepHeight ?? defaults.movement?.stepHeight; },
|
|
123
|
+
get collisionHeight() { return read().config.movement?.collisionHeight ?? defaults.movement?.collisionHeight; },
|
|
124
|
+
get maxClimbGrade() { return read().config.movement?.maxClimbGrade ?? defaults.movement?.maxClimbGrade; },
|
|
125
|
+
get feel() {
|
|
126
|
+
const current = read();
|
|
127
|
+
const base = defaults.movement?.feel;
|
|
128
|
+
if (current !== previous || base !== previousBase) {
|
|
129
|
+
const authored = current.config.movement?.feel;
|
|
130
|
+
feel = authored === undefined ? base : { ...base, ...authored };
|
|
131
|
+
previous = current;
|
|
132
|
+
previousBase = base;
|
|
133
|
+
}
|
|
134
|
+
return feel;
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
physics: {
|
|
138
|
+
...defaults.physics,
|
|
139
|
+
get gravity() { return read().config.physics?.gravity ?? defaults.physics?.gravity; },
|
|
140
|
+
get jumpVelocity() { return read().config.physics?.jumpVelocity ?? defaults.physics?.jumpVelocity; },
|
|
141
|
+
},
|
|
142
|
+
get diagnostics() { return read().diagnostics; },
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type ForceFieldConfig } from "../physics/forceVolume.js";
|
|
2
|
+
import type { AuthoredWeatherConfig } from "../world/authoredWeather.js";
|
|
3
|
+
import { type EmitterConfig } from "../vfx/particles.js";
|
|
4
|
+
import { type ParticleAttachOptions } from "../vfx/particleDirector.js";
|
|
5
|
+
import type { FireGridConfig } from "../world/weather.js";
|
|
6
|
+
import type { FlockConfig } from "../ai/flock.js";
|
|
7
|
+
import type { EditorDocument, EditorVec3 } from "./types.js";
|
|
8
|
+
/** A named emitter authored in metres, with game-owned appearance and attachment ids. */
|
|
9
|
+
export interface AuthoredEmitter {
|
|
10
|
+
id: string;
|
|
11
|
+
/** Spawn only while this authored fire area burns; live particles finish their lifetime. */
|
|
12
|
+
fireAreaId?: string;
|
|
13
|
+
position: EditorVec3;
|
|
14
|
+
config: EmitterConfig;
|
|
15
|
+
options?: ParticleAttachOptions;
|
|
16
|
+
}
|
|
17
|
+
/** A bounded fire grid; fuel and damage policy remain game content. */
|
|
18
|
+
export interface AuthoredFireArea {
|
|
19
|
+
id: string;
|
|
20
|
+
/** Rain cooling in heat units/second at full exposure; zero disables automatic extinguishing. */
|
|
21
|
+
extinguishRate?: number;
|
|
22
|
+
position: EditorVec3;
|
|
23
|
+
config: Omit<FireGridConfig, "fuelAt" | "origin" | "wind">;
|
|
24
|
+
fuel?: readonly number[];
|
|
25
|
+
ignitions?: readonly {
|
|
26
|
+
col: number;
|
|
27
|
+
row: number;
|
|
28
|
+
}[];
|
|
29
|
+
}
|
|
30
|
+
/** An authored habitat selects a game species and optionally follows a document path. */
|
|
31
|
+
export interface AuthoredFlockHabitat {
|
|
32
|
+
id: string;
|
|
33
|
+
species: string;
|
|
34
|
+
position: EditorVec3;
|
|
35
|
+
radius: number;
|
|
36
|
+
count: number;
|
|
37
|
+
seed: string | number;
|
|
38
|
+
role: "cosmetic" | "gameplay";
|
|
39
|
+
routeId?: string;
|
|
40
|
+
steering: FlockConfig;
|
|
41
|
+
/** Opt into authored wind/force acceleration; maxAcceleration is the game policy cap. */
|
|
42
|
+
/** Coupling in 1/second, bounded to 0..1e12. */
|
|
43
|
+
windResponse?: number;
|
|
44
|
+
/** Maximum acceleration in metres/second², bounded to 0..1e12. */
|
|
45
|
+
maxAcceleration?: number;
|
|
46
|
+
forceMask?: number;
|
|
47
|
+
}
|
|
48
|
+
/** Authored simulation inputs. Runtime particles, heat, accumulation and actors save separately. */
|
|
49
|
+
export interface EditorSimulation {
|
|
50
|
+
weather?: AuthoredWeatherConfig;
|
|
51
|
+
/** Authoritative acceleration volumes; games select target masks and caps. */
|
|
52
|
+
forces?: readonly ForceFieldConfig[];
|
|
53
|
+
emitters?: readonly AuthoredEmitter[];
|
|
54
|
+
fires?: readonly AuthoredFireArea[];
|
|
55
|
+
habitats?: readonly AuthoredFlockHabitat[];
|
|
56
|
+
}
|
|
57
|
+
/** Decode authoring inputs with located diagnostics and bounded work before any document mutation.
|
|
58
|
+
* @capability validate-simulation diagnose authored weather, effect, fire, force and habitat documents before publication
|
|
59
|
+
*/
|
|
60
|
+
export declare function decodeEditorSimulation(value: unknown, document?: Pick<EditorDocument, "paths">): EditorSimulation;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { validateForceField } from "../physics/forceVolume.js";
|
|
2
|
+
import { validateAuthoredWeather } from "../world/authoredWeather.js";
|
|
3
|
+
import { validateEmitterConfig } from "../vfx/particles.js";
|
|
4
|
+
import { validateParticleAttachOptions } from "../vfx/particleDirector.js";
|
|
5
|
+
function object(value, path) {
|
|
6
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
7
|
+
throw new Error(`${path}: expected an object`);
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
function known(value, allowed, path) {
|
|
11
|
+
for (const key of Object.keys(value))
|
|
12
|
+
if (!allowed.includes(key))
|
|
13
|
+
throw new Error(`${path}.${key}: unknown field`);
|
|
14
|
+
}
|
|
15
|
+
function finite(value, path, min = -Infinity, max = Infinity) {
|
|
16
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < min || value > max)
|
|
17
|
+
throw new Error(`${path}: expected a finite number in ${min}..${max}`);
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function text(value, path) {
|
|
21
|
+
if (typeof value !== "string" || value.trim().length === 0 || value.length > 128)
|
|
22
|
+
throw new Error(`${path}: expected a nonempty string of at most 128 characters`);
|
|
23
|
+
}
|
|
24
|
+
function vector(value, path) {
|
|
25
|
+
const point = object(value, path);
|
|
26
|
+
for (const axis of ["x", "y", "z"])
|
|
27
|
+
finite(point[axis], `${path}.${axis}`);
|
|
28
|
+
}
|
|
29
|
+
function tuple(value, path) {
|
|
30
|
+
if (!Array.isArray(value) || value.length !== 3)
|
|
31
|
+
throw new Error(`${path}: expected [x,y,z]`);
|
|
32
|
+
value.forEach((component, index) => finite(component, `${path}[${index}]`));
|
|
33
|
+
}
|
|
34
|
+
function serializable(value, path, depth = 0) {
|
|
35
|
+
if (depth > 24)
|
|
36
|
+
throw new Error(`${path}: nesting exceeds 24 levels`);
|
|
37
|
+
if (value === null || typeof value === "string" || typeof value === "boolean" || value === undefined)
|
|
38
|
+
return;
|
|
39
|
+
if (typeof value === "number") {
|
|
40
|
+
finite(value, path);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value !== "object")
|
|
44
|
+
throw new Error(`${path}: expected serializable data`);
|
|
45
|
+
for (const [key, item] of Object.entries(value))
|
|
46
|
+
serializable(item, `${path}.${key}`, depth + 1);
|
|
47
|
+
}
|
|
48
|
+
function rows(value, path, limit, check) {
|
|
49
|
+
if (value === undefined)
|
|
50
|
+
return;
|
|
51
|
+
if (!Array.isArray(value) || value.length > limit)
|
|
52
|
+
throw new Error(`${path}: expected at most ${limit} entries`);
|
|
53
|
+
const ids = new Set();
|
|
54
|
+
value.forEach((entry, index) => {
|
|
55
|
+
const location = `${path}[${index}]`;
|
|
56
|
+
const row = object(entry, location);
|
|
57
|
+
text(row.id, `${location}.id`);
|
|
58
|
+
if (ids.has(row.id))
|
|
59
|
+
throw new Error(`${location}.id: duplicate id ${row.id}`);
|
|
60
|
+
ids.add(row.id);
|
|
61
|
+
vector(row.position, `${location}.position`);
|
|
62
|
+
check(row, location);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Decode authoring inputs with located diagnostics and bounded work before any document mutation.
|
|
66
|
+
* @capability validate-simulation diagnose authored weather, effect, fire, force and habitat documents before publication
|
|
67
|
+
*/
|
|
68
|
+
export function decodeEditorSimulation(value, document) {
|
|
69
|
+
const source = object(value, "$.simulation");
|
|
70
|
+
known(source, ["weather", "forces", "emitters", "fires", "habitats"], "$.simulation");
|
|
71
|
+
serializable(source, "$.simulation");
|
|
72
|
+
if (source.weather !== undefined) {
|
|
73
|
+
object(source.weather, "$.simulation.weather");
|
|
74
|
+
try {
|
|
75
|
+
validateAuthoredWeather(source.weather);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
throw new Error(`$.simulation.weather: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (source.forces !== undefined) {
|
|
82
|
+
if (!Array.isArray(source.forces) || source.forces.length > 128)
|
|
83
|
+
throw new Error("$.simulation.forces: expected at most 128 fields");
|
|
84
|
+
source.forces.forEach((value, index) => {
|
|
85
|
+
try {
|
|
86
|
+
validateForceField(value);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
throw new Error(`$.simulation.forces[${index}]: ${error instanceof Error ? error.message : String(error)}`);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
rows(source.emitters, "$.simulation.emitters", 128, (row, path) => {
|
|
94
|
+
known(row, ["id", "position", "config", "options", "fireAreaId"], path);
|
|
95
|
+
if (row.fireAreaId !== undefined) {
|
|
96
|
+
text(row.fireAreaId, `${path}.fireAreaId`);
|
|
97
|
+
if (!Array.isArray(source.fires) || !source.fires.some((area) => area !== null && typeof area === "object" && area.id === row.fireAreaId))
|
|
98
|
+
throw new Error(`${path}.fireAreaId: missing fire area ${row.fireAreaId}`);
|
|
99
|
+
}
|
|
100
|
+
const diagnostics = validateEmitterConfig(row.config);
|
|
101
|
+
if (diagnostics.length)
|
|
102
|
+
throw new Error(`${path}.config: ${diagnostics.join("; ")}`);
|
|
103
|
+
if (row.options !== undefined) {
|
|
104
|
+
const errors = validateParticleAttachOptions(row.options);
|
|
105
|
+
if (errors.length)
|
|
106
|
+
throw new Error(`${path}.options: ${errors.join("; ")}`);
|
|
107
|
+
}
|
|
108
|
+
const config = object(row.config, `${path}.config`);
|
|
109
|
+
for (const key of ["max", "rate", "spread", "drag"])
|
|
110
|
+
if (config[key] !== undefined)
|
|
111
|
+
finite(config[key], `${path}.config.${key}`, 0, key === "max" ? 100_000 : key === "drag" ? 1 : Infinity);
|
|
112
|
+
if (config.max !== undefined && !Number.isInteger(config.max))
|
|
113
|
+
throw new Error(`${path}.config.max: expected an integer`);
|
|
114
|
+
for (const key of ["position", "spawnJitter", "direction", "gravity"])
|
|
115
|
+
if (config[key] !== undefined)
|
|
116
|
+
tuple(config[key], `${path}.config.${key}`);
|
|
117
|
+
for (const key of ["lifetime", "speed"])
|
|
118
|
+
if (config[key] !== undefined) {
|
|
119
|
+
const range = object(config[key], `${path}.config.${key}`);
|
|
120
|
+
finite(range.min, `${path}.config.${key}.min`, 0);
|
|
121
|
+
finite(range.max, `${path}.config.${key}.max`, range.min);
|
|
122
|
+
}
|
|
123
|
+
if (row.options !== undefined) {
|
|
124
|
+
const options = object(row.options, `${path}.options`);
|
|
125
|
+
if (options.space !== undefined && options.space !== "local" && options.space !== "world")
|
|
126
|
+
throw new Error(`${path}.options.space: expected local or world`);
|
|
127
|
+
if (options.active !== undefined && typeof options.active !== "boolean")
|
|
128
|
+
throw new Error(`${path}.options.active: expected boolean`);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
rows(source.fires, "$.simulation.fires", 32, (row, path) => {
|
|
132
|
+
known(row, ["id", "position", "config", "fuel", "ignitions", "extinguishRate"], path);
|
|
133
|
+
if (row.extinguishRate !== undefined)
|
|
134
|
+
finite(row.extinguishRate, `${path}.extinguishRate`, 0);
|
|
135
|
+
const config = object(row.config, `${path}.config`);
|
|
136
|
+
known(config, ["cols", "rows", "cellSize", "ignitionThreshold", "spreadRate", "burnRate", "windBias", "maxCells"], `${path}.config`);
|
|
137
|
+
const cols = finite(config.cols, `${path}.config.cols`, 1, 256);
|
|
138
|
+
const count = cols * finite(config.rows, `${path}.config.rows`, 1, 256);
|
|
139
|
+
if (!Number.isInteger(cols) || !Number.isInteger(config.rows) || count > 16_384)
|
|
140
|
+
throw new Error(`${path}.config: expected integral dimensions with at most 16384 cells`);
|
|
141
|
+
finite(config.cellSize, `${path}.config.cellSize`, Number.EPSILON);
|
|
142
|
+
if (config.maxCells !== undefined) {
|
|
143
|
+
const capacity = finite(config.maxCells, `${path}.config.maxCells`, count, 1_048_576);
|
|
144
|
+
if (!Number.isInteger(capacity))
|
|
145
|
+
throw new Error(`${path}.config.maxCells: expected an integer`);
|
|
146
|
+
}
|
|
147
|
+
for (const key of ["ignitionThreshold", "spreadRate", "burnRate", "windBias"])
|
|
148
|
+
if (config[key] !== undefined)
|
|
149
|
+
finite(config[key], `${path}.config.${key}`, 0, key === "windBias" ? 1 : Infinity);
|
|
150
|
+
if (row.fuel !== undefined) {
|
|
151
|
+
if (!Array.isArray(row.fuel) || row.fuel.length !== count)
|
|
152
|
+
throw new Error(`${path}.fuel: expected ${count} cell values`);
|
|
153
|
+
row.fuel.forEach((fuel, index) => finite(fuel, `${path}.fuel[${index}]`, 0, 1));
|
|
154
|
+
}
|
|
155
|
+
if (row.ignitions !== undefined) {
|
|
156
|
+
if (!Array.isArray(row.ignitions) || row.ignitions.length > count)
|
|
157
|
+
throw new Error(`${path}.ignitions: expected at most ${count} cells`);
|
|
158
|
+
for (const cell of row.ignitions) {
|
|
159
|
+
const point = object(cell, `${path}.ignitions`);
|
|
160
|
+
known(point, ["col", "row"], `${path}.ignitions`);
|
|
161
|
+
finite(point.col, `${path}.ignitions.col`, 0, cols - 1);
|
|
162
|
+
finite(point.row, `${path}.ignitions.row`, 0, config.rows - 1);
|
|
163
|
+
if (!Number.isInteger(point.col) || !Number.isInteger(point.row))
|
|
164
|
+
throw new Error(`${path}.ignitions: expected integral cell coordinates`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
rows(source.habitats, "$.simulation.habitats", 32, (row, path) => {
|
|
169
|
+
known(row, ["id", "species", "position", "radius", "count", "seed", "role", "routeId", "steering", "windResponse", "maxAcceleration", "forceMask"], path);
|
|
170
|
+
text(row.species, `${path}.species`);
|
|
171
|
+
finite(row.radius, `${path}.radius`, Number.EPSILON);
|
|
172
|
+
const count = finite(row.count, `${path}.count`, 0, 256);
|
|
173
|
+
if (!Number.isInteger(count))
|
|
174
|
+
throw new Error(`${path}.count: expected an integer`);
|
|
175
|
+
if (row.role !== "cosmetic" && row.role !== "gameplay")
|
|
176
|
+
throw new Error(`${path}.role: expected cosmetic or gameplay`);
|
|
177
|
+
if (typeof row.seed !== "string" && typeof row.seed !== "number")
|
|
178
|
+
throw new Error(`${path}.seed: expected string or number`);
|
|
179
|
+
if (typeof row.seed === "number")
|
|
180
|
+
finite(row.seed, `${path}.seed`);
|
|
181
|
+
for (const key of ["windResponse", "maxAcceleration"])
|
|
182
|
+
if (row[key] !== undefined)
|
|
183
|
+
finite(row[key], `${path}.${key}`, 0, 1e12);
|
|
184
|
+
if (row.forceMask !== undefined) {
|
|
185
|
+
finite(row.forceMask, `${path}.forceMask`, 0, 0xffffffff);
|
|
186
|
+
if (!Number.isInteger(row.forceMask))
|
|
187
|
+
throw new Error(`${path}.forceMask: expected a uint32`);
|
|
188
|
+
}
|
|
189
|
+
const steering = object(row.steering, `${path}.steering`);
|
|
190
|
+
known(steering, ["maxSpeed", "accel", "separationRadius", "separationWeight", "neighborRadius", "cohesionWeight", "alignmentWeight", "seekWeight", "stragglerRadius", "stragglerBoost"], `${path}.steering`);
|
|
191
|
+
for (const key of ["maxSpeed", "separationRadius", "neighborRadius"])
|
|
192
|
+
finite(steering[key], `${path}.steering.${key}`, Number.EPSILON);
|
|
193
|
+
for (const [key, item] of Object.entries(steering))
|
|
194
|
+
finite(item, `${path}.steering.${key}`, 0);
|
|
195
|
+
if (row.routeId !== undefined) {
|
|
196
|
+
text(row.routeId, `${path}.routeId`);
|
|
197
|
+
if (document !== undefined && !document.paths.some((route) => route.id === row.routeId && route.points.length > 1))
|
|
198
|
+
throw new Error(`${path}.routeId: missing route ${row.routeId} with at least two points`);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
return structuredClone(source);
|
|
202
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** A solid or clearance volume in the prefab's unnormalized local asset space. */
|
|
2
|
+
export interface StaticPrefabBox {
|
|
3
|
+
min: readonly [number, number, number];
|
|
4
|
+
max: readonly [number, number, number];
|
|
5
|
+
}
|
|
6
|
+
/** Persisted static export settings; source parts remain editable in the prefab fragment. */
|
|
7
|
+
export interface StaticPrefabBake {
|
|
8
|
+
assetId: string;
|
|
9
|
+
/** Omit for a conservative closed exterior. Explicit boxes preserve authored openings. */
|
|
10
|
+
collisionBoxes?: readonly StaticPrefabBox[];
|
|
11
|
+
/** Required empty spaces, checked against movement solids before export. */
|
|
12
|
+
clearances?: readonly (StaticPrefabBox & {
|
|
13
|
+
id: string;
|
|
14
|
+
})[];
|
|
15
|
+
}
|
|
16
|
+
/** Validate and copy static prefab export settings from document or RPC input. */
|
|
17
|
+
export declare function parseStaticPrefabBake(value: unknown): StaticPrefabBake;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Validate and copy static prefab export settings from document or RPC input. */
|
|
2
|
+
export function parseStaticPrefabBake(value) {
|
|
3
|
+
const record = (input) => {
|
|
4
|
+
if (input === null || typeof input !== "object" || Array.isArray(input))
|
|
5
|
+
throw new Error("expected an object");
|
|
6
|
+
return input;
|
|
7
|
+
};
|
|
8
|
+
const input = record(value);
|
|
9
|
+
for (const key of Object.keys(input)) {
|
|
10
|
+
if (!["assetId", "collisionBoxes", "clearances"].includes(key))
|
|
11
|
+
throw new Error(`unknown static bake field: ${key}`);
|
|
12
|
+
}
|
|
13
|
+
if (typeof input.assetId !== "string" || input.assetId.trim().length === 0)
|
|
14
|
+
throw new Error("assetId: expected a nonempty string");
|
|
15
|
+
const boxes = (items, clearance) => {
|
|
16
|
+
if (!Array.isArray(items) || items.length === 0 || items.length > 4096)
|
|
17
|
+
throw new Error("expected 1..4096 boxes");
|
|
18
|
+
const ids = new Set();
|
|
19
|
+
return items.map((item, index) => {
|
|
20
|
+
const box = record(item);
|
|
21
|
+
for (const key of Object.keys(box)) {
|
|
22
|
+
if (!["min", "max", ...(clearance ? ["id"] : [])].includes(key))
|
|
23
|
+
throw new Error(`box ${index}: unknown field ${key}`);
|
|
24
|
+
}
|
|
25
|
+
for (const key of ["min", "max"]) {
|
|
26
|
+
const vector = box[key];
|
|
27
|
+
if (!Array.isArray(vector) || vector.length !== 3 || !vector.every((axis) => typeof axis === "number" && Number.isFinite(axis)))
|
|
28
|
+
throw new Error(`box ${index}.${key}: expected three finite numbers`);
|
|
29
|
+
}
|
|
30
|
+
const min = box.min;
|
|
31
|
+
const max = box.max;
|
|
32
|
+
if (!min.every((axis, i) => axis < max[i]))
|
|
33
|
+
throw new Error(`box ${index}: min must be less than max on every axis`);
|
|
34
|
+
if (clearance) {
|
|
35
|
+
if (typeof box.id !== "string" || box.id.trim().length === 0 || ids.has(box.id))
|
|
36
|
+
throw new Error(`clearance ${index}: expected a unique nonempty id`);
|
|
37
|
+
ids.add(box.id);
|
|
38
|
+
}
|
|
39
|
+
return { min: [...min], max: [...max], ...(clearance ? { id: box.id } : {}) };
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
const collisionBoxes = input.collisionBoxes === undefined ? undefined : boxes(input.collisionBoxes, false);
|
|
43
|
+
const clearances = input.clearances === undefined ? undefined : boxes(input.clearances, true);
|
|
44
|
+
if (clearances !== undefined && collisionBoxes === undefined)
|
|
45
|
+
throw new Error("clearances require explicit collisionBoxes");
|
|
46
|
+
for (const clearance of clearances ?? []) {
|
|
47
|
+
if (collisionBoxes.some((solid) => solid.min.every((axis, i) => axis < clearance.max[i] && solid.max[i] > clearance.min[i])))
|
|
48
|
+
throw new Error(`clearance ${clearance.id} intersects a collision box`);
|
|
49
|
+
}
|
|
50
|
+
return { assetId: input.assetId, ...(collisionBoxes === undefined ? {} : { collisionBoxes }), ...(clearances === undefined ? {} : { clearances }) };
|
|
51
|
+
}
|
package/dist/editor/types.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { EditorSimulation } from "./simulation.js";
|
|
2
|
+
import type { MaterialAsset } from "../material/materialAsset.js";
|
|
3
|
+
import type { StaticPrefabBake } from "./staticPrefab.js";
|
|
1
4
|
import type { ParamSchema } from "../scene/sceneKinds.js";
|
|
2
5
|
import type { EditorUiDocument } from "../ui/hudDocument.js";
|
|
3
6
|
import type { MinimapBakeBounds } from "../world/minimapBake.js";
|
|
@@ -100,6 +103,8 @@ export interface EditorNote {
|
|
|
100
103
|
export type EditorTerrain = TerraformSnapshot;
|
|
101
104
|
/** The four placeable-object collections a prefab fragment or clipboard fragment carries. */
|
|
102
105
|
export interface EditorFragmentContent {
|
|
106
|
+
/** Referenced reusable materials travel with prefab and clipboard content. */
|
|
107
|
+
materialAssets?: MaterialAsset[];
|
|
103
108
|
markers: readonly EditorMarker[];
|
|
104
109
|
volumes: readonly EditorVolume[];
|
|
105
110
|
paths: readonly EditorPath[];
|
|
@@ -115,6 +120,8 @@ export interface EditorPrefab {
|
|
|
115
120
|
id: string;
|
|
116
121
|
name: string;
|
|
117
122
|
fragment: EditorFragmentContent;
|
|
123
|
+
/** Offline export settings; world instances remain authored markers. */
|
|
124
|
+
staticBake?: StaticPrefabBake;
|
|
118
125
|
}
|
|
119
126
|
/**
|
|
120
127
|
* A named, persisted list of object ids — a selection bookmark (restore, add-to) that can also
|
|
@@ -219,11 +226,19 @@ export interface EditorEnvironment {
|
|
|
219
226
|
zenithColor?: string;
|
|
220
227
|
sunIntensity?: number;
|
|
221
228
|
ambientIntensity?: number;
|
|
229
|
+
/** Sun compass bearing in degrees (0 = -Z, 90 = +X); runtime `SkySunConfig.azimuth`. */
|
|
230
|
+
sunAzimuth?: number;
|
|
231
|
+
/** Sun height above the horizon in degrees; runtime `SkySunConfig.elevation`. */
|
|
232
|
+
sunElevation?: number;
|
|
222
233
|
fog?: EditorFogConfig;
|
|
223
234
|
}
|
|
224
235
|
/** The full authored scene: every marker, volume, path, note, and sculpted terrain for a game. */
|
|
225
236
|
export interface EditorDocument {
|
|
237
|
+
/** Reusable material library; per-marker named assignments live in meta.materialAssignments. */
|
|
238
|
+
materialAssets?: MaterialAsset[];
|
|
226
239
|
version: 1;
|
|
240
|
+
/** Authored weather, emitter, fire and habitat inputs; runtime state saves separately. */
|
|
241
|
+
simulation?: EditorSimulation;
|
|
227
242
|
markers: EditorMarker[];
|
|
228
243
|
volumes: EditorVolume[];
|
|
229
244
|
paths: EditorPath[];
|
|
@@ -17,6 +17,10 @@ export interface ReputationLedgerConfig {
|
|
|
17
17
|
min?: number;
|
|
18
18
|
max?: number;
|
|
19
19
|
}
|
|
20
|
+
/** Plain JSON standings of a {@link ReputationLedger}: stored values only, `initial` stays config. */
|
|
21
|
+
export interface ReputationLedgerState {
|
|
22
|
+
records: Record<string, Record<string, number>>;
|
|
23
|
+
}
|
|
20
24
|
export interface ReputationLedger {
|
|
21
25
|
standing(actorId: string, factionId: string): number;
|
|
22
26
|
hasStanding(actorId: string, factionId: string): boolean;
|
|
@@ -26,6 +30,8 @@ export interface ReputationLedger {
|
|
|
26
30
|
relation(actorId: string, factionId: string): FactionRelation;
|
|
27
31
|
standings(actorId: string): Record<string, number>;
|
|
28
32
|
reset(actorId: string, factionId?: string): void;
|
|
33
|
+
snapshot(): ReputationLedgerState;
|
|
34
|
+
restore(next: ReputationLedgerState): void;
|
|
29
35
|
}
|
|
30
36
|
export declare function createReputationLedger(config?: ReputationLedgerConfig): ReputationLedger;
|
|
31
37
|
export interface EffectiveRelationInput {
|
|
@@ -77,6 +77,17 @@ export function createReputationLedger(config = {}) {
|
|
|
77
77
|
else
|
|
78
78
|
records.get(actorId)?.delete(factionId);
|
|
79
79
|
},
|
|
80
|
+
snapshot() {
|
|
81
|
+
const out = {};
|
|
82
|
+
for (const [actorId, row] of records)
|
|
83
|
+
out[actorId] = Object.fromEntries(row);
|
|
84
|
+
return { records: out };
|
|
85
|
+
},
|
|
86
|
+
restore(next) {
|
|
87
|
+
records.clear();
|
|
88
|
+
for (const [actorId, row] of Object.entries(next.records))
|
|
89
|
+
records.set(actorId, new Map(Object.entries(row)));
|
|
90
|
+
},
|
|
80
91
|
};
|
|
81
92
|
}
|
|
82
93
|
export function effectiveRelation(input) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { WeaponHandlingFrame } from "../combat/weaponHandling.js";
|
|
2
|
+
import type { WeaponPresentationTuning } from "../combat/weaponPresentation.js";
|
|
1
3
|
export interface CameraFollowState {
|
|
2
4
|
entityId: string;
|
|
3
5
|
target: {
|
|
@@ -12,6 +14,11 @@ export interface CameraFollowState {
|
|
|
12
14
|
};
|
|
13
15
|
distance: number;
|
|
14
16
|
}
|
|
17
|
+
/** What a camera rig reads about the held weapon each frame; see `GameCameraConfig.weapon`. */
|
|
18
|
+
export interface CameraWeaponView {
|
|
19
|
+
handling: WeaponHandlingFrame;
|
|
20
|
+
presentation?: WeaponPresentationTuning;
|
|
21
|
+
}
|
|
15
22
|
export interface FirstPersonCameraConfig {
|
|
16
23
|
/** Camera height above the followed entity's origin. Default 1.6. */
|
|
17
24
|
eyeHeight?: number;
|
|
@@ -162,6 +169,8 @@ export interface LockOnCameraConfig {
|
|
|
162
169
|
export type ChaseView = "chase" | "cockpit" | "hood" | "rear";
|
|
163
170
|
/** Speed-reactive vehicle chase rig (#27) — speed→FOV, spring arm, procedural shake, interior views. */
|
|
164
171
|
export interface ChaseCameraConfig {
|
|
172
|
+
/** Heading the chase boom follows: target body (default), or independent input/aim yaw for camera-relative walking. Seat views always follow the body. */
|
|
173
|
+
headingSource?: "body" | "input";
|
|
165
174
|
/** Boom distance behind the vehicle. Default 6. */
|
|
166
175
|
distance?: number;
|
|
167
176
|
/** Camera height above the follow point. Default 2.6. */
|
|
@@ -170,11 +179,47 @@ export interface ChaseCameraConfig {
|
|
|
170
179
|
lookHeight?: number;
|
|
171
180
|
/** Spring-arm damping (higher = stiffer, less lag). Default 6. */
|
|
172
181
|
springDamping?: number;
|
|
173
|
-
/**
|
|
182
|
+
/**
|
|
183
|
+
* Speed→FOV curve: FOV lerps from `base` to `max` as speed climbs to `speedForMax`. Speed is the
|
|
184
|
+
* followed entity's sim `velocity` when it publishes one. `response` (default 6) eases the FOV
|
|
185
|
+
* toward the curve with `1-exp(-response*dt)`; `Infinity` applies it unsmoothed.
|
|
186
|
+
*/
|
|
174
187
|
fov?: {
|
|
175
188
|
base?: number;
|
|
176
189
|
max?: number;
|
|
177
190
|
speedForMax?: number;
|
|
191
|
+
response?: number;
|
|
192
|
+
};
|
|
193
|
+
/** Pull the boom back by up to `extra` world units as speed climbs to `speedForMax` (default the FOV curve's `speedForMax`). */
|
|
194
|
+
distanceBySpeed?: {
|
|
195
|
+
extra: number;
|
|
196
|
+
speedForMax?: number;
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Tilt the boom with the target's body pitch (`rotationX`) so the camera stays behind it on
|
|
200
|
+
* slopes and ramps. `blend` (default 1) is the fraction of the pitch followed, clamped to `max`
|
|
201
|
+
* radians (default 0.6) and eased by `response` (default 5). Omit the block to keep a level boom.
|
|
202
|
+
*/
|
|
203
|
+
pitchFollow?: {
|
|
204
|
+
blend?: number;
|
|
205
|
+
max?: number;
|
|
206
|
+
response?: number;
|
|
207
|
+
};
|
|
208
|
+
/** How `ctx.camera.kickFov(degrees)` impulses feel: the kick decays at `decay` per second (default 7) and the summed kick is clamped to `max` degrees (default 20). */
|
|
209
|
+
fovKick?: {
|
|
210
|
+
decay?: number;
|
|
211
|
+
max?: number;
|
|
212
|
+
};
|
|
213
|
+
/** Input action that, while held, swings the chase view to look back at what is behind the target. */
|
|
214
|
+
lookBackAction?: string;
|
|
215
|
+
/**
|
|
216
|
+
* Pull the boom in past scene colliders, objects, terrain and walls via `ctx.scene.raycast`
|
|
217
|
+
* (entities never block). `radius` (default 0.3) keeps the lens off the surface; `minDistance`
|
|
218
|
+
* (default 0.8) is the closest the camera gets to the look pivot. Default on; `false` disables.
|
|
219
|
+
*/
|
|
220
|
+
collision?: boolean | {
|
|
221
|
+
radius?: number;
|
|
222
|
+
minDistance?: number;
|
|
178
223
|
};
|
|
179
224
|
/** Procedural shake amplitude per unit of speed (adds to the trauma channel). Default 0.0. */
|
|
180
225
|
shakePerSpeed?: number;
|
|
@@ -434,7 +479,7 @@ export interface GameCameraConfig {
|
|
|
434
479
|
};
|
|
435
480
|
initialDistance?: number;
|
|
436
481
|
initialHeight?: number;
|
|
437
|
-
/** Initial horizontal boom facing (radians): 0 = camera behind on -Z, PI = camera on +Z. Seeds
|
|
482
|
+
/** Initial horizontal boom facing (radians): 0 = camera behind on -Z, PI = camera on +Z. Seeds orbit and input-owned chase once; unset keeps orbit's -Z placement or seeds chase from its first acquired target's heading. */
|
|
438
483
|
initialYaw?: number;
|
|
439
484
|
/** Initial boom elevation (radians): 0 = level, positive = camera above the target looking down. Seeds the orbit rig; unset derives elevation from `initialHeight`/`initialDistance`. */
|
|
440
485
|
initialPitch?: number;
|
|
@@ -446,6 +491,13 @@ export interface GameCameraConfig {
|
|
|
446
491
|
followEntityId?: string | null;
|
|
447
492
|
/** Fired each frame after cameraFollow lock is applied. */
|
|
448
493
|
onCameraFollow?: (state: CameraFollowState) => void;
|
|
494
|
+
/**
|
|
495
|
+
* The followed entity's held weapon, read every frame by the `first` and `shoulder` rigs. Return the
|
|
496
|
+
* weapon's `createWeaponHandling` frame and its presentation tuning, or `null` for none. First person
|
|
497
|
+
* poses the viewmodel from it (offset, ADS pose, viewmodel FOV, sway, bob, kick) and adds the recoil to
|
|
498
|
+
* the look; over-the-shoulder takes its ADS blend from `adsProgress` and adds the same recoil.
|
|
499
|
+
*/
|
|
500
|
+
weapon?: (entityId: string) => CameraWeaponView | null;
|
|
449
501
|
rotateSpeed?: number;
|
|
450
502
|
zoomSpeed?: number;
|
|
451
503
|
dampingFactor?: number;
|