@jgengine/core 0.10.0 → 0.11.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 +13 -2
- package/LICENSE +203 -661
- package/README.md +1 -1
- package/dist/ai/crowd.d.ts +55 -0
- package/dist/ai/crowd.js +114 -0
- package/dist/ai/flock.d.ts +5 -2
- package/dist/ai/flock.js +5 -2
- package/dist/ai/groupAssist.d.ts +1 -0
- package/dist/ai/groupAssist.js +1 -0
- package/dist/ai/heatSystem.d.ts +76 -0
- package/dist/ai/heatSystem.js +97 -0
- package/dist/ai/interestScheduler.d.ts +107 -0
- package/dist/ai/interestScheduler.js +118 -0
- package/dist/ai/jobBoard.d.ts +1 -0
- package/dist/ai/jobBoard.js +1 -0
- package/dist/ai/laneSelect.d.ts +5 -2
- package/dist/ai/laneSelect.js +5 -2
- package/dist/ai/mobBrain.d.ts +1 -0
- package/dist/ai/mobBrain.js +1 -0
- package/dist/ai/spawnDirector.d.ts +7 -2
- package/dist/ai/spawnDirector.js +18 -15
- package/dist/ai/spawnPoint.d.ts +3 -1
- package/dist/ai/spawnPoint.js +3 -1
- package/dist/ai/targetAcquisition.d.ts +84 -0
- package/dist/ai/targetAcquisition.js +92 -0
- package/dist/anim/easing.d.ts +19 -0
- package/dist/anim/easing.js +19 -0
- package/dist/anim/oscillator.d.ts +12 -0
- package/dist/anim/oscillator.js +25 -0
- package/dist/area/areaEffectField.d.ts +127 -0
- package/dist/area/areaEffectField.js +175 -0
- package/dist/area/stackPolicy.d.ts +47 -0
- package/dist/area/stackPolicy.js +99 -0
- package/dist/authoring.d.ts +1 -0
- package/dist/authoring.js +1 -0
- package/dist/board/laneBoard.d.ts +13 -0
- package/dist/board/laneBoard.js +13 -0
- package/dist/board/timelineBoard.d.ts +10 -0
- package/dist/board/timelineBoard.js +10 -0
- package/dist/combat/abilityKit.d.ts +18 -0
- package/dist/combat/abilityKit.js +24 -9
- package/dist/combat/animationState.d.ts +5 -0
- package/dist/combat/animationState.js +5 -0
- package/dist/combat/breakMeters.d.ts +10 -0
- package/dist/combat/breakMeters.js +10 -0
- package/dist/combat/castRunner.d.ts +5 -0
- package/dist/combat/castRunner.js +5 -0
- package/dist/combat/comboPoints.d.ts +5 -0
- package/dist/combat/comboPoints.js +5 -0
- package/dist/combat/comboString.d.ts +5 -0
- package/dist/combat/comboString.js +5 -0
- package/dist/combat/damageInterceptors.d.ts +192 -0
- package/dist/combat/damageInterceptors.js +251 -0
- package/dist/combat/damageMatchup.d.ts +68 -0
- package/dist/combat/damageMatchup.js +42 -0
- package/dist/combat/damageResolution.d.ts +63 -0
- package/dist/combat/damageResolution.js +50 -0
- package/dist/combat/death.d.ts +7 -1
- package/dist/combat/death.js +5 -0
- package/dist/combat/deathReason.d.ts +14 -0
- package/dist/combat/deathReason.js +1 -0
- package/dist/combat/defensiveWindow.d.ts +5 -0
- package/dist/combat/defensiveWindow.js +5 -0
- package/dist/combat/dotField.d.ts +5 -1
- package/dist/combat/dotField.js +5 -1
- package/dist/combat/downed.d.ts +5 -0
- package/dist/combat/downed.js +5 -0
- package/dist/combat/effects.d.ts +5 -0
- package/dist/combat/effects.js +5 -0
- package/dist/combat/encounterSequence.d.ts +258 -0
- package/dist/combat/encounterSequence.js +426 -0
- package/dist/combat/hitReaction.d.ts +57 -1
- package/dist/combat/hitReaction.js +45 -5
- package/dist/combat/magazine.d.ts +54 -0
- package/dist/combat/magazine.js +96 -0
- package/dist/combat/projectiles.d.ts +5 -0
- package/dist/combat/projectiles.js +32 -14
- package/dist/combat/receivedDamage.d.ts +117 -0
- package/dist/combat/receivedDamage.js +92 -0
- package/dist/combat/regenShield.d.ts +5 -1
- package/dist/combat/regenShield.js +5 -1
- package/dist/combat/renderCues.d.ts +51 -0
- package/dist/combat/renderCues.js +44 -0
- package/dist/combat/resourcePool.d.ts +5 -0
- package/dist/combat/resourcePool.js +5 -0
- package/dist/combat/rewardAllocation.d.ts +121 -0
- package/dist/combat/rewardAllocation.js +251 -0
- package/dist/combat/shotOrigin.d.ts +18 -1
- package/dist/combat/shotOrigin.js +39 -0
- package/dist/combat/statusApplication.d.ts +96 -0
- package/dist/combat/statusApplication.js +81 -0
- package/dist/combat/telegraph.d.ts +11 -2
- package/dist/combat/telegraph.js +11 -2
- package/dist/combat/thresholdCrossings.d.ts +85 -0
- package/dist/combat/thresholdCrossings.js +116 -0
- package/dist/combat.d.ts +34 -0
- package/dist/combat.js +34 -0
- package/dist/commands/commandRegistry.d.ts +11 -0
- package/dist/commands/commandRegistry.js +9 -2
- package/dist/crafting/crop.d.ts +18 -0
- package/dist/crafting/crop.js +18 -0
- package/dist/crafting/production.d.ts +5 -0
- package/dist/crafting/production.js +5 -0
- package/dist/devtools/rewriteTunables.d.ts +17 -0
- package/dist/devtools/rewriteTunables.js +276 -0
- package/dist/devtools/saveEndpoint.d.ts +46 -0
- package/dist/devtools/saveEndpoint.js +48 -0
- package/dist/devtools/transformTunables.d.ts +3 -0
- package/dist/devtools/transformTunables.js +3 -0
- package/dist/devtools/tunableSchema.d.ts +31 -0
- package/dist/devtools/tunableSchema.js +31 -0
- package/dist/economy/currency.d.ts +12 -1
- package/dist/economy/currency.js +12 -1
- package/dist/economy/listingBook.d.ts +91 -0
- package/dist/economy/listingBook.js +144 -0
- package/dist/economy/resourceLedger.d.ts +222 -0
- package/dist/economy/resourceLedger.js +353 -0
- package/dist/economy/sharedWallet.d.ts +14 -0
- package/dist/economy/sharedWallet.js +14 -0
- package/dist/economy/techTree.d.ts +12 -0
- package/dist/economy/techTree.js +12 -0
- package/dist/economy/wallet.d.ts +26 -2
- package/dist/economy/wallet.js +35 -6
- package/dist/editor/commands.d.ts +190 -7
- package/dist/editor/commands.js +727 -55
- package/dist/editor/document.d.ts +159 -13
- package/dist/editor/document.js +750 -19
- package/dist/editor/grid.d.ts +229 -0
- package/dist/editor/grid.js +366 -0
- package/dist/editor/gridAdapters.d.ts +80 -0
- package/dist/editor/gridAdapters.js +134 -0
- package/dist/editor/index.d.ts +73 -6
- package/dist/editor/index.js +6 -2
- package/dist/editor/liveSync.d.ts +127 -0
- package/dist/editor/liveSync.js +249 -0
- package/dist/editor/runtimeInspector.d.ts +88 -0
- package/dist/editor/runtimeInspector.js +239 -0
- package/dist/editor/types.d.ts +107 -1
- package/dist/faction/reputation.d.ts +5 -0
- package/dist/faction/reputation.js +5 -0
- package/dist/format/distance.d.ts +17 -0
- package/dist/format/distance.js +18 -0
- package/dist/format/duration.d.ts +27 -0
- package/dist/format/duration.js +45 -0
- package/dist/format/speed.d.ts +17 -0
- package/dist/format/speed.js +25 -0
- package/dist/game/connectedPlayers.d.ts +6 -4
- package/dist/game/connectedPlayers.js +2 -2
- package/dist/game/cosmetics.d.ts +9 -0
- package/dist/game/cosmetics.js +16 -0
- package/dist/game/defineGame.d.ts +100 -2
- package/dist/game/defineGame.js +39 -2
- package/dist/game/defineSystem.d.ts +88 -0
- package/dist/game/defineSystem.js +45 -0
- package/dist/game/dialogue.d.ts +33 -0
- package/dist/game/dialogue.js +21 -0
- package/dist/game/events.d.ts +35 -13
- package/dist/game/events.js +5 -0
- package/dist/game/feed.d.ts +33 -0
- package/dist/game/feed.js +35 -2
- package/dist/game/gamePhase.d.ts +11 -0
- package/dist/game/gamePhase.js +16 -0
- package/dist/game/leaderboard.d.ts +5 -0
- package/dist/game/leaderboard.js +5 -0
- package/dist/game/loadout.d.ts +5 -0
- package/dist/game/loadout.js +5 -0
- package/dist/game/lootFilter.d.ts +2 -0
- package/dist/game/lootFilter.js +2 -0
- package/dist/game/lootPipeline.d.ts +164 -0
- package/dist/game/lootPipeline.js +216 -0
- package/dist/game/lootTable.d.ts +19 -2
- package/dist/game/lootTable.js +15 -5
- package/dist/game/objectives.d.ts +43 -0
- package/dist/game/objectives.js +36 -0
- package/dist/game/paramLayers.d.ts +148 -0
- package/dist/game/paramLayers.js +185 -0
- package/dist/game/ping.d.ts +5 -0
- package/dist/game/ping.js +5 -0
- package/dist/game/playableGame.d.ts +92 -7
- package/dist/game/quest.d.ts +8 -0
- package/dist/game/quest.js +29 -0
- package/dist/game/race.d.ts +141 -0
- package/dist/game/race.js +169 -0
- package/dist/game/recordBook.d.ts +3 -1
- package/dist/game/recordBook.js +4 -1
- package/dist/game/ruleSelection.d.ts +75 -0
- package/dist/game/ruleSelection.js +127 -0
- package/dist/game/runDraft.d.ts +5 -0
- package/dist/game/runDraft.js +5 -0
- package/dist/game/saveStore.d.ts +111 -0
- package/dist/game/saveStore.js +312 -0
- package/dist/game/snapshotHistory.d.ts +67 -0
- package/dist/game/snapshotHistory.js +95 -0
- package/dist/game/social.d.ts +38 -1
- package/dist/game/social.js +71 -0
- package/dist/game/spawnPoints.d.ts +5 -0
- package/dist/game/spawnPoints.js +5 -0
- package/dist/game/systemRuntime.d.ts +50 -0
- package/dist/game/systemRuntime.js +157 -0
- package/dist/game/systemSchedule.d.ts +72 -0
- package/dist/game/systemSchedule.js +242 -0
- package/dist/game/toasts.d.ts +43 -0
- package/dist/game/toasts.js +44 -0
- package/dist/game/trade.d.ts +10 -0
- package/dist/game/trade.js +10 -0
- package/dist/game/unlocks.d.ts +8 -0
- package/dist/game/unlocks.js +16 -0
- package/dist/game/vfxInstance.d.ts +138 -0
- package/dist/game/vfxInstance.js +96 -0
- package/dist/game/worldItem.d.ts +17 -4
- package/dist/game/worldItem.js +17 -4
- package/dist/gameplay.d.ts +91 -0
- package/dist/gameplay.js +91 -0
- package/dist/index.d.ts +5 -14
- package/dist/index.js +5 -14
- package/dist/input/actionBindings.d.ts +23 -6
- package/dist/input/actionBindings.js +23 -6
- package/dist/input/axisInput.d.ts +7 -2
- package/dist/input/axisInput.js +7 -2
- package/dist/input/controlGroups.d.ts +67 -0
- package/dist/input/controlGroups.js +41 -0
- package/dist/input/touchScheme.d.ts +82 -6
- package/dist/input/touchScheme.js +123 -10
- package/dist/interaction/lockpick.d.ts +61 -0
- package/dist/interaction/lockpick.js +168 -0
- package/dist/interaction/proximityPrompt.d.ts +2 -0
- package/dist/interaction/proximityPrompt.js +2 -0
- package/dist/interaction/qte.d.ts +5 -0
- package/dist/interaction/qte.js +5 -0
- package/dist/interaction/skillCheck.d.ts +5 -0
- package/dist/interaction/skillCheck.js +5 -0
- package/dist/inventory/inventoryModel.d.ts +12 -0
- package/dist/inventory/inventoryModel.js +12 -0
- package/dist/inventory/shapedGrid.d.ts +16 -0
- package/dist/inventory/shapedGrid.js +16 -0
- package/dist/inventory/slotModel.d.ts +10 -0
- package/dist/inventory/slotModel.js +10 -0
- package/dist/item/durability.d.ts +5 -0
- package/dist/item/durability.js +5 -0
- package/dist/item/itemIdentity.d.ts +171 -0
- package/dist/item/itemIdentity.js +138 -0
- package/dist/item/itemInstanceRegistry.d.ts +32 -0
- package/dist/item/itemInstanceRegistry.js +34 -0
- package/dist/item/itemgen.d.ts +163 -0
- package/dist/item/itemgen.js +151 -0
- package/dist/item/modularItem.d.ts +5 -0
- package/dist/item/modularItem.js +5 -0
- package/dist/item/use.d.ts +5 -0
- package/dist/item/use.js +5 -0
- package/dist/item/useBehavior.d.ts +142 -0
- package/dist/item/useBehavior.js +105 -0
- package/dist/item/weapon.d.ts +5 -0
- package/dist/item/weapon.js +5 -0
- package/dist/math/scalar.d.ts +25 -0
- package/dist/math/scalar.js +47 -0
- package/dist/meta/changelog.d.ts +4 -1
- package/dist/meta/changelog.js +16 -2
- package/dist/movement/avoidance.d.ts +45 -0
- package/dist/movement/avoidance.js +123 -0
- package/dist/movement/cameraRig.d.ts +8 -0
- package/dist/movement/cameraRig.js +8 -0
- package/dist/movement/dash.d.ts +11 -0
- package/dist/movement/dash.js +11 -0
- package/dist/movement/formation.d.ts +135 -0
- package/dist/movement/formation.js +194 -0
- package/dist/movement/glideModel.d.ts +5 -0
- package/dist/movement/glideModel.js +5 -0
- package/dist/movement/grappleSwing.d.ts +5 -0
- package/dist/movement/grappleSwing.js +5 -0
- package/dist/movement/leaderTrail.d.ts +5 -0
- package/dist/movement/leaderTrail.js +5 -0
- package/dist/movement/movementModel.d.ts +35 -8
- package/dist/movement/movementModel.js +42 -14
- package/dist/movement/playerMovement.js +65 -10
- package/dist/movement/poseState.d.ts +13 -0
- package/dist/movement/poseState.js +16 -0
- package/dist/movement/steering.d.ts +8 -0
- package/dist/movement/steering.js +18 -0
- package/dist/multiplayer/combatSnapshot.d.ts +3 -0
- package/dist/multiplayer/combatSnapshot.js +3 -0
- package/dist/multiplayer/lagCompensation.d.ts +5 -0
- package/dist/multiplayer/lagCompensation.js +5 -0
- package/dist/multiplayer/presenceModel.d.ts +9 -1
- package/dist/multiplayer/presenceModel.js +9 -1
- package/dist/multiplayer/simultaneousCommit.d.ts +3 -0
- package/dist/multiplayer/simultaneousCommit.js +3 -0
- package/dist/multiplayer.d.ts +11 -0
- package/dist/multiplayer.js +11 -0
- package/dist/nav/pathFollow.d.ts +57 -0
- package/dist/nav/pathFollow.js +109 -0
- package/dist/orders/orderKinds.d.ts +156 -0
- package/dist/orders/orderKinds.js +197 -0
- package/dist/orders/orderQueue.d.ts +208 -0
- package/dist/orders/orderQueue.js +211 -0
- package/dist/physics/drivableVehicle.d.ts +29 -0
- package/dist/physics/drivableVehicle.js +16 -0
- package/dist/physics/kinematicVehicle.d.ts +21 -1
- package/dist/physics/kinematicVehicle.js +23 -8
- package/dist/physics/physicsWorld.d.ts +24 -12
- package/dist/physics/physicsWorld.js +11 -5
- package/dist/physics/radialImpulse.d.ts +25 -0
- package/dist/physics/radialImpulse.js +27 -0
- package/dist/procedural.d.ts +3 -0
- package/dist/procedural.js +3 -0
- package/dist/progression/statGraph.d.ts +116 -0
- package/dist/progression/statGraph.js +318 -0
- package/dist/puzzle/cellGrid.d.ts +9 -0
- package/dist/puzzle/cellGrid.js +9 -0
- package/dist/puzzle/fallingPiece.d.ts +9 -0
- package/dist/puzzle/fallingPiece.js +9 -0
- package/dist/puzzle/nonogram.d.ts +39 -0
- package/dist/puzzle/nonogram.js +136 -0
- package/dist/random/nameGen.d.ts +5 -0
- package/dist/random/nameGen.js +5 -0
- package/dist/random/pick.d.ts +8 -0
- package/dist/random/pick.js +31 -0
- package/dist/random/rng.d.ts +22 -0
- package/dist/random/rng.js +28 -8
- package/dist/random/seedLink.d.ts +4 -0
- package/dist/random/seedLink.js +4 -0
- package/dist/relation/keyedValues.d.ts +54 -0
- package/dist/relation/keyedValues.js +115 -0
- package/dist/relation/thresholds.d.ts +62 -0
- package/dist/relation/thresholds.js +62 -0
- package/dist/render/color.d.ts +12 -0
- package/dist/render/color.js +26 -0
- package/dist/render/lookPreset.d.ts +41 -0
- package/dist/render/lookPreset.js +29 -0
- package/dist/render/postProcessing.d.ts +20 -0
- package/dist/render/postProcessing.js +21 -1
- package/dist/rules/predicate.d.ts +53 -0
- package/dist/rules/predicate.js +64 -0
- package/dist/rules/ruleEffects.d.ts +38 -0
- package/dist/rules/ruleEffects.js +37 -0
- package/dist/rules/triggeredRules.d.ts +160 -0
- package/dist/rules/triggeredRules.js +241 -0
- package/dist/runtime/adapter.d.ts +42 -3
- package/dist/runtime/adapter.js +44 -1
- package/dist/runtime/cameraDirector.d.ts +1 -0
- package/dist/runtime/cameraDirector.js +1 -0
- package/dist/runtime/commandRunner.d.ts +1 -0
- package/dist/runtime/commandRunner.js +1 -0
- package/dist/runtime/context/combatFx.d.ts +23 -0
- package/dist/runtime/context/combatFx.js +161 -0
- package/dist/runtime/context/registries.d.ts +13 -0
- package/dist/runtime/context/registries.js +84 -0
- package/dist/runtime/context/worldItems.d.ts +21 -0
- package/dist/runtime/context/worldItems.js +37 -0
- package/dist/runtime/descriptors/baseline.d.ts +48 -0
- package/dist/runtime/descriptors/baseline.js +134 -0
- package/dist/runtime/descriptors/features.d.ts +74 -0
- package/dist/runtime/descriptors/features.js +281 -0
- package/dist/runtime/gameContext.d.ts +114 -13
- package/dist/runtime/gameContext.js +303 -395
- package/dist/runtime/gameRuntime.d.ts +1 -0
- package/dist/runtime/gameRuntime.js +1 -0
- package/dist/runtime/headlessRunner.d.ts +60 -0
- package/dist/runtime/headlessRunner.js +44 -0
- package/dist/runtime/hostPersistence.d.ts +12 -0
- package/dist/runtime/hostPersistence.js +12 -0
- package/dist/runtime/hostedGameRunner.d.ts +8 -3
- package/dist/runtime/hostedGameRunner.js +26 -3
- package/dist/runtime/hostedWorldSession.d.ts +12 -4
- package/dist/runtime/hostedWorldSession.js +11 -4
- package/dist/runtime/inputSnapshot.d.ts +8 -3
- package/dist/runtime/inputSnapshot.js +6 -2
- package/dist/runtime/motionIntents.d.ts +11 -2
- package/dist/runtime/motionIntents.js +16 -2
- package/dist/runtime/perContext.d.ts +15 -0
- package/dist/runtime/perContext.js +21 -0
- package/dist/runtime/runtimeSave.d.ts +79 -0
- package/dist/runtime/runtimeSave.js +105 -0
- package/dist/runtime/save.d.ts +4 -0
- package/dist/runtime/save.js +4 -0
- package/dist/runtime/snapshot.d.ts +7 -0
- package/dist/runtime/snapshot.js +7 -0
- package/dist/runtime/visibility.d.ts +2 -1
- package/dist/runtime/visibility.js +2 -1
- package/dist/runtime/worldChannel.d.ts +7 -3
- package/dist/runtime/worldChannel.js +51 -6
- package/dist/runtime/worldMirror.d.ts +14 -2
- package/dist/runtime/worldMirror.js +15 -2
- package/dist/runtime/worldProjection.d.ts +47 -0
- package/dist/runtime/worldProjection.js +52 -0
- package/dist/runtime/worldReplication.d.ts +30 -7
- package/dist/runtime/worldReplication.js +41 -9
- package/dist/runtime/worldSnapshot.d.ts +30 -3
- package/dist/runtime/worldSnapshot.js +20 -5
- package/dist/scene/assetCatalog.d.ts +21 -0
- package/dist/scene/assetCatalog.js +4 -0
- package/dist/scene/assetDiagnostics.d.ts +69 -0
- package/dist/scene/assetDiagnostics.js +110 -0
- package/dist/scene/assetGenerator.d.ts +65 -0
- package/dist/scene/assetGenerator.js +64 -0
- package/dist/scene/assetSpace.d.ts +137 -0
- package/dist/scene/assetSpace.js +259 -0
- package/dist/scene/authoredTriggers.d.ts +169 -0
- package/dist/scene/authoredTriggers.js +340 -0
- package/dist/scene/behaviorRuntime.d.ts +75 -0
- package/dist/scene/behaviorRuntime.js +217 -0
- package/dist/scene/behaviors.d.ts +14 -2
- package/dist/scene/behaviors.js +7 -2
- package/dist/scene/bodyBind.d.ts +60 -0
- package/dist/scene/bodyBind.js +51 -0
- package/dist/scene/builtinSceneKinds.d.ts +5 -0
- package/dist/scene/builtinSceneKinds.js +62 -0
- package/dist/scene/colliders.d.ts +13 -1
- package/dist/scene/colliders.js +37 -1
- package/dist/scene/entityStats.d.ts +11 -2
- package/dist/scene/entityStats.js +11 -2
- package/dist/scene/entityStore.d.ts +12 -1
- package/dist/scene/entityStore.js +15 -0
- package/dist/scene/facing.d.ts +18 -0
- package/dist/scene/facing.js +26 -0
- package/dist/scene/modelSockets.d.ts +33 -0
- package/dist/scene/modelSockets.js +29 -0
- package/dist/scene/objectQuery.d.ts +2 -0
- package/dist/scene/objectQuery.js +2 -0
- package/dist/scene/objectStore.d.ts +4 -0
- package/dist/scene/objectStore.js +6 -0
- package/dist/scene/possession.d.ts +8 -0
- package/dist/scene/possession.js +14 -0
- package/dist/scene/roster.d.ts +3 -0
- package/dist/scene/roster.js +12 -0
- package/dist/scene/sceneKinds.d.ts +240 -0
- package/dist/scene/sceneKinds.js +214 -0
- package/dist/scene/sceneOwnership.d.ts +169 -0
- package/dist/scene/sceneOwnership.js +179 -0
- package/dist/scene/selectionBookmarks.d.ts +93 -0
- package/dist/scene/selectionBookmarks.js +122 -0
- package/dist/scene/vehicleSeat.d.ts +68 -0
- package/dist/scene/vehicleSeat.js +59 -0
- package/dist/sensor/concealment.d.ts +3 -0
- package/dist/sensor/concealment.js +3 -0
- package/dist/sensor/freezeMonitor.d.ts +1 -0
- package/dist/sensor/freezeMonitor.js +1 -0
- package/dist/sensor/frustumSensor.d.ts +7 -3
- package/dist/sensor/frustumSensor.js +7 -3
- package/dist/sensor/hiddenStateProbe.d.ts +5 -2
- package/dist/sensor/hiddenStateProbe.js +5 -2
- package/dist/sensor/recordingBuffer.d.ts +2 -1
- package/dist/sensor/recordingBuffer.js +2 -1
- package/dist/sensor/replayLoop.d.ts +6 -2
- package/dist/sensor/replayLoop.js +6 -2
- package/dist/sensor/revealQuery.d.ts +2 -1
- package/dist/sensor/revealQuery.js +2 -1
- package/dist/sensor/visionCone.d.ts +10 -3
- package/dist/sensor/visionCone.js +10 -3
- package/dist/session/contestedChannel.d.ts +1 -0
- package/dist/session/contestedChannel.js +1 -0
- package/dist/session/extraction.d.ts +1 -0
- package/dist/session/extraction.js +1 -0
- package/dist/session/roles.d.ts +6 -0
- package/dist/session/roles.js +6 -0
- package/dist/session/roundState.d.ts +8 -0
- package/dist/session/roundState.js +2 -0
- package/dist/settings/settingsModel.d.ts +1 -0
- package/dist/settings/settingsModel.js +1 -0
- package/dist/stats/accumulatorMeter.d.ts +6 -0
- package/dist/stats/accumulatorMeter.js +6 -0
- package/dist/stats/eventMeter.d.ts +5 -0
- package/dist/stats/eventMeter.js +5 -0
- package/dist/stats/rollCheck.d.ts +5 -0
- package/dist/stats/rollCheck.js +5 -0
- package/dist/stats/statModifiers.d.ts +5 -0
- package/dist/stats/statModifiers.js +5 -0
- package/dist/store/defineKeyedStore.d.ts +38 -0
- package/dist/store/defineKeyedStore.js +42 -0
- package/dist/store/defineStore.d.ts +33 -0
- package/dist/store/defineStore.js +35 -0
- package/dist/store/observableKeyedStore.d.ts +5 -0
- package/dist/store/observableKeyedStore.js +34 -2
- package/dist/survival/decayMeter.d.ts +55 -0
- package/dist/survival/decayMeter.js +130 -55
- package/dist/survival/moodle.d.ts +2 -0
- package/dist/survival/regionHealth.d.ts +6 -0
- package/dist/survival/regionHealth.js +5 -0
- package/dist/tactics/fallingGrid.d.ts +4 -2
- package/dist/tactics/fallingGrid.js +4 -2
- package/dist/tactics/predictiveQuery.d.ts +3 -0
- package/dist/tactics/predictiveQuery.js +3 -0
- package/dist/tactics/snapshot.d.ts +2 -0
- package/dist/tactics/snapshot.js +2 -0
- package/dist/tactics/surface.d.ts +1 -0
- package/dist/tactics/surface.js +1 -0
- package/dist/tactics/tacticalGrid.d.ts +1 -0
- package/dist/tactics/tacticalGrid.js +1 -0
- package/dist/time/beatClock.d.ts +14 -4
- package/dist/time/beatClock.js +14 -4
- package/dist/time/calendarClock.d.ts +3 -0
- package/dist/time/calendarClock.js +3 -0
- package/dist/time/idleProgress.d.ts +4 -0
- package/dist/time/idleProgress.js +4 -0
- package/dist/time/simClock.d.ts +2 -0
- package/dist/time/simClock.js +20 -5
- package/dist/ui/actionModel.d.ts +110 -0
- package/dist/ui/actionModel.js +159 -0
- package/dist/ui/gameLayout.d.ts +30 -11
- package/dist/ui/gameLayout.js +30 -11
- package/dist/ui/hudDocument.d.ts +114 -0
- package/dist/ui/hudDocument.js +220 -0
- package/dist/ui/hudLayout.d.ts +33 -1
- package/dist/ui/hudLayout.js +204 -17
- package/dist/ui/selectionModel.d.ts +92 -0
- package/dist/ui/selectionModel.js +88 -0
- package/dist/ui/tooltipModel.d.ts +63 -0
- package/dist/ui/tooltipModel.js +82 -0
- package/dist/ui.d.ts +15 -0
- package/dist/ui.js +15 -0
- package/dist/visibility/bounds.d.ts +8 -1
- package/dist/visibility/bounds.js +8 -1
- package/dist/visibility/frustum.d.ts +12 -3
- package/dist/visibility/frustum.js +12 -3
- package/dist/work/jobQueue.d.ts +201 -0
- package/dist/work/jobQueue.js +253 -0
- package/dist/work/unitTraining.d.ts +58 -0
- package/dist/work/unitTraining.js +52 -0
- package/dist/world/authoredObjects.d.ts +58 -0
- package/dist/world/authoredObjects.js +69 -0
- package/dist/world/buildingGenerator.d.ts +18 -0
- package/dist/world/buildingGenerator.js +62 -0
- package/dist/world/buildings.d.ts +5 -0
- package/dist/world/buildings.js +5 -0
- package/dist/world/catenary.d.ts +22 -0
- package/dist/world/catenary.js +69 -0
- package/dist/world/features.d.ts +115 -7
- package/dist/world/features.js +56 -5
- package/dist/world/footprintGrid.d.ts +59 -0
- package/dist/world/footprintGrid.js +141 -0
- package/dist/world/geometry.d.ts +64 -0
- package/dist/world/geometry.js +94 -0
- package/dist/world/grassKind.d.ts +51 -0
- package/dist/world/grassKind.js +101 -0
- package/dist/world/pathInstances.d.ts +27 -0
- package/dist/world/pathInstances.js +27 -0
- package/dist/world/pathTerrain.d.ts +80 -0
- package/dist/world/pathTerrain.js +142 -0
- package/dist/world/placeAsset.d.ts +81 -0
- package/dist/world/placeAsset.js +77 -0
- package/dist/world/placement.d.ts +4 -0
- package/dist/world/placement.js +4 -0
- package/dist/world/placementController.d.ts +6 -0
- package/dist/world/placementController.js +6 -0
- package/dist/world/polyline.d.ts +34 -0
- package/dist/world/polyline.js +81 -0
- package/dist/world/roads.d.ts +8 -0
- package/dist/world/roads.js +38 -0
- package/dist/world/scatterRegion.d.ts +164 -0
- package/dist/world/scatterRegion.js +409 -0
- package/dist/world/sceneShapes.d.ts +43 -0
- package/dist/world/sceneShapes.js +1 -0
- package/dist/world/segment.d.ts +27 -0
- package/dist/world/segment.js +43 -0
- package/dist/world/soilKind.d.ts +48 -0
- package/dist/world/soilKind.js +92 -0
- package/dist/world/spatialSample.d.ts +241 -0
- package/dist/world/spatialSample.js +465 -0
- package/dist/world/terraform.d.ts +213 -1
- package/dist/world/terraform.js +655 -20
- package/dist/world/terrain.d.ts +119 -20
- package/dist/world/terrain.js +247 -41
- package/dist/world/terrainGuides.d.ts +182 -0
- package/dist/world/terrainGuides.js +343 -0
- package/dist/world/vec2.d.ts +81 -0
- package/dist/world/vec2.js +128 -0
- package/dist/world/vec3.d.ts +49 -0
- package/dist/world/vec3.js +76 -0
- package/dist/world/vegetation.d.ts +71 -0
- package/dist/world/vegetation.js +151 -0
- package/dist/world/visibilityField.d.ts +154 -0
- package/dist/world/visibilityField.js +271 -0
- package/dist/world/volumetricClouds.d.ts +52 -0
- package/dist/world/volumetricClouds.js +43 -0
- package/dist/world/walls.d.ts +9 -0
- package/dist/world/walls.js +9 -0
- package/dist/world/waterKind.d.ts +55 -0
- package/dist/world/waterKind.js +99 -0
- package/dist/world.d.ts +132 -0
- package/dist/world.js +132 -0
- package/package.json +32 -8
- package/skills/jgengine/SKILL.md +51 -0
- package/skills/jgengine/api.md +434 -0
- package/skills/jgengine/capabilities.md +29 -0
- package/skills/jgengine/reference-cartridge.md +72 -0
- package/skills/jgengine-combat/SKILL.md +34 -0
- package/skills/jgengine-combat/api.md +549 -0
- package/skills/jgengine-combat/capabilities.md +169 -0
- package/skills/jgengine-combat/reference.md +98 -0
- package/skills/jgengine-gameplay/SKILL.md +39 -0
- package/skills/jgengine-gameplay/api.md +1794 -0
- package/skills/jgengine-gameplay/capabilities.md +378 -0
- package/skills/jgengine-gameplay/reference-systems.md +60 -0
- package/skills/jgengine-world/SKILL.md +38 -0
- package/skills/jgengine-world/api.md +2652 -0
- package/skills/jgengine-world/capabilities.md +370 -0
- package/skills/jgengine-world/reference.md +222 -0
- package/dist/cartridge/runtime.d.ts +0 -15
- package/dist/cartridge/runtime.js +0 -556
- package/dist/cartridge/spec.d.ts +0 -188
- package/dist/cartridge/spec.js +0 -19
- package/dist/cartridge/validate.d.ts +0 -2
- package/dist/cartridge/validate.js +0 -78
- package/dist/store/engineStore.d.ts +0 -22
- package/dist/store/engineStore.js +0 -50
- package/dist/store/features.d.ts +0 -10
- package/dist/store/features.js +0 -23
- package/llms.txt +0 -5389
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# jgengine domain API — World features
|
|
2
|
+
|
|
3
|
+
Reference module for the [`jgengine-world` API](SKILL.md) skill. Load this when you need the renderer-free world surface.
|
|
4
|
+
|
|
5
|
+
## World features
|
|
6
|
+
|
|
7
|
+
Descriptors from `@jgengine/core/world/features` — config data the runner/world layer interprets:
|
|
8
|
+
|
|
9
|
+
| Feature | Use |
|
|
10
|
+
|---------|-----|
|
|
11
|
+
| `biomes({ map, zones, bounds? })` | Region atmosphere/rules layering; zones reference biome ids |
|
|
12
|
+
| `voxel({ seed, generate?, streaming? })` | Block worlds |
|
|
13
|
+
| `plots(config)` | Shared city + instanced interiors |
|
|
14
|
+
| `tilemap({ map })` | 2D/2.5D levels |
|
|
15
|
+
| `flat()` | Plain arena |
|
|
16
|
+
| `environment({ terrain, sky, weather, vegetation, water, structures, pads })` | Composable outdoor scene — terrain + sky/time-of-day + rain/snow + grass + ocean + buildings + ground pads. Each field takes the matching descriptor: `terrain()`, `sky()`, `rain()`/`snow()`, `grass()`, `ocean()`, `building()`, `pad()`. `building()`, `ocean()`, `rain()`/`snow()`, and `grass()` take `position: [x, z]` — sugar for `area.position` — to site a cluster/water body/vegetation patch away from the origin (several settlements, an offset lake, a distant meadow), and a biome-banded world places grass by zone this way; building clusters ground-snap to the terrain field per building. **`rain()`/`snow()` are the exception:** their `area.position`/`position` are accepted for API symmetry but have **no visual effect** — `RainField`/`SnowField` always follow the camera and never apply the volume's transform, so `area.w/d/h` instead sizes a camera-anchored wrap-volume that must stay near camera scale (roughly 50-120 per axis) or the storm goes sub-pixel-invisible at district scale; dial `density`/`width` (rain)/`flakeSize` (snow)/`opacity` for a heavier storm instead of a bigger area; each `pad()` (a flat platform/paved patch — `{ center, size: [w,d] | { radius }, height?, color?, rotationY? }`) implicitly flattens the terrain beneath it via a `TerrainFlattenMask`, so a building pad or spawn circle never fights the noise field underneath. `grass({ density })` is **blades/m²** (default 4, matching `vegetation`'s density): blade count scales with patch **area**, capped at a per-field perf budget (`budget`/buffer `count`) — a devtools warning fires when a large patch is clamped, so raising density on a big field adds blades instead of silently no-opping against a fixed budget |
|
|
17
|
+
|
|
18
|
+
`biomes`/`voxel`/`plots`/`tilemap` share a `WorldGridConfig` (`cells?: WorldGridCell[]`, `cellSize?`, `baseHeight?`, `defaultColor?`) — a `WorldGridCell` is `{ x, z, height?, color? }`, one extruded box per cell. `resolveGridInstances(config)` (`@jgengine/core/world/gridInstances`) is the pure cell→instance math (position, scale, color per cell); the shell renders the result as a single `THREE.InstancedMesh` **automatically whenever `PlayableGame.environment` is unset and `game.world` is one of these four grid kinds** — no manual render wiring for a cell-based world, same auto-render convention as `environment()` worlds.
|
|
19
|
+
|
|
20
|
+
`terrain()`'s `material` (a named palette — `"grass" | "sand" | "snow" | "rock" | "ash"`, resolved via `resolveTerrainPalette`/`TERRAIN_MATERIAL_PALETTES` in `world/terrain`) sets the default low/high/waterline colors; `colors: { low?, high?, waterline? }` overrides any of them field-by-field, and `segments` tunes the render mesh's subdivision. `biomeBands: BiomeBand[]` (`{ z, fade?, material?, colors? }`) cross-fades the **ground palette along the world's z axis** — ordered zones (vale → marsh → peaks) that blend across a `fade`-wide window (default 64) centered on each boundary; it's the linear counterpart to the radial `materialRegions` (which paint on top; each region is a `TerrainCircleRegion` (default), `TerrainPolylineRegion` (roads/rivers), or `TerrainRectRegion` (rotatable districts), sharing a `TerrainRegionStyle`). Pure/testable via `createTerrainPaletteSampler`/`createBiomeBandSampler` in `world/terrain`; the shell renders it through the same per-vertex `paletteAt` seam as regions, no extra wiring. (Per-zone **fog/sky** cross-fade isn't wired yet — three.js fog is scene-global — so bands drive ground color today.) `flatten: TerrainFlattenMask[]` (`{ center, radius, height?, falloff? }`) carves explicit flat circles into the noise field independent of pads — building foundations, spawn circles, roads — blending back to the noise height over `falloff` (default `radius * 0.5`). `detail: TerrainDetailConfig` (`{ rockColor?, sandColor?, snowColor?, rockSlopeStart?, snowHeight?, waterLevel?, detailScale?, macroScale?, roughness?, strength? }`) swaps the flat vertex-colour ground for a procedural noise shader (`@jgengine/shell`'s `createTerrainDetailMaterial`, defaults via `resolveTerrainDetail`): it keeps the biome-tinted base and blends noise-broken **rock by slope**, **sand by waterline**, and **snow by height** over it — textured-reading terrain with no image assets, full PBR (lit/shadowed/fogged), composing with the post-processing chain. Omit for the flat look. `detail.material: { maps, repeat?, strength? }` (`TerrainDetailMaterialConfig`) is the real-texture seam: `maps` is `buildMaterialCatalog({ basePath }).resolve("material/grass")!.maps` from `@jgengine/assets` (or any URLs with the same `{ color, normal, roughness, ao, displacement }` shape) — `resolveTerrainDetail` fills `repeat` (world units per tile, default 4) and `strength` (blend 0..1, default 1), and the shell's `createTerrainDetailMaterial(detail, textures)` tiles the loaded color/roughness/ao/normal by world position and blends them over the same procedural rock/sand/snow result, never replacing it. The shell resolves the map URLs to `THREE.Texture`s itself (`useTexture`/`useLoader`, e.g. in the game's `environment` component) — `core` only ever sees the URL strings. Detail noise is coordinate-stable at any world position — the fragment hash is integer/position-based, so `detailScale` keeps full fine detail far from origin (a playfield at `|x|≈550` reads the same as one near origin; the old sin-hash flattened past `worldPos/scale ≳ 100`). `sky({ preset?, timeOfDay?, horizonColor?, zenithColor?, sunIntensity?, ambientIntensity?, radius?, hazeStrength?, sunGlowStrength?, fog? })` — `preset: "day" | "dusk" | "night"` (default `"day"`) is the static look; `timeOfDay: true` instead drives sun position, sky colors, and fog from the world clock's `calendar().dayFraction` every frame (`@jgengine/shell`'s `TimeOfDayDaylight` mounts this automatically for an `environment()` world with `sky` set — no per-game render wiring). The sky dome renders a bright HDR sun disc + warm glow (aligned to the sun light, so it blooms through a `postProcessing` chain), a mid-sky procedural cloud band, and a horizon haze — no HDRI assets; richer than a flat gradient with zero config. The dome **follows the camera every frame** (XZ), so it never leaves the viewer — but its `radius` (default 260) still has to clear your `fog.far` and any far scenery; a playfield sited far from the origin (or with distant landmarks) sets `radius` up to a few thousand so nothing pokes through the shell. `hazeStrength` (default 0.5) sizes the dusty horizon band and `sunGlowStrength` (default 1) the bloom around the sun disc — leave both unset for the stock look, raise/lower to dial a dusk/dust-storm horizon (the-robots uses `radius: 2600, hazeStrength: 0.62, sunGlowStrength: 0.6`). No game needs its own sky-dome mesh; configure `sky()` instead. **Gotcha:** `sunIntensity` / `ambientIntensity` are honored only on the **day** keyframe (`daylightCycle` builds dusk/night/dawn from fixed constants). Raising intensities under `preset: "dusk"` or `"night"` does nothing — use `preset: "day"` plus warm `horizonColor`/`zenithColor` when the first screenshot must be bright and readable (see `jgengine`'s first-shot art recipe).
|
|
21
|
+
|
|
22
|
+
**Default look is cinematic (#773).** A 3D game gets a real sky + shadow-casting sun/hemisphere rig + tuned post stack out of the box (`look` unset ⇒ `"cinematic"`; `defineGame({ look: "flat" })` opts back to the bare rig). The preset **defers to a world that declares its own `sky`** — an `environment({ sky })` world keeps exactly the dome/time-of-day/lighting it configured; the preset only supplies a sky (and the post stack) to worlds that have none. So `sky()` tuning below still owns the look for scenes that set it. Full rig + resolver: `jgengine-ui` → "Rendering".
|
|
23
|
+
|
|
24
|
+
**Flatten-mask composition semantics** (`withFlattenMasks` — easy to fight if guessed): masks apply **sequentially and the last matching mask wins outright**; there is no mask-to-mask blending. Inside `radius` the height becomes that mask's `target` exactly; in the `radius..radius+falloff` annulus it lerps from `target` back to the **raw noise height**, never to another mask's output. `target` defaults to the raw noise height at the mask's own center (`height` overrides). Consequences to know before composing roads or multi-site layouts: (1) a chain of default-height circles stair-steps, because each snaps to its own center's noise — give road masks explicit `height` values ramped between the endpoint site targets; (2) a later mask's falloff annulus punches raw-noise bumps through an earlier flat area — order masks so the surface that must win comes last, keep road `falloff` small (≤ radius), and skip road circles inside an already-flat site radius; (3) raw noise rarely herds — to guarantee canyon walls, emit masks with `height` *above* the surroundings flanking the corridor. A working road-ramp recipe (overlapping circles every ~12 units, height ramped between site targets, ramp clamped flat inside each site radius, wall masks alongside) lives in `Games/the-robots/src/game/world/level.ts`.
|
|
25
|
+
|
|
26
|
+
`parentSpace` positions are local to that space — convert at seams only.
|
|
27
|
+
|
|
28
|
+
### Query primitives (renderer-free, for gameplay)
|
|
29
|
+
|
|
30
|
+
Pure `@jgengine/core` functions so gameplay reads the same world the shell renders — no three.js needed:
|
|
31
|
+
|
|
32
|
+
| Primitive | Answers |
|
|
33
|
+
|-----------|---------|
|
|
34
|
+
| `resolveTerrainField(terrain(...))` / `noiseField(cfg)` → `TerrainField` | `sampleHeight(x,z)`, `sampleNormal(x,z)`, `waterLevel` — ground-snap, collision, camera. `resolveGroundStep` slope-limits movement |
|
|
35
|
+
| `snapToGround(field, position, offset?)` → `[x,y,z]` | Replaces a position's `y` with the field's sampled ground height (+ `offset`) at its `x`/`z` — the pure version of a spawn/placement ground-snap |
|
|
36
|
+
| `snapEntityToGround(entities, id, field, offset?)` → `boolean` | Ground-snaps an already-spawned entity in place via `entities.setPose` — false when `id` is unknown; the imperative sibling of `snapToGround` for "drop this entity onto the terrain right now" (mount dismount, teleport, respawn) |
|
|
37
|
+
| `windField(cfg)` → `WindField` | `at(t)`, `atPoint(x,z,t)`, `strengthAt` — one wind source for weather sway, grass, sailing, fire spread |
|
|
38
|
+
| `waterSurface(cfg)` / `waterSurfaceFromDescriptor(ocean(...))` → `WaterSurface` | `height(x,z,t)`, `normal`, `displace` — buoyancy, floating, shoreline (CPU Gerstner matching the ocean shader) |
|
|
39
|
+
| `scatter(cfg)` → `ScatterPoint[]` | Seeded, overlap-aware point distribution — vegetation, props, lots, spawn points (`minDistance`, `avoid` rects) |
|
|
40
|
+
| `createRegionField({ regions })` → `RegionField` | `sampleRegion(x,z)` blends content-agnostic biomes by nearest selector — height + `tint`/`water`/`fog`/`speedMultiplier` + opaque `data`. Extends `TerrainField`, so it ground-snaps too |
|
|
41
|
+
| `scatterItems(field, area, layersFor)` → `ScatterInstance[]` | Region-driven content scatter — density per region, grounded, above-water/slope-aware. `pickWeighted` for weighted rolls. (vs `scatter`'s pure geometric points) |
|
|
42
|
+
| `resolveScatterRegion(region, terrain?)` / `resolveScatter(doc, terrain?)` | Deterministic instanced placements for a **freehand polygon** scatter region (the editor's foliage lasso): `density`/`minSpacing`/`seed`, `minScale`/`maxScale`, weighted `palette`, `maxSlope`/`minHeight`/`maxHeight` masks, `edgeFalloff` feathering, `alignToNormal`. Regions ride editor paths of `kind` `SCATTER_PATH_KIND` — `scatterRegionFromPath`/`readScatterRules`/`readScatterPalette` build one from a path's meta, `scatterRegionEstimate` gives the live count readout, `resolveScatter` walks every scatter path in a document. Polygon helpers: `pointInPolygon`, `distanceToPolygonEdge`, `polygonArea`, `polygonBounds`, `isScatterPath`. Same seed → same field; GPU-instanced, never one node per instance. |
|
|
43
|
+
| `resolveAuthoredObjects(doc)` / `placeAuthoredObjects(store, objects, sampleHeight, opts?)` / `placeAuthoredObjectsFromDocument` / `markerCatalogId(marker)` | Catalog mesh props from editor markers: every marker with first-class `catalogId` or `meta.catalogId` → `{ catalogId, x, z, rotationY, instanceId, verticalOffset }[]`. `placeAuthoredObjects` grounds on `sampleHeight` and calls `store.place` (WorldScene renders via `objectModels`). Shell: `<AuthoredObjects document field />` or `<AuthoredScene placeObjects />`. Never hand-loop `document.markers` for prop placement. |
|
|
44
|
+
| `buildingIndex(district)` → `BuildingIndex` | `at`/`within`/`nearest`/`isInside`/`blockers` over a generated district — placement avoidance, pathfinding |
|
|
45
|
+
|
|
46
|
+
**Voxel field (`world/voxelField`).** `createVoxelField<T extends string>({ chunkSize? })` (default 16) is a logical block lattice for voxel games and instanced renderers — distinct from the `voxel()` `WorldFeature` descriptor above (that's the runner-level world kind; this is the block data structure a voxel game's gameplay reads and writes). `set`/`remove`/`get`/`has`/`fill`/`clear`/`count`/`cells`/`bounds` are the block CRUD (`set` returns `false` only when the identical type is already there — a no-op write). `neighbors(x, y, z)` returns the 6-adjacent occupied cells; `exposedFaces(x, y, z)` returns the `VoxelFace`s (`"px"|"nx"|"py"|"ny"|"pz"|"nz"`) not touching another voxel — feed that straight into greedy-meshing/face-culling. `raycast(origin, direction, maxDistance)` runs a 3D DDA and returns `{ x, y, z, type, face, adjacent, distance }`, where `adjacent` is the empty cell just in front of the hit — the placement target for block-place tools. Renderers dirty-track via `chunkOf(x, y, z)` + `chunkVersion(chunk)` and `subscribe(listener)`, so an instanced mesh only rebuilds the chunks that changed. For a non-`environment()` voxel world, assert on `field.summary()` (`{ blocks, types, bounds }`) the same way an `environment()` world asserts on `summarizeEnvironment` (see `jgengine-verify`).
|
|
47
|
+
|
|
48
|
+
**Destructible terrain (`world/carve`).** Two runtime-editable primitives for dig/carve worlds. `VoxelVolume` is a dense grid of material ids (0 = empty) — `carve({ center, radius, toolStrength })` clears a sphere of solid cells the tool is strong enough to break and returns the count removed (feed a loot roll), `deposit({ center, radius, material })` fills one (Deep Rock tunnels, Astroneer terrain add); `solidAtWorld` reads it back for collision. `CarvableField` (via `carvableTerrain(base)`) wraps any `TerrainField` and writes craters/mounds into its height — `carve({ x, z, radius, depth })`/`deposit({ x, z, radius, height })` — so ground-snap, collision, and the shell mesh all read the deformed surface (Helldivers 2 explosion craters). Cell strengths come from a `VoxelMaterial` table (DATA). Renders through `@jgengine/shell/terrain/CarvedTerrain`.
|
|
49
|
+
|
|
50
|
+
Renderers for these descriptors live in `@jgengine/shell` (`shell/terrain`, `shell/water`, `shell/weather`, `shell/structures`).
|
|
51
|
+
|
|
52
|
+
### Environment fields, weather hooks & realm composition
|
|
53
|
+
Renderer-free survival/environment primitives that extend the world query layer — meters, spawn gating, and damage-in-sunlight read the same world the shell renders, all ticking on game-time `dt`.
|
|
54
|
+
- **Environment field** (`world/envField`): `createEnvironmentField({ dayLength, baseTemperature, nightDrop, altitudeLapse, terrain, rain, occluders, heatSources, ambientFloor, temperatureAt })` → `EnvironmentField`. Sample **temperature**, **wetness**, **lightExposure** (direct sun/sky), and **ambientLight** (spawn gating) at any `(x, z, time)` — `sample(x, z, time, y?)` returns all four plus `sheltered`. Occluders (roofs/canopy) shade sun and shelter from rain; heat sources (campfires) warm nearby positions; `sunElevation(time)` drives the day cycle. Sun damages a vampire, cold forces campfires, low ambient light spawns mobs — the field answers "am I in sun vs. shade / cold vs. warm / dark vs. lit". Pure and instantaneous; stateful build-up belongs to a decay meter reading the field.
|
|
55
|
+
- **Weather → gameplay** (`world/weather`): `resolveWeather(state, table)` turns a `WeatherState { kind, intensity }` into concrete `ResolvedWeather` (`grip`, `visibility`, `structureDamage`, `chill`, `ignition`, `spread`) via a game-owned `WeatherModifierTable` — multipliers interpolate from neutral by intensity, rate effects scale linearly. Read `grip`/`visibility` in movement and AI, `structureDamage` on a building tick.
|
|
56
|
+
- **Fire spread** (`world/weather`): `createFireGrid({ cols, rows, cellSize, origin, fuelAt, spreadRate, burnRate, wind, windBias })` → `FireGrid` is a **coarse cellular** propagation (not a fluid solver): `ignite(x, z)` / `igniteCell(col, row)`, then `step(dt, { spread, wetnessAt })` transfers heat to neighbours biased by wind, consumes fuel (`unburnt → burning → burnt`), and honours firebreaks (zero-fuel cells) and rain/wetness suppression. `resolveWeather(...).spread` feeds the step; `@jgengine/shell/weather` `FireSpreadLayer` renders the burning/scorched cells.
|
|
57
|
+
- **Realm composition** (`world/realm`): `composeRealm(base, cards)` assembles a played instance at runtime from a deck of modifier **cards** (Nightingale realm cards) — a `major` card is the biome base, `minor` cards layer environment param overrides, a `WeatherState`, and spawn-table edits (`set`/`add`/`scale`/`remove`). The result recomposes both the environment (into a sampleable field via `composed.environmentField(extra?)`) and the `spawnTable`, and depends on the weather hooks above to turn its `weather` into gameplay modifiers.
|
|
58
|
+
- **Gameplay visibility / fog-of-war** (`world/visibilityField`, distinct from render culling in `core/visibility`): `createVisibilityField({ bounds, cellSize, memory })` → `VisibilityField` tracks per **viewer group** whether each grid cell is `hidden`, `remembered`, or `observed`. Each update the caller feeds a group's observed cells — `observe(group, cells)` or `observeCircle(group, x, z, radius)`, unioning multiple sources via `cellsInRadius(...out)` — and gets back a bounded `VisibilityDelta` (never a full-grid scan). Terrain memory is injected, not baked: `{ kind: "permanent" }` keeps last-known terrain (RTS explored map, generalizing single-group `world/fog`), `"none"` is pure line-of-sight, `{ kind: "decay", updates }` ages memory out. Day/night, height, stealth, and sensor rules stay the caller's job — they decide which cells count as observed. `visibleTo(group, entities, positionOf)` is the authoritative disclosure filter: it returns only entities in cells the group currently **observes**, so a unit that left view (or a rival group's units) never leaks into a client's replica — enemy re-hides while remembered terrain stays. `toState()`/`restore` round-trips sparsely; `cells(group)` gives dense codes for minimap draw.
|
|
59
|
+
### Survival meters, moodles & multi-region health
|
|
60
|
+
The `survival/` domain — decay-over-time meters and per-part health, both feeding one stacking **moodle** status display distinct from numeric bars.
|
|
61
|
+
- **Decay meters** (`survival/decayMeter`): `createDecayMeterSet([{ id, max, min?, start?, rate, thresholds }])` → `DecayMeterSet`. Each named meter (hunger, thirst, oxygen, sanity, warmth, stamina) drains/recovers on `tick(dt)` at `rate`, refills from consumables/actions via `refill(id, amount)`, and raises threshold moodles (`below`/`above`). `setRateModifier(id, mult)` lets the environment drive them — read an env field, then speed warmth loss when cold or oxygen loss in a toxic biome.
|
|
62
|
+
- **Moodles** (`survival/moodle`): the shared status stack, distinct from raw bars. `stackMoodles(...groups)` folds meter, ailment, and buff `Moodle[]` into one worst-first display (same-id stacks add, worst severity wins). `createMoodleStack()` holds timed buffs (`add({ id, label, duration })` — Valheim's concurrent food buffs) and expires them on `tick(dt)`.
|
|
63
|
+
- **Multi-region health** (`survival/regionHealth`): `createMultiRegionHealth({ regions, ailments })` → `MultiRegionHealth` gives per-part pools (head/thorax/arms/legs, Tarkov/DayZ style) — `damage(regionId, amount)` scales by `vulnerability` and kills when a `vital` part empties; a stacking **ailment queue** (`applyAilment`, `tick(dt)` drains like bleed) carries per-injury treatment (`treat(itemId)` clears wounds via bandage/tourniquet/splint). `ailmentMoodles()` shares the moodle display with the meters (#78 + #90).
|
|
64
|
+
### Interactive building & terraform (renderer-free tools)
|
|
65
|
+
Turn data-only placement into the build tooling of Valheim/Enshrouded/The Sims/Fortnite/Dinkum. Pure `@jgengine/core/world` math + `@jgengine/shell/structures` render (`PlacementGhost`, `TransformGizmo`); terraform brush via `shell/terrain/*`. Demo: `apps/dev` `builder-sandbox`.
|
|
66
|
+
| Primitive | Answers |
|
|
67
|
+
|-----------|---------|
|
|
68
|
+
| `createPlacementController({ footprint, rules, snapMode, grid })` | Owns the ghost: `hover(hit)` → `PlacementPreview` (`valid` tint wraps `validatePlacement`), `rotate()`, `setSnapMode`/`cycleSnapMode` (`"grid"`/`"free"`/`"surface"`), `commit()` → `PlacementCommit` (`rotationY` via `quarterTurnsToRotationY`). Feed it `pointer.worldHit()`. |
|
|
69
|
+
| `placeAssetFromCommit(commit, assetId, opts?)` / `resolvePlaceAsset` | **One place-asset verb** for editor `place_asset` and in-game builds → `PlaceAssetResult`. `toStructureInput(result)` → structure store; `toEditorMarker(result)` → scene-document marker. |
|
|
70
|
+
| `PlacementGhost` / `TransformGizmo` (`@jgengine/shell/structures`) | Ghost preview (valid/invalid tint). Runtime select/move/rotate/scale gizmo — editor `SelectionGizmo` mounts the same `TransformGizmo`. |
|
|
71
|
+
| `snapToNearest(registry, placed, movingDef, cursor, { snapDistance })` | Typed connector sockets — snaps a piece's socket onto the nearest **compatible** placed socket (`socketsCompatible` = both sides `accept` the other type). `worldSockets`/`socketWorldPosition` expand a piece's sockets to world space. |
|
|
72
|
+
| `solveSupport(pieces, links, { maxDistance })` → `SupportResult` | Walks the connector graph to any `grounded` piece: `supported` stays, `unsupported` collapses, `distance` (hops-to-ground) drives the white→red decay tint. `toDebrisBodies(pieces, unsupported)` → `AddBodyOptions[]` for the `PhysicsWorld` debris sink. |
|
|
73
|
+
| `createWallDrawTool({ snap, closeTolerance })` | Drag wall points → auto-encloses when the path returns to the start (`isEnclosed`), `footprint()` derives the room `EnclosedFootprint`, `roof()` auto-fits a hip/gable/flat `RoofPlan`. `createSurfacePaint()` stores per-tile floor/wall surfaces. |
|
|
74
|
+
| `createPlacedStructureStore()` | Save/load a built layout: `add`/`move`/`rotate`/`remove`/`select`, `snapshot()`↔`load()` round-trip (survives reload), `subscribe` for the renderer. |
|
|
75
|
+
| `createEditableTerrain({ bounds, base, cellSize })` → `EditableTerrain` | A `TerrainField` you can **write back to**: `apply(edit: TerraformEdit)` sculpts `raise`/`lower`/`smooth`/`flatten`/`noise`/`ramp` (plus surface `paint`) under a cursor and re-samples `sampleHeight`; brush `shape`/`heightLimit`/`falloff` shape the footprint; `surfaceAt`, `snapshot`/`restore`, `reset`. `createTerraformBrush(field)` is the simple cursor tool (radius/strength). This write-back grid is the shared terrain-edit pattern. |
|
|
76
|
+
| Compact sculpt strokes | `beginTerraformStroke(terrain)` batches a whole drag of stamps into one `TerraformDelta` (`.stamp(edit)` → `.delta()`) so undo replays a stroke as a single step without copying the heightfield. `editDelta`/`applyDelta`/`revertDelta` do the same for one edit. Serialize with `createTerrainSnapshot({ bounds, cellSize })` and rebuild via `editableTerrainFromSnapshot(snapshot, base)`; `applyDeltaToSnapshot`/`revertDeltaFromSnapshot` redo/undo a delta onto a stored snapshot copy-on-write. Backs the editor's Terrain tool (`jgengine-editor`). |
|
|
77
|
+
| Material paint strokes | The same delta model for surface layers: `terrain.paintDelta(edit)` / `beginSurfaceStroke(terrain)` record a `SurfaceDelta`; `fillSurfaceDelta(surface)` fills or clears (`null`) every cell; `autoPaintDelta({ surface, minSlope, maxSlope, minHeight, maxHeight })` paints by a slope/height rule (rock on steep, snow up high). `applySurfaceDelta`/`revertSurfaceDelta` on the live field, `applySurfaceDeltaToSnapshot`/`revertSurfaceDeltaFromSnapshot` copy-on-write on a stored snapshot. `surfaceAt(x,z)` reads the painted id back for coloring. |
|
|
78
|
+
| `createPlotPermissions({ plotId, ownerId, guildId? })` + `createContributionPool(goal)` | Per-plot/guild edit authority (`canEdit`/`canView`, `grant`/`revoke` `BuildRole`, guild inheritance) for co-op building, plus a pooled-resource contribution model (`contribute` caps at the goal, reports overflow, `isComplete`, per-contributor totals). |
|
|
79
|
+
| `createFootprintGrid({ cellSize? })` (`world/footprintGrid`) | Multi-cell footprint occupancy/reservation on a shared build grid — `cellsFor(origin, footprint, quarterTurns?)` derives the cells, `reserve(id, kind, cells)`/`release(id)`/`isFree`/`occupantAt`/`kindAt` hold the persistent claim `placementController`'s ghost preview doesn't. `footprintObstacles(grid)` bridges live reservations into `world/placement`'s `PlacementRules.obstacles` unchanged — no separate obstacle bookkeeping. `boundaryNeighbors(grid, cells)`/`hasValidAdjacency(grid, cells, accepts, requireConnection?)` validate connective-piece adjacency (roads/pipes/belts): every touching occupied neighbor must satisfy `accepts(kind)`, and `requireConnection` demands at least one (a road segment with nothing to connect to is invalid). |
|
|
80
|
+
|
|
81
|
+
### Physics world (optional, headless)
|
|
82
|
+
|
|
83
|
+
`physics/physicsWorld` `PhysicsWorld` is a standalone fixed-capacity rigid-body sim (SoA buffers, spatial-hash broadphase, sleeping) — **not** the `defineGame` `physics: { gravity, jumpVelocity }` field, which the built-in walk controller reads directly every frame (see "Controller kinematics" above; both values are real and honored, not dead config). Reach for `PhysicsWorld` when a game needs many colliding dynamic bodies (piles, debris, stress scenes): `new PhysicsWorld({ capacity, bounds, … })`, `addBody({ position, mass?, ...shape })`, then `step(dt)` per tick → `PhysicsStats`. Core owns the sim; `@jgengine/shell/world/InstancedBodies` renders its bodies. Most games never need it — the character controller covers ordinary movement. The broadphase grid (`nx*ny*nz` cells from `bounds`/`cellSize`) throws at construction if it would exceed a sane cell cap — shrink `bounds` or raise `cellSize` (same guard on `physics/spatialGrid`'s `SpatialGrid`).
|
|
84
|
+
|
|
85
|
+
**Body shape: box or sphere.** `AddBodyOptions` is a discriminated union — `{ shape?: "box", halfExtents }` (box is the default, `shape` omittable) or `{ shape: "sphere", radius }` (the radius fills all three half-extent columns, so broadphase/bounds see the sphere's enclosing AABB). `world.shape[i]` (`SHAPE_BOX` / `SHAPE_SPHERE`) reports a live body's shape for a consumer walking the raw SoA arrays. Sphere-sphere and sphere-box pairs resolve with a proper radial normal (not the axis-aligned box/box path) — balls, projectile bodies, and rolling debris collide correctly against both boxes and each other.
|
|
86
|
+
|
|
87
|
+
**Collision shapes are box/sphere/voxel only.** Every collider in the engine — `PhysicsWorld` bodies (box `halfExtents` or sphere `radius`), object/entity picking (`scene/objectQuery` raycasts against unit boxes), and `world/voxelField`/`world/carve` blocks — is an axis-aligned box, a sphere, or a voxel cell. Arbitrary authored level-mesh collision (a sculpted GLB as a collider) is not supported. The seams for custom collision are `movement.beforeCommit` (steer or replace the walk controller's resolved step) and object raycasts (query the scene yourself and react) — not a mesh collider.
|
|
88
|
+
|
|
89
|
+
**Ballistic collision sweep (`physics/ballisticSweep`).** `createBallisticSweep(world, { step?, radius? })` → `BallisticSweep`, a `(origin, velocity, gravity, maxTime) => BallisticSweepHit | null` function that marches the closed-form arc (constant gravity, straight lateral) through a `PhysicsWorld` and reports the first sample inside any live body's AABB (sleeping bodies included), refined by one bisection step; `null` means the whole arc is clear. `step` (default 1/60) is the march interval in seconds, `radius` (default 0) inflates every body's AABB before the point test — pass the projectile's own radius. Wire it into `combat/projectiles` via `ProjectileSystemDeps.sweepBallistic`: when set, a ballistic shot settles at the swept impact point instead of the closed-form landing; omitted or `null` falls back to that closed-form arc.
|
|
90
|
+
|
|
91
|
+
**Removing and moving bodies.** `removeBody(id)` tombstones a body — it drops out of integration/broadphase and its slot is queued for the next `addBody` — without moving or invalidating any other body's `id` (ids are raw SoA slots, stored as-is in joints and game state, so nothing ever gets swapped). It conservatively wakes any sleeping body whose AABB touched the removed one's (no persistent contact set to consult, so this errs toward waking too much, never too little). `setVelocity(id, x, y, z)` and `setPosition(id, x, y, z)` write a body's velocity/position directly (instead of poking the public `velX`/`posX` SoA arrays) and wake it if asleep; `teleport(id, x, y, z)` is `setPosition` plus a hard velocity reset (respawn/teleporter, vs. sliding). `isAlive(id)`/`highWater` (one past the highest slot ever handed out) let a consumer that iterates the raw SoA arrays skip tombstoned holes correctly instead of assuming `count` is a dense `0..count` range.
|
|
92
|
+
|
|
93
|
+
**Joints & constraints.** `hingeJoint`/`fixedJoint`/`distanceJoint`/`springJoint(opts)` connect two bodies, or a body to a fixed world point (omit `bodyB`). The sim is translational (no angular DOF), so `hinge`/`fixed` pin the shared anchor (the `axis` is retained metadata), `distance` holds a fixed separation, and `spring` drives toward `restLength` with `stiffness`/`damping` (suspension, follow-point carry). `removeJoint(id)`, `setJointAnchor(id, x, y, z)` (move anchor B — a world anchor's follow point, or body B's local offset), `setJointAnchorA(id, x, y, z)` (move anchor A's local offset — e.g. re-rotating a suspension mount each frame as the chassis turns), `setJointRest`, and `readJointSegments(out)` for `@jgengine/shell/world/InstancedJoints` (debug line render). This is the foundation under vehicles, ragdolls, grapples, and carry.
|
|
94
|
+
|
|
95
|
+
**Collision → gameplay events.** `world.onCollision(listener, minApproachSpeed?)` delivers every impacting contact — `CollisionEvent { a, b, nx, ny, nz, approachSpeed, impulse }` — to game code during `step` (the object is reused; read/copy it, never retain). This is the seam crash-damage and destruction read; pass `null` to detach.
|
|
96
|
+
|
|
97
|
+
**Actors on top of the sim:** `physics/ragdoll` (`createRagdoll(world, { bones, links, balance? })` — jointed bones, floppy or active-ragdoll via a balance motor), `physics/carryable` (`Carryable` — grab a body to a follow point, shared multi-owner carry, `carrySpeedMultiplier` encumbrance, drop/throw; the raycast pick is the caller's job, core owns the constraint), `physics/forceVolume` (`ForceVolume` — impulse/velocity/accelerate trigger region, `once` for boost pads; `PlatformCarry` — carry bodies standing on a moving platform by its per-`step` delta). Separately, `physics/spatialGrid` `SpatialGrid` is a broad-phase grid over the x/z plane, **distinct** from the rigid-body sim, for cheap same-tick proximity across hundreds–thousands of simple movers — `rebuild(count, xs, zs)` then `queryCircle` (swarm enemies hitting a player/AoE) or `forEachPair` (mutual separation).
|
|
98
|
+
|
|
99
|
+
**Swarm-scale presentation.** `@jgengine/shell/world/SpriteBatch` renders up to `capacity` sprite-sheet quads as one `InstancedMesh` (per-instance atlas `frame`, `plane: "xy" | "xz"` or `billboard`, `pixelated` nearest-filtering) — the batched path for bullet-heaven swarms, item streams, and tile-grid presentation instead of one `<sprite>` per entity. Pair it with `world/lod`'s `createLodScheduler` (see the concept table) to band render detail by distance and throttle far entities' update work.
|
|
100
|
+
|
|
101
|
+
**Traversal (`physics/traversal`).** `Grapple` fires a rope from a body to a fixed world point on the joint API — `fire(x,y,z)` attaches a `distance` (rigid) or `elastic` (spring) joint, `reel(dt)`/`payOut(dt)` shorten/lengthen the rope to pull the traveller in, `moveAnchor` re-points it (ziplines, grapple-to-moving-target). Grapple/zipline/swing (Sekiro, Deep Rock, Just Cause) are all the same primitive; the raycast that finds the anchor is the caller's. `Glide` is a reduced-gravity, forward-thrust wingsuit/glider over a body — call `apply(dt, steerX, steerZ)` each frame before `step` to feed back most of gravity (`gravityScale`), thrust along the steer vector, and clamp descent; stop calling it to fall normally, no attach/detach state.
|
|
102
|
+
**Structural destruction (`physics/structure`).** `StructureGraph` models a building as nodes (pieces) + load-bearing edges with some nodes `anchor`ed (foundations). `damage(id, n)`/`damageEdge(a,b,n)`/`severEdge(a,b)` wear pieces and connections; when one breaks, the graph recomputes reachability to an anchor and returns a single `CollapseEvent { fell }` — every piece the loss disconnected. `toDebris(world, event)` sinks the fallen pieces into a `PhysicsWorld` as rigid bodies (The Finals, Rainbow Six). It is coarse by design: **replicate the collapse event (the `fell` id list), not each fragment's physics** — game clients re-derive the debris locally. Piece integrity and edge strength default from a `StructureMaterial` table (DATA).
|
|
103
|
+
**Group movement (`movement/formation` + `movement/avoidance`).** Three separable seams keep "where the group stands" apart from "how each member gets there" so a game can pair them with any route source. `placeFormation(destination, facing, count, generator)` maps a `FormationSlotGenerator`'s local `[right, forward]` offsets into world XZ slots around a target — formation shape is **caller data**, not an engine enum; `lineFormation`/`boxFormation`/`wedgeFormation`/`circleFormation` are sample generators and a custom one needs no engine edit. `assignFormationSlots(members, slots, { previous?, stickiness? })` is deterministic greedy nearest matching (`assignment[member] = slot`, `-1` when short on slots); `stickiness` biases a member toward its previous slot to cut per-frame reshuffle. `facingYaw(from, to)` orients a formation along its travel heading. Then `resolveLocalAvoidance(agents, { iterations?, strength?, padding?, weights? })` pushes overlapping circular agents apart in place — a bounded, deterministic Jacobi relaxation over an internal 3×3 hash grid (never an O(n²) whole-world scan), honoring per-agent radii and inverse-mass `weights` (`0` = pinned anchor/obstacle). Route planning (nav path, flow field, direct seek) stays the caller's; this owns slot geometry, matching, and separation only. Squads, parties, convoys, and non-formation crowds all compose the same three calls.
|
|
104
|
+
### Vehicles, mounts, crash damage & racing
|
|
105
|
+
Five primitives layer a driving/racing game over the physics sim and `world/water`. All are **data-first** (spec the chassis/wheels/grip curve, damage thresholds, and checkpoint layout as catalog data) and pure `@jgengine/core`; renderers live in the game/shell. Each `update(dt, …)` runs **before** the shared `world.step(dt)`.
|
|
106
|
+
- **Analog input — `input/axisInput`.** `AxisInput { throttle, brake, steer, handbrake }` is a continuous channel, **distinct from the digital action bindings**. `new AxisChannel({ bindings, smoothing })` ramps held keys into pedal-like analog values (`sample(dt, isDown)`), or `setAnalog(axis, value)` drives it straight from a gamepad axis. `DRIVE_AXIS_BINDINGS` is a ready WASD/arrow map. `sampleAxisBindings` is the instantaneous, unsmoothed read the channel ramps toward each frame — exposed headlessly as `ctx.input.axis(bindings, ranges?)`, bound to **action names** rather than raw key codes (#533.7).
|
|
107
|
+
- **`physics/vehicleBody`.** `createVehicleBody(world, config)` is an arcade car: a chassis box body with per-wheel suspension held by G3's `springJoint` against the sampled `groundHeight`, drive/brake along the heading, and a `GripCurve` (`sampleGripCurve`) that bleeds lateral velocity for cornering — and, under `handbrake`, drift. `update(dt, axisInput)` then `world.step`. Because the chassis is a real body it still collides, which feeds crash damage. Rocket League, Trackmania, Wreckfest.
|
|
108
|
+
- **`physics/kinematicVehicle`.** `createKinematicVehicle(tuning, options?)` is the no-`PhysicsWorld` sibling of `vehicleBody` — same steer-yaw/accel/grip-curve arcade feel, driven purely by `tick(dt, axis, modifiers?)` returning `{ position, heading, forwardSpeed, lateralSpeed, slip, surface }`. `surfaceFriction`/`dragAt` hooks (grip-by-position, drag zones) and `clampMove` (walls/arena bounds) keep game flavor out of the sim; `scaleVelocity`/`resetTo` cover boost pads and respawns. Racers and open-world cars that don't need real rigid-body collision.
|
|
109
|
+
- **`physics/drivableVehicle`** (#533.1). `tickDrivableVehicle(vehicle, dt, axis, { groundHeight?, modifiers? })` is the missing wire from an `AxisInput` sample through a `KinematicVehicle` to a `setPose`-ready entity patch — the throttle/steer/handbrake → sim → `entities.setPose(id, drive.pose)` loop every drivable-vehicle game hand-rolled, one call instead of a hand-copied per-game `stepCar`. `groundHeight` snaps the resulting `y` to terrain each tick; omit it to keep the sim's flat `y`. Returns `{ pose, step }` — `step` is the raw `KinematicVehicleStep` for HUD speed/slip reads.
|
|
110
|
+
- **`physics/buoyancy`.** `createBuoyantBody(world, { body, water, … })` floats a body on a CPU `WaterSurface` (Archimedes per hull point + water drag) so it settles at the waterline and rides the Gerstner waves; pass an `AxisInput` to `update(dt, time, input?)` and it drives as a boat (thrust + yaw + keel). Sea of Thieves, BOTW rafts.
|
|
111
|
+
- **`scene/mount`.** `createMountController()` transfers control to a driven entity: `register({ id, kit, seats })`, `mount(riderId, mountId, seatId?)`, `dismount`. Read `cameraTarget(riderId)` to point the follow camera and `driveTarget(riderId)` to route that rider's `AxisInput` at the mount — the control seat drives, passenger seats ride (multi-seat shared vehicles), and an un-mounted rider drives themselves. `driver(mountId)`/`occupants(mountId)`/`kitOf`. Palworld mounts, V Rising horse, a crewed ship.
|
|
112
|
+
- **`scene/stationClaim`.** `createStationClaim(controller?)` layers **facet stations** on `scene/mount` for a vehicle several players crew at once: `register({ id, kit, stations })` where each `Station` tags a seat with a `facet` (`"steer"`/`"sails"`/`"cannon"`). `claim(playerId, vehicleId, facetOrStationId)`, `release`, `controllerOf(vehicleId, facet)` (who mans it), `facetOf(playerId)`, `openFacets`, `crew`. Only a `control` station operates the hull (`driver`/`driveTarget`); the rest ride but command their own facet. Sea of Thieves helm + sails + cannons.
|
|
113
|
+
- **`scene/vehicleSeat`** (#533.2). `createVehicleSeats(controller?)` composes `scene/mount`'s control-transfer with the enter/exit-vehicle transition every game hand-rolled: `register({ id, kit, seats? })` then `enter(riderId, vehicleId, options?)` resolves a free seat and returns `{ seat, cameraTarget, driveTarget, riderMovementPatch }` in one call — feed `cameraTarget` into `ctx.camera.follow(...)`, `riderMovementPatch` (`{ frozen: true }`) into `entities.update(riderId, { movement: { ...current, frozen } })`. `exit(riderId, vehiclePose, offset?)` computes a side-door placement (`distance`/`side: "left" | "right"`) next to the vehicle and returns the same triad in reverse (`riderMovementPatch: { frozen: false }`, `cameraTarget` back to the rider). Pure — no entity/camera side effects, the caller applies the patches via its own `ctx`. `isSeated`/`driverOf`; `.mounts` exposes the underlying `MountController` for direct dismount (e.g. a vehicle explosion that skips the placement math). GTA-style boarding, mountable creatures, boat docking.
|
|
114
|
+
- **`physics/damageZones`.** `createDamageModel({ zones, disableAt })` maps accumulated contact impulse (from `onCollision`) to **coarse discrete stages** (not soft-body): `absorb(zoneId, impulse)` / `routeCollision(event, resolveZone)` bump a zone's stage (caller swaps the visual/collider), an optional `detachStage` ejects a part as debris once, and crossing `disableAt` flips a whole-vehicle `disabled` state. Wreckfest crumple/derby.
|
|
115
|
+
- **`game/race`.** `raceTrack({ checkpoints, laps })` is an ordered ring of AABB checkpoint volumes (the final one is the finish line); `createRaceState({ track, win })` — driven each tick by `update(now, positions)` on game time — emits `checkpoint.hit` / `lap.completed` / `position.changed` / `race.finished`, keeps split times, resolves a pluggable `RaceWinCondition` (`firstPastPost`, `topK` round-cut, `everyoneFinishes`, `lastStanding` derby), and `resetToCheckpoint(id)` hands back a respawn pose. `removeRacer(id)` drops a racer mid-race and renumbers the remaining standings; `reset()` clears all racer progress/finish state back to construction time so the same instance replays without rebuilding it. Trackmania, Mario Kart, Fall Guys.
|
|
116
|
+
- **Session lifecycle** (`RaceSessionState`, phases `idle → countdown → racing → finished`) — the pure clock every racer wraps around `RaceState`, so no game hand-rolls its own `RacePhase`/`COUNTDOWN_SECONDS`. `idleRaceSession()` is the grid; `startRaceCountdown({ seconds })` drops the lights (default 3, `seconds ≤ 0` = standing start); `tickRaceSession(session, dt)` bleeds the countdown then accumulates `elapsed` once green; `finishRaceSession(session)` freezes the clock at the flag. It carries no track/lap data — compose it with `RaceState` (positions/laps) and `createLapTimer` (per-lap wall-clock). A racing game layers medals/DNF on top of it.
|
|
117
|
+
```ts
|
|
118
|
+
let session = startRaceCountdown(); // { phase: "countdown", countdown: 3, elapsed: 0 }
|
|
119
|
+
session = tickRaceSession(session, 3); // { phase: "racing", countdown: 0, elapsed: 0 }
|
|
120
|
+
session = tickRaceSession(session, 12.4); // elapsed 12.4
|
|
121
|
+
session = finishRaceSession(session); // { phase: "finished", elapsed: 12.4 }
|
|
122
|
+
```
|
|
123
|
+
- **Placement → outcome** turns a `race.finished` `ranking` (or any finish order, index 0 = winner) into results: `racePlacements(order, { winningPlaces })` → `{ racerId, place, outcome }[]` (`place` 1-based); `placementOf(order, id)` → one racer or `null`; `raceOutcomeOf(order, id)` is the `ranking[0] === player ? "win" : "lose"` check generalized to a `winningPlaces` podium (an absent racer loses). A caravan-race game derives its win/strand this way.
|
|
124
|
+
```ts
|
|
125
|
+
raceOutcomeOf(event.ranking, ctx.player.userId); // "win" | "lose"
|
|
126
|
+
racePlacements(event.ranking, { winningPlaces: 3 }); // podium = wins
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Sim-snapshot → scene-entity pose bind
|
|
130
|
+
|
|
131
|
+
`ctx.scene.entity.bind(key)` (`scene/bodyBind`, `createBodyBind`) replaces a hand-written per-tick `setPose` loop plus its `despawn`/`spawn` respawn dance: call `bind(key).sync(bodies, dt?)` once a tick with every sim body's current snapshot (`{ id, kind, position, rotationY?, rotationX?, rotationZ?, role?, meta? }`, `kind` = catalog entity name, read only the first time an `id` appears). An `id` seen for the first time spawns from its `kind`; one already bound gets posed (`dt` derives velocity the same as a direct `setPose`); a previously-bound `id` absent from this tick's `bodies` is despawned — a dying/leaving sim body just stops appearing in the iterable, no explicit `despawn` call. `bind` is lazily keyed (one bind per swarm/roster the game tracks — a player+rivals racing pack, a wave of enemies, a vehicle pool), so unrelated scene entities are untouched. `wreckway` and similar racing packs, caravans, and creature-swarms drive their racer/caravan/creature-swarm poses this way.
|
|
132
|
+
```ts
|
|
133
|
+
ctx.scene.entity.bind("racers").sync(
|
|
134
|
+
[{ id: ctx.player.userId, kind: KART_PLAYER_ENTITY, position, rotationY, role: "player" }],
|
|
135
|
+
dt,
|
|
136
|
+
);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Authored behavior triggers (volumes / markers)
|
|
140
|
+
|
|
141
|
+
Schema'd `on`/`action` meta on editor volumes and markers — "when player enters this volume, spawn wave 2" is scene data, not game code. `@jgengine/core/scene/authoredTriggers`:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
registerTriggerAction({
|
|
145
|
+
id: "spawn_wave",
|
|
146
|
+
label: "Spawn wave",
|
|
147
|
+
schema: { fields: [{ type: "number", key: "wave", default: 1, min: 1 }] },
|
|
148
|
+
});
|
|
149
|
+
// volume.meta: { on: "enter"|"exit"|"interact", action: "spawn_wave", wave: 2 }
|
|
150
|
+
const runtime = createAuthoredTriggerRuntime({
|
|
151
|
+
document: editorLayers,
|
|
152
|
+
handlers: { spawn_wave: (e: TriggerDispatchEvent) => startWave(e.params.wave as number) },
|
|
153
|
+
});
|
|
154
|
+
// each tick:
|
|
155
|
+
runtime.step({
|
|
156
|
+
actors: [{ id: playerId, position: entity.position }],
|
|
157
|
+
interact: ctx.input.justPressed("interact") ? [playerId] : [],
|
|
158
|
+
});
|
|
159
|
+
// discovery: listTriggerActions() / getTriggerAction(id) / collectAuthoredTriggers(doc) / pointInVolume(volume, point)
|
|
160
|
+
// types: TriggerEvent · TriggerActionDefinition · AuthoredTrigger · TriggerHandlers · TriggerSourceKind · AuthoredTriggerRuntime
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Multi-triggers: `meta.triggers: [{ on, action, ...params }]`. Editor inspector renders action params from the declared schema when the game has registered actions.
|
|
164
|
+
|
|
165
|
+
### Continuous area effects (source-following membership)
|
|
166
|
+
|
|
167
|
+
The dynamic counterpart to authored triggers: moving sources emit an area that **follows them** each tick, receivers get `enter`/`refresh`/`leave` edges, and membership is cleaned up on source removal/disable/clear. Genre-agnostic and pure — it never applies damage/heals/stats itself; you route each edge through your own pipeline (auras, hazard/heal fields, capture zones, silence/music areas). `@jgengine/core/area/areaEffectField`:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
const field = createAreaEffectField<{ dps: number }>();
|
|
171
|
+
// each tick, per live source (so the shape follows its emitter):
|
|
172
|
+
field.setSource({ id: bossId, shape: { kind: "sphere", center: bossPos, radius: 3 }, payload: { dps: 6 }, refreshMs: 700 });
|
|
173
|
+
field.removeSource(deadBossId); // -> leave edges (reason "source-removed"); auto cleanup
|
|
174
|
+
const events = field.step({
|
|
175
|
+
dtMs: dt * 1000,
|
|
176
|
+
candidates: (shape) => index.querySphere(shape.center[0], shape.center[1], shape.center[2], shape.radius, out), // bounded broad phase
|
|
177
|
+
positionOf: (id) => positions.get(id), // undefined => receiver gone -> leave
|
|
178
|
+
eligible: (sourceId, receiverId) => isEnemy(receiverId), // optional team/immunity gate
|
|
179
|
+
});
|
|
180
|
+
for (const e of events) if (e.kind === "refresh") applyDamage(e.receiverId, e.payload.dps * (e.ticks ?? 1));
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
- Bounded by design (caller supplies the candidate query) and serializable (`serialize()` keeps membership + refresh phase across save/load; re-`setSource` live shapes before the next `step`).
|
|
184
|
+
- `custom` shape leaves a seam for authored volumes / non-sphere shapes while still exposing a bounding sphere for the broad phase.
|
|
185
|
+
- Overlap aggregation is a policy over `field.membershipsOf(receiverId)` — `@jgengine/core/area/stackPolicy`: `independentStacks` · `uniqueByStackKey` · `cappedStacks` · `extremumStack` (strongest/weakest) · `sumMagnitude` (additive).
|
|
186
|
+
- First adopter: `Games/the-robots` boss overload auras (`game/entities/enemies/hazardAura.ts`).
|
|
187
|
+
|
|
188
|
+
### Spawn placement
|
|
189
|
+
|
|
190
|
+
`spawn(catalogId, { id?, position | anchor, offset?, parentSpace?, group? })` — anchor `{ kind: "entity" | "zone", id }` with offset `{ radius, pattern }` or `{ xyz }`. Catalog supplies movement/model; no behaviors on spawn.
|
|
191
|
+
|
|
192
|
+
**Named spawn/respawn points** (`game/spawnPoints`) — `createSpawnPoints()`: `record(id, { x, y, z, rotationY? })` names a point (level bounds, team spawns, checkpoints), `get(id)`/`list()` read them back, `respawn(entities, entityId, spawnId)` teleports an existing entity to a recorded point via `setPose` in one call — the id-keyed alternative to threading raw coordinates through respawn logic by hand.
|
|
193
|
+
|
|
194
|
+
**Level sequence** (`game/levelSequence`) — `createLevelSequence({ levels: [{ id, config }], retriesPerLevel? })` is a pure, deterministic campaign machine: `start()` enters level 0, `clear()` marks the current level cleared, `advance()` moves to the next (or `"complete"` after the last), `fail()` consumes an attempt and returns `"retry"` while `retriesPerLevel` remain else `"failed"`, `retry()` restarts after a retry-eligible failure. `current()` → `{ id, index, config, attempt } | null`; `progress()` → `{ index, total, cleared }`; `reset()` rewinds to idle. Mirrors the reducer style of `game/race` and `ai/spawnDirector` — a level-select/roguelike-run campaign shell without hand-rolling the state machine per game.
|
|
195
|
+
|
|
196
|
+
### Ambient behaviors + instance lifecycle (`scene/behaviors`, `scene/behaviorRuntime`)
|
|
197
|
+
|
|
198
|
+
Attach `patrol({ waypoints, speed, loop?, startProgress?, groundClamp? })` or `wander({ radius })` in a spawn's `behaviors`; the host's `advanceBehaviors(ctx, dt)` (already wired in the runners) keeps the per-entity nav state and poses it — register-once ambient traffic/idle routes, never a per-game `advancePathFollow` + `setPose` loop. `startProgress` seeds a follower at a distributed phase (see `PathProgress` below); `groundClamp: true` samples world ground height for the pose Y so an XZ route rides terrain.
|
|
199
|
+
|
|
200
|
+
`behaviorControl(ctx)` is the per-entity lifecycle surface (keyed by entity id, no full-world scan) games use instead of bypassing the runtime for possession/freezing/streaming/staggering:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
const bc = behaviorControl(ctx);
|
|
204
|
+
bc.pause(id, "driven"); // retain state, stop advance+pose (player commandeered the car)
|
|
205
|
+
bc.resume(id); // catch-up policy: "freeze" (default, deterministic) | "advance" (silently fast-forward paused span)
|
|
206
|
+
bc.disable(id, "panic"); // hold off until enable() — frozen in place
|
|
207
|
+
bc.enable(id);
|
|
208
|
+
bc.seek(id, { kind: "normalized", value: 0.5 }); // patrol only; jump to semantic progress
|
|
209
|
+
bc.serialize(id) / bc.restore(id, snap); // BehaviorSnapshot round-trips exactly
|
|
210
|
+
bc.status(id) / bc.reason(id) / bc.inspect(id) / bc.list(); // editor/debug inspection
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Path progress adapter (`nav/pathFollow`): `PathProgress` is `{ kind: "normalized", value } | { kind: "distance", value } | { kind: "segment", index, fraction }` — travel direction falls out of the resulting `heading`. `pathFollowSeek(config, progress)` places a follower without simulating from the start (loops wrap, non-loops clamp + report `done`); `pathFollowProgress(config, state)` reads it back as `{ distance, normalized, segment, fraction }`; `pathLength(config)` sums the polyline. Vice-isle's traffic + pedestrians run entirely on this seam (phase-distributed `startProgress`, `pause` on drive-away, `disable` on wanted-panic) — no hand-rolled route loop.
|
|
214
|
+
### Entity orders (command queue)
|
|
215
|
+
|
|
216
|
+
`orders/orderQueue` + `orders/orderKinds` — a serializable per-entity command queue for player- or AI-issued intent, instead of a hard-coded RTS verb kit. Build a shared registry once with `createOrderRegistry<Ctx>()` and register verbs as data; engine code never branches on a verb. Give each commandable unit its own `createOrderQueue(registry, { genId?, onEvent? })`.
|
|
217
|
+
|
|
218
|
+
- **Lifecycle**: `issue(request)` validates and queues; `tick(ctx, dt)` activates the next order, advances the active one, and completes/cancels it — returning `{ active, activated, completed, canceled }`. `cancelActive`/`cancelAll`/`clear` force cleanup. `serialize()`/`load()` (or the `initial` option) round-trip mid-flight state for save and host-authoritative replication.
|
|
219
|
+
- **Preemption policy** on each issue: `replace` (preempt active + clear pending), `append` (shift-queue), `front` (jump the queue without dropping the active order), `reject` (refuse while busy). Mark an order `uninterruptible` and a replacing order waits until it finishes; explicit `cancelActive` still forces it.
|
|
220
|
+
- **Order kinds** are `OrderKind<Ctx, Payload>` with `start`/`update`/`finish`, composed over two narrow adapters — `OrderMover` (`position`/`moveToward`/`halt`) and `OrderTargeting` (`acquire`/`positionOf`). Built-ins ship as ordinary compositions with a configurable `kind` string so a game re-skins a verb ("harvest") without engine edits: `defineMoveOrder`, `defineStopOrder`, `defineHoldOrder`, `defineAttackMoveOrder`, `defineTargetedOrder`, `definePatrolOrder`. Engagement verbs write `{ engaging, inRange }` into `order.state`; the game reads it to run the actual attack/effect, keeping combat resolution game-side.
|
|
221
|
+
|
|
222
|
+
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { type AbilityKit } from "../combat/abilityKit.js";
|
|
2
|
-
import type { GameLoop } from "../game/defineGame.js";
|
|
3
|
-
import type { GameContext, GameContextContent } from "../runtime/gameContext.js";
|
|
4
|
-
import { type CartridgeRun, type CartridgeSpec } from "./spec.js";
|
|
5
|
-
export interface CartridgeRuntime {
|
|
6
|
-
content: GameContextContent;
|
|
7
|
-
loop: GameLoop<GameContext>;
|
|
8
|
-
run(ctx: GameContext): CartridgeRun;
|
|
9
|
-
weaponKit(ctx: GameContext): AbilityKit;
|
|
10
|
-
chooseUpgrade(ctx: GameContext, offerId: string): void;
|
|
11
|
-
begin(ctx: GameContext): void;
|
|
12
|
-
reset(ctx: GameContext): void;
|
|
13
|
-
spec: CartridgeSpec;
|
|
14
|
-
}
|
|
15
|
-
export declare function createCartridge(spec: CartridgeSpec): CartridgeRuntime;
|