@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,60 +1,92 @@
|
|
|
1
|
+
import { validateEmitterConfig } from "./particles.js";
|
|
1
2
|
/**
|
|
2
3
|
* Create the particle intent registry a `GameContext` exposes as `ctx.particles`.
|
|
3
4
|
* State is data-only and serializable: standing emitters survive `snapshot`/`restore`,
|
|
4
5
|
* queued bursts are transient. The renderer subscribes, drains bursts, and mirrors
|
|
5
6
|
* the emitter list — the director never allocates particle pools itself.
|
|
6
7
|
*
|
|
7
|
-
* @capability particle-director
|
|
8
|
+
* @capability particle-director named particle start/stop/burst/retune lifecycle, local/world following, render outputs, bounded cosmetic event exchange, and serializable emitter definitions
|
|
8
9
|
*/
|
|
9
|
-
export function createParticleDirector() {
|
|
10
|
+
export function createParticleDirector(options = {}) {
|
|
10
11
|
let nextSeq = 1;
|
|
11
12
|
let bursts = [];
|
|
12
13
|
const emitters = new Map();
|
|
13
14
|
const listeners = new Set();
|
|
15
|
+
let events = [];
|
|
16
|
+
const limit = (n, fallback, cap) => Number.isFinite(n) ? Math.max(0, Math.min(cap, Math.floor(n))) : fallback;
|
|
17
|
+
const maxEmitters = limit(options.maxEmitters, 128, 1024);
|
|
18
|
+
const maxBursts = limit(options.maxBursts, 32, 256);
|
|
19
|
+
const maxEvents = limit(options.maxEvents, 128, 4096);
|
|
20
|
+
function queue(config, count, blending, emitterId, attachOptions) {
|
|
21
|
+
const wanted = Number.isFinite(count) ? Math.min(65536, Math.floor(count)) : 0;
|
|
22
|
+
if (wanted <= 0 || bursts.length >= maxBursts)
|
|
23
|
+
return;
|
|
24
|
+
bursts.push({ seq: nextSeq++, config: structuredClone(config), count: wanted,
|
|
25
|
+
...(blending === undefined ? {} : { blending }),
|
|
26
|
+
...(emitterId === undefined ? {} : { emitterId }),
|
|
27
|
+
...(attachOptions === undefined ? {} : { options: structuredClone(attachOptions) }) });
|
|
28
|
+
notify();
|
|
29
|
+
}
|
|
30
|
+
function setActive(id, active) {
|
|
31
|
+
const current = emitters.get(id);
|
|
32
|
+
if (current === undefined || (current.active ?? true) === active)
|
|
33
|
+
return;
|
|
34
|
+
emitters.set(id, { ...current, active });
|
|
35
|
+
notify();
|
|
36
|
+
}
|
|
14
37
|
function notify() {
|
|
15
38
|
for (const listener of listeners)
|
|
16
39
|
listener();
|
|
17
40
|
}
|
|
18
41
|
return {
|
|
19
|
-
burst(config, count, blending) {
|
|
20
|
-
|
|
21
|
-
if (wanted <= 0)
|
|
22
|
-
return;
|
|
23
|
-
bursts.push({ seq: nextSeq++, config, count: wanted, ...(blending === undefined ? {} : { blending }) });
|
|
24
|
-
notify();
|
|
42
|
+
burst(config, count, blending, options) {
|
|
43
|
+
queue(config, count, blending, undefined, options);
|
|
25
44
|
},
|
|
26
45
|
attach(id, config, options) {
|
|
27
|
-
emitters.
|
|
28
|
-
id
|
|
29
|
-
|
|
30
|
-
...(options?.follow === undefined ? {} : { follow: options.follow }),
|
|
31
|
-
...(options?.offset === undefined ? {} : { offset: options.offset }),
|
|
32
|
-
...(options?.blending === undefined ? {} : { blending: options.blending }),
|
|
33
|
-
});
|
|
46
|
+
if (!emitters.has(id) && emitters.size >= maxEmitters)
|
|
47
|
+
throw new RangeError(`Particle emitter capacity ${maxEmitters} exceeded by ${id}`);
|
|
48
|
+
emitters.set(id, { id, config: structuredClone(config), ...structuredClone(options ?? {}) });
|
|
34
49
|
notify();
|
|
35
50
|
},
|
|
36
|
-
retune(id, patch) {
|
|
51
|
+
retune(id, patch, options) {
|
|
37
52
|
const current = emitters.get(id);
|
|
38
53
|
if (current === undefined)
|
|
39
54
|
return;
|
|
40
|
-
emitters.set(id, { ...current, config: { ...current.config, ...patch } });
|
|
55
|
+
emitters.set(id, { ...current, ...structuredClone(options ?? {}), config: { ...current.config, ...structuredClone(patch) } });
|
|
41
56
|
notify();
|
|
42
57
|
},
|
|
58
|
+
start(id) { setActive(id, true); },
|
|
59
|
+
stop(id) { setActive(id, false); },
|
|
60
|
+
burstNamed(id, count) {
|
|
61
|
+
const spec = emitters.get(id);
|
|
62
|
+
if (spec === undefined)
|
|
63
|
+
return;
|
|
64
|
+
const { id: _, config, ...options } = spec;
|
|
65
|
+
queue(config, count, spec.blending, id, options);
|
|
66
|
+
},
|
|
67
|
+
reportEvents(id, next) {
|
|
68
|
+
for (let i = 0, n = Math.min(next.length, maxEvents - events.length); i < n; i++) {
|
|
69
|
+
events.push({ ...structuredClone(next[i]), emitterId: id });
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
drainEvents() { const drained = events; events = []; return drained; },
|
|
43
73
|
detach(id) {
|
|
44
74
|
if (!emitters.delete(id))
|
|
45
75
|
return;
|
|
46
76
|
notify();
|
|
47
77
|
},
|
|
48
78
|
clear() {
|
|
49
|
-
if (emitters.size === 0 && bursts.length === 0)
|
|
79
|
+
if (emitters.size === 0 && bursts.length === 0 && events.length === 0)
|
|
50
80
|
return;
|
|
51
81
|
emitters.clear();
|
|
52
82
|
bursts = [];
|
|
83
|
+
events = [];
|
|
53
84
|
notify();
|
|
54
85
|
},
|
|
55
86
|
emitters() {
|
|
56
87
|
return Array.from(emitters.values());
|
|
57
88
|
},
|
|
89
|
+
limits() { return { maxEmitters, maxBursts, maxEvents }; },
|
|
58
90
|
drainBursts() {
|
|
59
91
|
if (bursts.length === 0)
|
|
60
92
|
return [];
|
|
@@ -67,15 +99,74 @@ export function createParticleDirector() {
|
|
|
67
99
|
return () => listeners.delete(listener);
|
|
68
100
|
},
|
|
69
101
|
snapshot() {
|
|
70
|
-
return { nextSeq, emitters: Array.from(emitters.values()) };
|
|
102
|
+
return { nextSeq, emitters: structuredClone(Array.from(emitters.values())) };
|
|
71
103
|
},
|
|
72
104
|
restore(state) {
|
|
73
|
-
nextSeq = state.nextSeq;
|
|
105
|
+
nextSeq = Math.max(1, Number.isFinite(state.nextSeq) ? Math.floor(state.nextSeq) : 1);
|
|
74
106
|
bursts = [];
|
|
75
107
|
emitters.clear();
|
|
76
|
-
|
|
77
|
-
|
|
108
|
+
events = [];
|
|
109
|
+
for (const spec of state.emitters.slice(0, maxEmitters))
|
|
110
|
+
emitters.set(spec.id, structuredClone(spec));
|
|
78
111
|
notify();
|
|
79
112
|
},
|
|
80
113
|
};
|
|
81
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Validate serializable binding, output, and bounded collision-child descriptors.
|
|
117
|
+
* @capability particle-binding-validation validate named particle follow spaces, render outputs, environment influence, and bounded collision child effects
|
|
118
|
+
*/
|
|
119
|
+
export function validateParticleAttachOptions(value) {
|
|
120
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
121
|
+
return ["particle options must be an object"];
|
|
122
|
+
const options = value;
|
|
123
|
+
const errors = [];
|
|
124
|
+
if (options.follow !== undefined && typeof options.follow !== "string")
|
|
125
|
+
errors.push("follow must be an entity id");
|
|
126
|
+
if (options.offset !== undefined && (!Array.isArray(options.offset) || options.offset.length !== 3 || options.offset.some((v) => typeof v !== "number" || !Number.isFinite(v))))
|
|
127
|
+
errors.push("offset must contain three finite numbers");
|
|
128
|
+
if (options.space !== undefined && options.space !== "world" && options.space !== "local")
|
|
129
|
+
errors.push("space must be world or local");
|
|
130
|
+
if (options.active !== undefined && typeof options.active !== "boolean")
|
|
131
|
+
errors.push("active must be boolean");
|
|
132
|
+
const output = (render, blending, prefix) => {
|
|
133
|
+
if (blending !== undefined && blending !== "normal" && blending !== "additive")
|
|
134
|
+
errors.push(`${prefix}blending must be normal or additive`);
|
|
135
|
+
if (render === undefined)
|
|
136
|
+
return;
|
|
137
|
+
if (render === null || typeof render !== "object" || Array.isArray(render)) {
|
|
138
|
+
errors.push(`${prefix}render must be an output descriptor`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const hint = render;
|
|
142
|
+
if (!["point", "streak", "flake", "flame", "smoke", "ribbon", "ripple"].includes(hint.shape))
|
|
143
|
+
errors.push(`${prefix}render.shape is unsupported`);
|
|
144
|
+
if (hint.stretch !== undefined && (typeof hint.stretch !== "number" || !Number.isFinite(hint.stretch) || hint.stretch < 0))
|
|
145
|
+
errors.push(`${prefix}render.stretch must be finite and nonnegative`);
|
|
146
|
+
};
|
|
147
|
+
output(options.render, options.blending, "");
|
|
148
|
+
if (options.environment !== undefined) {
|
|
149
|
+
const influence = options.environment;
|
|
150
|
+
if (influence === null || typeof influence !== "object" || Array.isArray(influence))
|
|
151
|
+
errors.push("environment must be an influence descriptor");
|
|
152
|
+
else {
|
|
153
|
+
for (const name of ["windResponse", "maxAcceleration"])
|
|
154
|
+
if (influence[name] !== undefined && (typeof influence[name] !== "number" || !Number.isFinite(influence[name]) || influence[name] < 0 || influence[name] > 1e12))
|
|
155
|
+
errors.push(`environment.${name} must be finite in 0..1e12`);
|
|
156
|
+
if (influence.mask !== undefined && (typeof influence.mask !== "number" || !Number.isInteger(influence.mask) || influence.mask < 0 || influence.mask > 0xffffffff))
|
|
157
|
+
errors.push("environment.mask must be an unsigned 32-bit integer");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (options.collisionEffect !== undefined) {
|
|
161
|
+
const child = options.collisionEffect;
|
|
162
|
+
if (child === null || typeof child !== "object" || Array.isArray(child))
|
|
163
|
+
errors.push("collisionEffect must be a child effect descriptor");
|
|
164
|
+
else {
|
|
165
|
+
errors.push(...validateEmitterConfig(child.config).map((error) => `collisionEffect.config.${error}`));
|
|
166
|
+
if (typeof child.count !== "number" || !Number.isInteger(child.count) || child.count < 1 || child.count > 65536)
|
|
167
|
+
errors.push("collisionEffect.count must be an integer in [1, 65536]");
|
|
168
|
+
output(child.render, child.blending, "collisionEffect.");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return errors;
|
|
172
|
+
}
|
package/dist/vfx/particles.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ForceFieldConfig } from "../physics/forceVolume.js";
|
|
1
2
|
/** A 3D vector `[x, y, z]`. */
|
|
2
3
|
export type Vec3 = readonly [number, number, number];
|
|
3
4
|
/** A `[min, max]` range a spawned particle draws uniformly from. */
|
|
@@ -10,6 +11,41 @@ export interface Curve {
|
|
|
10
11
|
start: number;
|
|
11
12
|
end: number;
|
|
12
13
|
}
|
|
14
|
+
/** Serializable spawn volume in emitter coordinates; cone widens from its base along +Y. */
|
|
15
|
+
export interface ParticleSpawnShape {
|
|
16
|
+
kind: "disc" | "sphere" | "cone";
|
|
17
|
+
radius: number;
|
|
18
|
+
height?: number;
|
|
19
|
+
/** Spawn on the outer surface instead of filling the volume. */
|
|
20
|
+
surface?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** Bounded particle contact policy; a query adapter can replace the fallback horizontal plane. */
|
|
23
|
+
export interface ParticleCollisionConfig {
|
|
24
|
+
planeY?: number;
|
|
25
|
+
response?: "kill" | "bounce";
|
|
26
|
+
restitution?: number;
|
|
27
|
+
maxQueries?: number;
|
|
28
|
+
maxEvents?: number;
|
|
29
|
+
}
|
|
30
|
+
/** One contact from an injected scene query, in simulation coordinates. */
|
|
31
|
+
export interface ParticleCollisionHit {
|
|
32
|
+
position: Vec3;
|
|
33
|
+
normal: Vec3;
|
|
34
|
+
}
|
|
35
|
+
/** Presentation events never authorize gameplay damage or physical impulses. */
|
|
36
|
+
export interface ParticleEvent {
|
|
37
|
+
type: "collision" | "death";
|
|
38
|
+
particleId: number;
|
|
39
|
+
position: Vec3;
|
|
40
|
+
velocity: Vec3;
|
|
41
|
+
normal?: Vec3;
|
|
42
|
+
}
|
|
43
|
+
/** Optional geometry adapter; queries are capped independently of rendering. */
|
|
44
|
+
export interface ParticleSystemOptions {
|
|
45
|
+
collision?: (from: Vec3, to: Vec3) => ParticleCollisionHit | null;
|
|
46
|
+
/** External cosmetic acceleration; return `out` to reuse its allocation. */
|
|
47
|
+
acceleration?: (position: Vec3, velocity: Vec3, out: [number, number, number]) => Vec3;
|
|
48
|
+
}
|
|
13
49
|
/**
|
|
14
50
|
* A particle emitter: how particles spawn and how each one evolves over its life.
|
|
15
51
|
* Every field is data — no functions — so an emitter is fully serializable and an
|
|
@@ -24,6 +60,7 @@ export interface EmitterConfig {
|
|
|
24
60
|
position?: Vec3;
|
|
25
61
|
/** Half-extents of a box the spawn point is jittered within. Default `[0, 0, 0]`. */
|
|
26
62
|
spawnJitter?: Vec3;
|
|
63
|
+
spawnShape?: ParticleSpawnShape;
|
|
27
64
|
/** Particle lifetime in seconds. Default `{ min: 1, max: 1 }`. */
|
|
28
65
|
lifetime?: Range;
|
|
29
66
|
/** Initial speed along the emit direction, in units/second. Default `{ min: 1, max: 1 }`. */
|
|
@@ -44,8 +81,13 @@ export interface EmitterConfig {
|
|
|
44
81
|
colorEnd?: number;
|
|
45
82
|
/** Opacity over life, each `0..1`. Default `{ start: 1, end: 0 }` (fade out). */
|
|
46
83
|
alpha?: Curve;
|
|
47
|
-
/**
|
|
84
|
+
/** Seed for deterministic births; retuning it resets future randomness. */
|
|
48
85
|
seed?: string | number;
|
|
86
|
+
/** Shared physical field descriptors, sampled as cosmetic acceleration; at most eight per particle. */
|
|
87
|
+
forces?: readonly ForceFieldConfig[];
|
|
88
|
+
collision?: ParticleCollisionConfig;
|
|
89
|
+
/** Enable bounded death events. Collision events use `collision.maxEvents`. */
|
|
90
|
+
deathEvents?: boolean;
|
|
49
91
|
}
|
|
50
92
|
/**
|
|
51
93
|
* Read-only packed buffers of the live particles, laid out for a renderer to
|
|
@@ -62,6 +104,11 @@ export interface ParticleBuffers {
|
|
|
62
104
|
colors: Float32Array;
|
|
63
105
|
/** `count` floats: opacity `0..1` per particle. */
|
|
64
106
|
alphas: Float32Array;
|
|
107
|
+
/** Stable birth ids, retained through pool compaction. */
|
|
108
|
+
ids: Float64Array;
|
|
109
|
+
/** Packed velocity and prior integration positions for streak/ribbon outputs. */
|
|
110
|
+
velocities: Float32Array;
|
|
111
|
+
previousPositions: Float32Array;
|
|
65
112
|
}
|
|
66
113
|
/** Serializable simulation state for save/restore and deterministic replay. */
|
|
67
114
|
export interface ParticleSnapshot {
|
|
@@ -69,6 +116,11 @@ export interface ParticleSnapshot {
|
|
|
69
116
|
accumulator: number;
|
|
70
117
|
count: number;
|
|
71
118
|
data: number[];
|
|
119
|
+
running?: boolean;
|
|
120
|
+
nextId?: number;
|
|
121
|
+
ids?: number[];
|
|
122
|
+
previousPositions?: number[];
|
|
123
|
+
config?: EmitterConfig;
|
|
72
124
|
}
|
|
73
125
|
/** A live, dt-driven particle simulation. */
|
|
74
126
|
export interface ParticleSystem {
|
|
@@ -76,14 +128,20 @@ export interface ParticleSystem {
|
|
|
76
128
|
update(dt: number): void;
|
|
77
129
|
/** Spawn `n` particles immediately (a one-shot puff/explosion), independent of `rate`. */
|
|
78
130
|
emit(n: number): void;
|
|
131
|
+
/** Resume continuous emission without replacing the live pool. */
|
|
132
|
+
start(): void;
|
|
133
|
+
/** Stop spawning while existing particles continue aging. */
|
|
134
|
+
stop(): void;
|
|
135
|
+
/** Consume bounded presentation collision/death events exactly once. */
|
|
136
|
+
drainEvents(): ParticleEvent[];
|
|
79
137
|
/** Live particle count. */
|
|
80
138
|
count(): number;
|
|
81
139
|
/** The packed buffers for rendering (valid until the next `update`/`emit`). */
|
|
82
140
|
buffers(): ParticleBuffers;
|
|
83
141
|
/** Kill all particles and reset the emission accumulator (keeps the seed stream). */
|
|
84
142
|
clear(): void;
|
|
85
|
-
/** Patch
|
|
86
|
-
configure(patch: Partial<EmitterConfig
|
|
143
|
+
/** Patch tuning in place; `replace` resets omitted fields to defaults while retaining live particles. */
|
|
144
|
+
configure(patch: Partial<EmitterConfig>, replace?: boolean): void;
|
|
87
145
|
subscribe(listener: () => void): () => void;
|
|
88
146
|
snapshot(): ParticleSnapshot;
|
|
89
147
|
restore(snapshot: ParticleSnapshot): void;
|
|
@@ -98,6 +156,11 @@ export interface ParticleSystem {
|
|
|
98
156
|
* embers, magic, or confetti. Travel/gameplay stays elsewhere; this owns only
|
|
99
157
|
* the spawn-integrate-fade lifecycle.
|
|
100
158
|
*
|
|
101
|
-
* @capability particle-system deterministic pooled particle emitter with
|
|
159
|
+
* @capability particle-system deterministic pooled particle emitter with spawn volumes, force fields, bounded collision/death events, live tuning, SoA render buffers, and serializable state
|
|
160
|
+
*/
|
|
161
|
+
export declare function createParticleSystem(config?: EmitterConfig, options?: ParticleSystemOptions): ParticleSystem;
|
|
162
|
+
/**
|
|
163
|
+
* Validate the serializable spawn/update descriptor before authoring or loading it.
|
|
164
|
+
* @capability particle-emitter-validation validate finite spawn volumes, force fields, lifetimes, appearance curves, and bounded particle collision policies
|
|
102
165
|
*/
|
|
103
|
-
export declare function
|
|
166
|
+
export declare function validateEmitterConfig(value: unknown): string[];
|