@jgengine/core 0.18.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +504 -7
- package/dist/ai/decisionGraph.d.ts +50 -4
- package/dist/ai/decisionGraph.js +159 -23
- package/dist/ai/flock.d.ts +1 -1
- package/dist/ai/flock.js +7 -4
- package/dist/ai/groupAssist.d.ts +10 -0
- package/dist/ai/groupAssist.js +11 -0
- package/dist/ai/pursuit.d.ts +1 -1
- package/dist/ai/pursuit.js +1 -1
- package/dist/ai/threat.d.ts +8 -0
- package/dist/ai/threat.js +10 -0
- package/dist/anim/animGraph.d.ts +43 -1
- package/dist/anim/animGraph.js +326 -10
- package/dist/anim/boneTexture.d.ts +55 -0
- package/dist/anim/boneTexture.js +43 -0
- package/dist/anim/footPlacement.d.ts +57 -0
- package/dist/anim/footPlacement.js +67 -0
- package/dist/anim/locomotionGraph.d.ts +18 -1
- package/dist/anim/locomotionGraph.js +42 -3
- package/dist/audio/audioFalloff.d.ts +6 -0
- package/dist/audio/doppler.d.ts +21 -0
- package/dist/audio/doppler.js +32 -0
- package/dist/audio/engineLayers.d.ts +83 -0
- package/dist/audio/engineLayers.js +115 -0
- package/dist/audio/voiceAllocator.d.ts +46 -0
- package/dist/audio/voiceAllocator.js +76 -0
- package/dist/combat/abilityKit.d.ts +20 -0
- package/dist/combat/abilityKit.js +41 -4
- package/dist/combat/death.d.ts +11 -3
- package/dist/combat/death.js +3 -3
- package/dist/combat/magazine.d.ts +9 -0
- package/dist/combat/magazine.js +24 -2
- package/dist/combat/projectiles.d.ts +94 -2
- package/dist/combat/projectiles.js +282 -61
- package/dist/combat/regenShield.d.ts +14 -0
- package/dist/combat/regenShield.js +23 -7
- package/dist/combat/shotOrigin.d.ts +6 -0
- package/dist/combat/shotOrigin.js +6 -0
- package/dist/combat/weaponFeedback.d.ts +36 -0
- package/dist/combat/weaponFeedback.js +42 -0
- package/dist/combat/weaponHandling.d.ts +161 -0
- package/dist/combat/weaponHandling.js +202 -0
- package/dist/combat/weaponPresentation.d.ts +115 -0
- package/dist/combat/weaponPresentation.js +96 -0
- package/dist/crafting/recipeCatalog.d.ts +16 -0
- package/dist/crafting/recipeCatalog.js +55 -0
- package/dist/devtools/warnOnce.d.ts +8 -0
- package/dist/devtools/warnOnce.js +18 -0
- package/dist/economy/auctionBook.d.ts +1 -1
- package/dist/economy/auctionBook.js +11 -1
- package/dist/economy/listingBook.d.ts +1 -1
- package/dist/economy/listingBook.js +11 -1
- package/dist/economy/resourceLedger.js +3 -3
- package/dist/economy/shopStock.js +7 -6
- package/dist/economy/techTree.js +7 -6
- package/dist/economy/unlockPoints.js +3 -6
- package/dist/editor/commandApply.js +89 -16
- package/dist/editor/commands.d.ts +38 -1
- package/dist/editor/commands.js +320 -3
- package/dist/editor/creatorStorage.d.ts +59 -0
- package/dist/editor/creatorStorage.js +164 -0
- package/dist/editor/document.d.ts +4 -0
- package/dist/editor/document.js +119 -18
- package/dist/editor/environment.js +8 -0
- package/dist/editor/index.d.ts +2 -0
- package/dist/editor/liveSync.d.ts +4 -1
- package/dist/editor/liveSync.js +26 -10
- package/dist/editor/materialAuthoring.d.ts +14 -0
- package/dist/editor/materialAuthoring.js +38 -0
- package/dist/editor/modelNodeVisibility.d.ts +11 -0
- package/dist/editor/modelNodeVisibility.js +29 -0
- package/dist/editor/movementCatalog.d.ts +90 -0
- package/dist/editor/movementCatalog.js +144 -0
- package/dist/editor/simulation.d.ts +60 -0
- package/dist/editor/simulation.js +202 -0
- package/dist/editor/staticPrefab.d.ts +17 -0
- package/dist/editor/staticPrefab.js +51 -0
- package/dist/editor/types.d.ts +15 -0
- package/dist/faction/reputation.d.ts +6 -0
- package/dist/faction/reputation.js +11 -0
- package/dist/game/cameraConfig.d.ts +54 -2
- package/dist/game/clipRoles.d.ts +2 -1
- package/dist/game/clipRoles.js +7 -3
- package/dist/game/contentValidation.d.ts +69 -0
- package/dist/game/contentValidation.js +118 -0
- package/dist/game/controlGate.d.ts +8 -0
- package/dist/game/controlGate.js +43 -6
- package/dist/game/defineGame.d.ts +32 -3
- package/dist/game/dialogueGraph.d.ts +20 -0
- package/dist/game/dialogueGraph.js +107 -0
- package/dist/game/events.d.ts +11 -0
- package/dist/game/keyValueStore.d.ts +17 -4
- package/dist/game/keyValueStore.js +35 -23
- package/dist/game/lootModifiers.d.ts +17 -0
- package/dist/game/lootModifiers.js +26 -0
- package/dist/game/lootTable.d.ts +4 -0
- package/dist/game/lootTable.js +16 -0
- package/dist/game/narrativeAuthoring.d.ts +67 -0
- package/dist/game/narrativeAuthoring.js +122 -0
- package/dist/game/playableGame.d.ts +134 -35
- package/dist/game/quest.d.ts +15 -0
- package/dist/game/quest.js +209 -63
- package/dist/game/questCatalog.d.ts +50 -0
- package/dist/game/questCatalog.js +205 -0
- package/dist/game/saveStore.d.ts +6 -5
- package/dist/game/saveStore.js +57 -57
- package/dist/game/systemRuntime.d.ts +2 -2
- package/dist/game/systemRuntime.js +84 -33
- package/dist/game/toasts.d.ts +8 -0
- package/dist/game/toasts.js +7 -0
- package/dist/game/vfxInstance.d.ts +9 -0
- package/dist/game/vfxInstance.js +12 -1
- package/dist/game/viewports.d.ts +43 -0
- package/dist/game/viewports.js +46 -0
- package/dist/game/worldItem.d.ts +5 -1
- package/dist/game/worldItem.js +0 -1
- package/dist/gameplay.d.ts +3 -0
- package/dist/gameplay.js +3 -0
- package/dist/input/actionBindings.d.ts +15 -1
- package/dist/input/actionBindings.js +14 -3
- package/dist/input/actionContexts.d.ts +25 -1
- package/dist/input/actionContexts.js +82 -25
- package/dist/input/axisInput.d.ts +4 -0
- package/dist/input/axisInput.js +4 -0
- package/dist/input/axisShaper.d.ts +73 -0
- package/dist/input/axisShaper.js +105 -0
- package/dist/input/gamepadModel.d.ts +36 -2
- package/dist/input/gamepadModel.js +69 -30
- package/dist/input/haptics.d.ts +50 -0
- package/dist/input/haptics.js +74 -0
- package/dist/input/inputBuffer.d.ts +40 -0
- package/dist/input/inputBuffer.js +59 -0
- package/dist/input/lookChannel.d.ts +10 -0
- package/dist/input/lookChannel.js +6 -0
- package/dist/input/pointer.d.ts +2 -0
- package/dist/input/pointer.js +4 -0
- package/dist/interaction/promptRegistry.d.ts +5 -4
- package/dist/interaction/promptRegistry.js +8 -2
- package/dist/inventory/inventoryModel.js +9 -6
- package/dist/inventory/shapedGrid.d.ts +48 -9
- package/dist/inventory/shapedGrid.js +69 -10
- package/dist/item/affix.d.ts +6 -0
- package/dist/item/affix.js +6 -0
- package/dist/item/durability.js +4 -0
- package/dist/item/itemInstanceRegistry.d.ts +32 -1
- package/dist/item/itemInstanceRegistry.js +111 -7
- package/dist/item/itemgen.d.ts +1 -1
- package/dist/item/itemgen.js +1 -1
- package/dist/item/modularItem.js +7 -1
- package/dist/material/appearanceSignals.d.ts +53 -0
- package/dist/material/appearanceSignals.js +47 -0
- package/dist/material/hairCards.d.ts +47 -0
- package/dist/material/hairCards.js +136 -0
- package/dist/material/materialAsset.d.ts +162 -0
- package/dist/material/materialAsset.js +382 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +444 -1
- package/dist/movement/characterController.d.ts +5 -2
- package/dist/movement/characterController.js +50 -23
- package/dist/movement/freeFlight.d.ts +2 -2
- package/dist/movement/freeFlight.js +3 -3
- package/dist/movement/glideModel.d.ts +3 -0
- package/dist/movement/glideModel.js +6 -0
- package/dist/movement/grappleSwing.d.ts +2 -0
- package/dist/movement/grappleSwing.js +5 -1
- package/dist/movement/movementModel.d.ts +63 -1
- package/dist/movement/movementModel.js +136 -26
- package/dist/movement/movementProbe.d.ts +51 -0
- package/dist/movement/movementProbe.js +119 -0
- package/dist/movement/playerMovement.d.ts +48 -2
- package/dist/movement/playerMovement.js +493 -87
- package/dist/movement/poseState.d.ts +2 -0
- package/dist/movement/poseState.js +29 -1
- package/dist/movement/solidObstacles.d.ts +11 -1
- package/dist/movement/solidObstacles.js +48 -3
- package/dist/movement/terrainGrade.d.ts +13 -0
- package/dist/movement/terrainGrade.js +33 -0
- package/dist/movement/voxelController.d.ts +2 -2
- package/dist/movement/voxelController.js +10 -10
- package/dist/nav/navFromEnvironment.d.ts +9 -3
- package/dist/nav/navFromEnvironment.js +18 -12
- package/dist/nav/navGrid.d.ts +19 -1
- package/dist/nav/navGrid.js +26 -10
- package/dist/nav/navMesh.d.ts +43 -2
- package/dist/nav/navMesh.js +534 -128
- package/dist/nav/solidRoute.d.ts +50 -0
- package/dist/nav/solidRoute.js +144 -0
- package/dist/physics/aircraftDynamics.d.ts +390 -0
- package/dist/physics/aircraftDynamics.js +704 -0
- package/dist/physics/ballisticSweep.d.ts +10 -3
- package/dist/physics/ballisticSweep.js +53 -29
- package/dist/physics/boatDynamics.d.ts +116 -0
- package/dist/physics/boatDynamics.js +160 -0
- package/dist/physics/drivableVehicle.d.ts +22 -8
- package/dist/physics/drivableVehicle.js +4 -2
- package/dist/physics/flightDynamics.d.ts +21 -4
- package/dist/physics/flightDynamics.js +28 -5
- package/dist/physics/forceVolume.d.ts +41 -0
- package/dist/physics/forceVolume.js +88 -0
- package/dist/physics/handlingProbe.d.ts +249 -0
- package/dist/physics/handlingProbe.js +606 -0
- package/dist/physics/kinematicVehicle.d.ts +40 -5
- package/dist/physics/kinematicVehicle.js +17 -2
- package/dist/physics/physicsBackend.d.ts +12 -0
- package/dist/physics/physicsBackendConformance.js +34 -0
- package/dist/physics/physicsWorldBackend.d.ts +1 -0
- package/dist/physics/physicsWorldBackend.js +52 -3
- package/dist/physics/spatialGrid.d.ts +1 -1
- package/dist/physics/spatialGrid.js +8 -9
- package/dist/physics/vehicleBackendLink.d.ts +49 -0
- package/dist/physics/vehicleBackendLink.js +102 -0
- package/dist/physics/vehicleDynamics.d.ts +375 -0
- package/dist/physics/vehicleDynamics.js +737 -0
- package/dist/physics/vehicleObstacles.d.ts +3 -2
- package/dist/physics/vehicleObstacles.js +29 -5
- package/dist/physics/worldColliders.d.ts +1 -1
- package/dist/physics/worldColliders.js +35 -3
- package/dist/puzzle/cellGrid.d.ts +15 -9
- package/dist/puzzle/cellGrid.js +13 -9
- package/dist/render/lookPreset.d.ts +9 -2
- package/dist/render/lookPreset.js +29 -2
- package/dist/render/postProcessing.d.ts +30 -0
- package/dist/render/postProcessing.js +1 -1
- package/dist/runtime/cameraDirector.d.ts +54 -3
- package/dist/runtime/cameraDirector.js +187 -0
- package/dist/runtime/commandDispatch.d.ts +14 -0
- package/dist/runtime/commandDispatch.js +19 -0
- package/dist/runtime/commandInput.d.ts +37 -0
- package/dist/runtime/commandInput.js +58 -0
- package/dist/runtime/commandRunner.d.ts +27 -0
- package/dist/runtime/commandRunner.js +31 -3
- package/dist/runtime/context/combat.d.ts +12 -4
- package/dist/runtime/context/combat.js +179 -11
- package/dist/runtime/context/deathLoot.d.ts +11 -8
- package/dist/runtime/context/deathLoot.js +17 -16
- package/dist/runtime/context/scene.d.ts +2 -0
- package/dist/runtime/context/scene.js +13 -21
- package/dist/runtime/descriptors/baseline.js +34 -3
- package/dist/runtime/descriptors/features.d.ts +2 -1
- package/dist/runtime/descriptors/features.js +18 -0
- package/dist/runtime/gameContext.js +86 -13
- package/dist/runtime/gameContextTypes.d.ts +33 -3
- package/dist/runtime/gameRuntime.d.ts +3 -1
- package/dist/runtime/gameRuntime.js +4 -0
- package/dist/runtime/headlessRunner.d.ts +3 -1
- package/dist/runtime/headlessRunner.js +11 -11
- package/dist/runtime/hostedGameRunner.d.ts +24 -1
- package/dist/runtime/hostedGameRunner.js +156 -10
- package/dist/runtime/hostedWorldSession.d.ts +17 -5
- package/dist/runtime/hostedWorldSession.js +154 -16
- package/dist/runtime/hostedWorldStore.d.ts +2 -2
- package/dist/runtime/inputRecorder.d.ts +1 -1
- package/dist/runtime/inputRecorder.js +21 -1
- package/dist/runtime/inputSnapshot.d.ts +22 -4
- package/dist/runtime/inputSnapshot.js +41 -3
- package/dist/runtime/localPlayers.d.ts +73 -0
- package/dist/runtime/localPlayers.js +119 -0
- package/dist/runtime/runtimeSave.d.ts +10 -6
- package/dist/runtime/runtimeSave.js +11 -5
- package/dist/runtime/simContext.d.ts +8 -3
- package/dist/runtime/simContext.js +7 -3
- package/dist/runtime/simSnapshot.js +2 -0
- package/dist/runtime/transport.d.ts +9 -0
- package/dist/runtime/transport.js +7 -1
- package/dist/runtime/worldReplication.d.ts +2 -0
- package/dist/runtime/worldReplication.js +1 -1
- package/dist/scene/assetCatalog.d.ts +2 -0
- package/dist/scene/behaviorRuntime.d.ts +38 -7
- package/dist/scene/behaviorRuntime.js +293 -30
- package/dist/scene/behaviors.d.ts +35 -2
- package/dist/scene/behaviors.js +29 -0
- package/dist/scene/entityStore.d.ts +14 -3
- package/dist/scene/entityStore.js +45 -9
- package/dist/scene/mount.d.ts +9 -0
- package/dist/scene/mount.js +35 -0
- package/dist/scene/possession.d.ts +2 -0
- package/dist/scene/possession.js +21 -1
- package/dist/scene/sceneKinds.d.ts +9 -0
- package/dist/scene/sceneRaycast.d.ts +7 -0
- package/dist/scene/sceneRaycast.js +55 -0
- package/dist/scene/spatial.d.ts +20 -1
- package/dist/scene/spatial.js +94 -2
- package/dist/scene/vehicleSeat.d.ts +41 -3
- package/dist/scene/vehicleSeat.js +94 -3
- package/dist/sensor/concealment.d.ts +6 -0
- package/dist/sensor/concealment.js +8 -0
- package/dist/sensor/freezeMonitor.d.ts +6 -0
- package/dist/sensor/freezeMonitor.js +8 -0
- package/dist/sensor/perception.d.ts +11 -1
- package/dist/sensor/perception.js +57 -10
- package/dist/sensor/recordingBuffer.d.ts +3 -0
- package/dist/sensor/recordingBuffer.js +8 -0
- package/dist/session/roundState.d.ts +4 -0
- package/dist/session/roundState.js +11 -0
- package/dist/settings/frameRateLimit.d.ts +16 -0
- package/dist/settings/frameRateLimit.js +37 -0
- package/dist/settings/graphicsProfile.d.ts +2 -0
- package/dist/settings/graphicsProfile.js +12 -0
- package/dist/settings/graphicsSettings.d.ts +40 -0
- package/dist/settings/graphicsSettings.js +81 -0
- package/dist/settings/settingsModel.d.ts +9 -1
- package/dist/settings/settingsModel.js +10 -0
- package/dist/stats/accumulatorMeter.d.ts +10 -0
- package/dist/stats/accumulatorMeter.js +6 -0
- package/dist/stats/eventMeter.d.ts +10 -3
- package/dist/stats/eventMeter.js +6 -3
- package/dist/store/defineStore.d.ts +3 -1
- package/dist/store/defineStore.js +3 -1
- package/dist/store/observableKeyedStore.d.ts +1 -1
- package/dist/store/observableKeyedStore.js +8 -6
- package/dist/time/simClock.d.ts +6 -2
- package/dist/time/simClock.js +5 -0
- package/dist/vfx/feedbackMixer.d.ts +61 -0
- package/dist/vfx/feedbackMixer.js +106 -0
- package/dist/vfx/particleDirector.d.ts +58 -6
- package/dist/vfx/particleDirector.js +113 -22
- package/dist/vfx/particles.d.ts +68 -5
- package/dist/vfx/particles.js +284 -32
- package/dist/visibility/assetStreaming.d.ts +21 -3
- package/dist/visibility/assetStreaming.js +108 -24
- package/dist/visibility/settings.d.ts +8 -2
- package/dist/visibility/settings.js +3 -0
- package/dist/visibility/simulationCulling.d.ts +8 -1
- package/dist/visibility/simulationCulling.js +7 -0
- package/dist/visibility/spatialIndex.d.ts +21 -0
- package/dist/visibility/spatialIndex.js +16 -0
- package/dist/visibility/visibilitySystem.js +1 -0
- package/dist/work/expedition.d.ts +110 -0
- package/dist/work/expedition.js +195 -0
- package/dist/work/staffedStation.d.ts +29 -0
- package/dist/work/staffedStation.js +27 -0
- package/dist/world/authoredAnimation.d.ts +38 -0
- package/dist/world/authoredAnimation.js +99 -0
- package/dist/world/authoredObjects.d.ts +24 -4
- package/dist/world/authoredObjects.js +43 -16
- package/dist/world/authoredSimulation.d.ts +74 -0
- package/dist/world/authoredSimulation.js +302 -0
- package/dist/world/authoredSolids.d.ts +46 -0
- package/dist/world/authoredSolids.js +75 -0
- package/dist/world/authoredWeather.d.ts +120 -0
- package/dist/world/authoredWeather.js +314 -0
- package/dist/world/buildingGenerator.js +2 -2
- package/dist/world/buildingKit.d.ts +11 -4
- package/dist/world/buildingKit.js +21 -8
- package/dist/world/buildings.d.ts +34 -2
- package/dist/world/buildings.js +11 -1
- package/dist/world/cityKind.d.ts +7 -0
- package/dist/world/cityKind.js +52 -1
- package/dist/world/environmentForces.d.ts +21 -0
- package/dist/world/environmentForces.js +42 -0
- package/dist/world/features.d.ts +32 -5
- package/dist/world/features.js +6 -0
- package/dist/world/footprintGrid.d.ts +62 -0
- package/dist/world/footprintGrid.js +217 -0
- package/dist/world/interiors.d.ts +1 -0
- package/dist/world/interiors.js +5 -3
- package/dist/world/lod.d.ts +6 -0
- package/dist/world/lod.js +8 -0
- package/dist/world/pathTerrain.d.ts +1 -1
- package/dist/world/terraform.d.ts +9 -1
- package/dist/world/terraform.js +53 -18
- package/dist/world/walls.d.ts +7 -0
- package/dist/world/walls.js +7 -0
- package/dist/world/weather.d.ts +17 -0
- package/dist/world/weather.js +116 -20
- package/dist/world/worldSolids.d.ts +95 -0
- package/dist/world/worldSolids.js +351 -0
- package/dist/world.d.ts +6 -6
- package/dist/world.js +5 -5
- package/package.json +1 -1
- package/skills/ce-handoff/SKILL.md +2 -2
- package/skills/game-design/SKILL.md +2 -2
- package/skills/jgengine/SKILL.md +28 -6
- package/skills/jgengine/api.md +139 -99
- package/skills/jgengine/capabilities.md +28 -0
- package/skills/jgengine/recipes/minimal-game.md +10 -2
- package/skills/jgengine-combat/SKILL.md +13 -2
- package/skills/jgengine-combat/api.md +150 -103
- package/skills/jgengine-combat/capabilities.md +29 -1
- package/skills/jgengine-combat/recipes/portable-runtime-state.md +60 -6
- package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +37 -3
- package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
- package/skills/jgengine-combat/reference.md +24 -2
- package/skills/jgengine-gameplay/SKILL.md +63 -0
- package/skills/jgengine-gameplay/api.md +583 -463
- package/skills/jgengine-gameplay/capabilities.md +92 -0
- package/skills/jgengine-gameplay/recipes/device-save-status.md +46 -0
- package/skills/jgengine-gameplay/recipes/expeditions.md +108 -0
- package/skills/jgengine-gameplay/recipes/narrative-authoring.md +76 -0
- package/skills/jgengine-gameplay/recipes/paged-item-storage.md +101 -0
- package/skills/jgengine-gameplay/recipes/quest-authoring.md +63 -0
- package/skills/jgengine-gameplay/recipes/shaped-inventory.md +50 -0
- package/skills/jgengine-gameplay/reference-content-validation.md +74 -0
- package/skills/jgengine-gameplay/reference-systems.md +123 -0
- package/skills/jgengine-world/SKILL.md +25 -4
- package/skills/jgengine-world/api.md +1045 -655
- package/skills/jgengine-world/capabilities.md +246 -9
- package/skills/jgengine-world/recipes/character-animation.md +62 -0
- package/skills/jgengine-world/recipes/controls.md +48 -0
- package/skills/jgengine-world/recipes/flight-feel.md +76 -0
- package/skills/jgengine-world/recipes/npc-senses.md +55 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +87 -0
- package/skills/jgengine-world/reference.md +180 -4
- package/skills/jgengine-world/visibility.md +139 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Recipe — flight feel (aircraft as physical numbers)
|
|
2
|
+
|
|
3
|
+
**What this wires:** an aircraft that flies the way the brief says. There is no default jet or plane: every aircraft is a mass, an inertia tensor, a set of surfaces and an engine the game picks, checked against numbers it writes down first.
|
|
4
|
+
|
|
5
|
+
## The seams
|
|
6
|
+
|
|
7
|
+
- **Sim.** `createRigidAircraft(tuning, { position, velocity, heading | orientation, wind?, groundHeight?, gravityField?, airDensity? })` from `@jgengine/core/physics/aircraftDynamics`. It is a quaternion rigid body: surface forces act at their mount points on the inertia tensor, so nothing commands a rotation rate. `snapshot()`/`restore()` for prediction and replay, `retune()` for damage, upgrades and staging, per-tick `modifiers` (`thrustScale`, `liftScale`, `dragScale`, extra `force`/`torque`).
|
|
8
|
+
- **Body frame.** `[left, up, forward]` about the centre of mass. A wing panel sits at `[±semiSpan/2, 0, 0]`, a tailplane at `[0, 0, -tailArm]`, a fin at `[0, finHeight, -tailArm]` with `normal: [1, 0, 0]`.
|
|
9
|
+
- **Surfaces.** Each one has `area` (m²), `liftSlope` (per rad), `stallAngle` (rad), `postStallLift` (share of peak kept past the stall), `incidence` (trim), and the drag polar `cd0` + `inducedDrag·CL²`. Split the wing into left and right panels so ailerons have a roll arm.
|
|
10
|
+
- **Controls.** `control: { pitch?, roll?, yaw? }` on a surface is AoA change per rad of that channel's deflection. The sim signs it from the lever arm, so positive input is always nose up, right roll, nose right. Elevons are one surface with both `pitch` and `roll`. `controls.<channel>` sets `maxDeflection` and actuator `rate`.
|
|
11
|
+
- **Rotors.** A `rotor` block makes the same body a helicopter: `maxThrust` (N, full collective and rotor speed), `radius` (m), hub `at`, `torque` (N·m at full collective, positive yaws the nose left), `cyclic` disc tilt, `damping`, `spoolRate` and an optional `tail` rotor (`maxThrust`, `at`, `sideDamping`).
|
|
12
|
+
- Throttle spins the rotor up; `input.collective` sets thrust; the pitch and roll channels tilt the disc; yaw drives the tail rotor.
|
|
13
|
+
- Neutral pedal gives no tail thrust, so the nose swings with the torque until the pilot (or SAS) holds it. Hover pedal is `torque·collective / (tail.maxThrust · tailArm)`.
|
|
14
|
+
- Thrust follows momentum theory: translational lift once clean air reaches the induced velocity `√(T/2ρA)`, less thrust while climbing through the disc, and Cheeseman–Bennett ground effect below about one rotor diameter.
|
|
15
|
+
- The step's `rotor` telemetry (`speed`, `thrust`, `torque`, `groundEffect`, `translationalLift`) drives rotor sound, dust and HUD.
|
|
16
|
+
- **Rockets.** A `motor` block: `thrustCurve` (`[seconds, N]` points over burn time), `propellantKg` on top of `massKg`, `massFlow` (kg/s at peak thrust; it scales with thrust), nozzle `at` and `gimbal` (rad, on the pitch and yaw actuators).
|
|
17
|
+
- Throttle above zero burns and advances the burn clock; throttle `0` coasts with the clock held.
|
|
18
|
+
- Mass and inertia fall as propellant leaves (inertia scales with total over dry mass), so acceleration and turn rate rise through the burn.
|
|
19
|
+
- Stage by `retune(nextStage)`: a new `motor` object loads its propellant and restarts its clock; the same object keeps both. Move the pose to the new stage's centre of mass with `restore` first if the stages differ in length.
|
|
20
|
+
- The step reports `massKg` and `motor` (`thrust`, `burnTime`, `propellantKg`, `burnedOut`).
|
|
21
|
+
- **Assists.** An `assists` block is a flight computer on the same actuators, so it never exceeds the airframe's authority, and full stick on an axis is always the pilot's.
|
|
22
|
+
- `sas: { pitch, roll, yaw }` (`0..1`) damps body rates and, hands-off, holds the attitude or heading at release with a learned trim. `sasRate` sets how firm (rad/s for full control). On a helicopter, `sas.yaw` holds heading against rotor torque with no pedal.
|
|
23
|
+
- `autoLevel` (`0..1`) flies the wings level and the nose to the horizon hands-off.
|
|
24
|
+
- `maxAngleOfAttack` (rad) and `maxG` cut the pitch command before the limit; `step.limited` reports it.
|
|
25
|
+
- `hoverHold` (`0..1`) flies hands-off cyclic to a stop over the ground: position → velocity → attitude → rate.
|
|
26
|
+
- `policy(context, command)` runs last and returns the command the actuators get: replace any built-in with the game's own.
|
|
27
|
+
- `step.command` is what the actuators got. Toggle assists with `retune({ ...aircraft.tuning(), assists })`.
|
|
28
|
+
- **Pose.** Set the entity's `rotationY` from `step.heading` and apply the rest of `step.orientation` to the mesh (the heading-free part: `qY(-heading) · orientation`). `step.pitch`/`step.bank` match three.js `Euler(-pitch, heading, bank, "YXZ")`; `aircraftAttitudeQuaternion(heading, pitch, bank)` goes the other way for spawns.
|
|
29
|
+
- **Camera.** The chase rig (`rig: "chase"`) follows heading; give it `frustum: { far }` in the thousands and a `yawResponse` around 4 so the boom swings smoothly over the top of a loop.
|
|
30
|
+
- **Telemetry.** `airspeed`, `angleOfAttack`, `sideslip`, `gLoad`, `stalled`/`stallFraction`, `thrust`, body rates and `deflection` drive the HUD, stall horn, wind noise and camera shake.
|
|
31
|
+
|
|
32
|
+
## Workflow
|
|
33
|
+
|
|
34
|
+
1. **Write the feel as numbers** on the `FlightReport` fields. Some examples:
|
|
35
|
+
- "Nimble aerobatic" is `rollRateDeg > 300` and `sustainedTurnRateDeg > 18`.
|
|
36
|
+
- "Heavy transport" is `rollRateDeg` 40–80 and `stallSpeed` 50–70.
|
|
37
|
+
- "Responsive jet" is `throttleResponse < 2`.
|
|
38
|
+
- "Easy hover" is `hoverDriftMeters < 5` with the game's assists on.
|
|
39
|
+
2. **Put them in a test** next to the tuning: `measureFlight((spawn) => createRigidAircraft(tuning, spawn), { cruiseSpeed })` from `@jgengine/core/physics/handlingProbe`. It flies deterministic autopilots through a full-aileron roll, full-throttle banked turns, an idle deceleration, a speed-held climb, a throttle step and a hands-off hover. Fields that don't apply are `NaN`: turn and stall need wings, hover drift needs a rotor.
|
|
40
|
+
3. **Start from the real aircraft**: mass, wing area, span, tail arm, thrust. Inertia is roughly `m·(span/4)²` for roll and `m·(length/4)²` for pitch and yaw.
|
|
41
|
+
4. **Trim it.** With the wing on the centre of mass, set wing `incidence` to the cruise lift coefficient over the lift slope, `W / (½ρV²·S·a)`, and leave the tail at `0`. It then flies level hands-off at that speed.
|
|
42
|
+
5. **Drive it**: `bun run drive flight --key KeyR+KeyS:10500 --key KeyD:1500 --record <name> --record-fps 20`, or `bun run drive flight-heli --key KeyR:1500 --wait 3000 --key KeyE:3500 --record <name> --record-fps 20` for the helicopter, or `bun run drive flight-rocket --key KeyR:400 --wait 12000 --record <name> --record-fps 20` for the two-stage rocket.
|
|
43
|
+
|
|
44
|
+
## Symptom → knob
|
|
45
|
+
|
|
46
|
+
| Symptom | Knob (direction) |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| Pitches up or down hands-off | wing `incidence` ↓ / ↑; wing further aft of the centre of mass ↑ stability |
|
|
49
|
+
| Loops too slowly | tail `control.pitch` ↑ (more AoA), entry speed or thrust ↑, `inertia.pitch` ↓ |
|
|
50
|
+
| Stalls in every hard pull | tail `control.pitch` ↓ so the elevator can't reach the stall angle, or wing `stallAngle` ↑ |
|
|
51
|
+
| Stall is a cliff / too gentle | `postStallLift` ↓ / ↑ |
|
|
52
|
+
| Rolls too slowly / too fast | aileron `control.roll` ↑ / ↓, wing panel `at` further out ↑ damping; `inertia.roll` sets how fast it gets there |
|
|
53
|
+
| Nose wanders, won't track | fin `area` ↑ or fin further aft |
|
|
54
|
+
| Controls feel laggy | `controls.<channel>.rate` ↑ |
|
|
55
|
+
| Top speed wrong | `engine.maxThrust`, `dragArea`, surface `cd0` |
|
|
56
|
+
| Bleeds speed in turns | `inducedDrag` ↓, wing `area` ↑ (lower CL for the same lift) |
|
|
57
|
+
| Throttle feels sluggish | `engine.spoolRate` ↑ |
|
|
58
|
+
| Helicopter won't hover / climbs away | hover collective is `weight / maxThrust` out of ground effect; `rotor.maxThrust` sets how much margin there is |
|
|
59
|
+
| Nose swings too hard without pedal | `rotor.torque` ↓, `inertia.yaw` ↑, `tail.sideDamping` ↑ |
|
|
60
|
+
| Pedal too weak to stop a swing | `tail.maxThrust` ↑ or tail rotor further aft |
|
|
61
|
+
| Cyclic twitchy / sluggish | `rotor.cyclic` ↓ / ↑, hub height (`rotor.at[1]`) ↓ / ↑, `rotor.damping` ↑ / ↓ |
|
|
62
|
+
| Pedal also rolls the body | tail rotor `at[1]` above the centre of mass; move it down or trim with cyclic |
|
|
63
|
+
| Rocket weathercocks too hard / tumbles | fin `area` ↓ / ↑, fins further aft for more stability |
|
|
64
|
+
| Gimbal too twitchy / can't steer | `motor.gimbal` ↓ / ↑; lighter late in the burn it turns faster on its own |
|
|
65
|
+
| Burns too long / short | `propellantKg` over `massFlow` sets burn time; the curve's last point cuts it off first |
|
|
66
|
+
| Helicopter too hard to hover on a keyboard | `assists.sas.yaw` 1 for heading hold, `assists.hoverHold` for cyclic; collective stays the pilot's |
|
|
67
|
+
| Assisted aircraft oscillates | `assists.sasRate` ↑ (softer), strengths ↓ |
|
|
68
|
+
| Players stall it pulling hard | `assists.maxAngleOfAttack` just under the wing `stallAngle`, or `maxG` |
|
|
69
|
+
| Sinks when it slows down | that is translational lift going away; `rotor.translationalLift` sets how much |
|
|
70
|
+
|
|
71
|
+
## Traps
|
|
72
|
+
|
|
73
|
+
- `createAircraftDynamics` (`physics/flightDynamics`) is deprecated: it commands rotation and can't loop. The CHANGELOG Migrate note maps its fields onto `createRigidAircraft`.
|
|
74
|
+
- Setting orientation or angular velocity by hand defeats the model. Change a surface, the inertia or the control authority.
|
|
75
|
+
- A single centreline wing has no roll arm, so ailerons on it do nothing. Split it into left and right panels.
|
|
76
|
+
- For multiplayer, send inputs, not poses. Predict with the same tuning and `restore(serverState)` on correction.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Recipe — NPC senses (perceive → remember → decide → route)
|
|
2
|
+
|
|
3
|
+
**What this wires:** an NPC that notices things, remembers where they were, acts on
|
|
4
|
+
that memory, and gives up when it goes stale. A guard that hears a sprint and
|
|
5
|
+
checks it out, a mob that leashes home, a pedestrian that flees a gunshot and a
|
|
6
|
+
patrol car that drives to the last sighting are all this loop with different
|
|
7
|
+
facts, graphs and speeds. No archetypes: the NPC is data plus a few named actions.
|
|
8
|
+
|
|
9
|
+
Working example: the `guard-probe` dev demo (`apps/dev/src/demo/guardProbe.ts`),
|
|
10
|
+
`bun run drive guard-probe`.
|
|
11
|
+
|
|
12
|
+
## The seams
|
|
13
|
+
|
|
14
|
+
- **Perceive → memory.** One `createPerception` per group of NPCs that share
|
|
15
|
+
senses. Each tick, push the stimuli your game produces (`sound` with a
|
|
16
|
+
`loudness` that scales range, `damage`) and call `observe(npc, candidates, nowMs)`
|
|
17
|
+
with a *bounded* candidate list from a spatial query, never every entity. Pass
|
|
18
|
+
`occluded` for walls. Memory decays linearly over `memorySeconds`; each observer
|
|
19
|
+
judges a stimulus once.
|
|
20
|
+
- **Memory → blackboard.** A small system copies the facts the graph needs into
|
|
21
|
+
the NPC's persistent blackboard: `behaviorControl(ctx).blackboard(id)`. Write
|
|
22
|
+
plain scalars (`alerted`, `lastKnownX`, `lastKnownZ`, `confidence`). Actions
|
|
23
|
+
and conditions read facts; they do not query the world for them.
|
|
24
|
+
- **Blackboard → decision.** The NPC's `decisionGraph` behavior is data:
|
|
25
|
+
conditions on blackboard keys, `memory` sequences for multi-step errands
|
|
26
|
+
(go → `wait` → give up), `cooldown` for barks, `random` (drawn from `ctx.rng`)
|
|
27
|
+
for idle variety. Register actions by name with `registerBehaviorActions`, and
|
|
28
|
+
release movement or claims in `onAbort` when a higher branch pre-empts them.
|
|
29
|
+
Set `thinkInterval` so a crowd thinks a few times a second.
|
|
30
|
+
- **Decision → route.** Actions that move ask one `createNavMeshQuery(mesh)` per
|
|
31
|
+
mesh for `findPath` and replan only when the goal moves. Bake the mesh from
|
|
32
|
+
scene geometry with `@jgengine/navbake` (`await initNavBake()`, then
|
|
33
|
+
`bakeNavMesh`) and store it as an editor `nav` bake; price hazards or lock doors
|
|
34
|
+
with `areaCosts` through `retune`.
|
|
35
|
+
|
|
36
|
+
## Gotchas
|
|
37
|
+
|
|
38
|
+
For an NPC that only acquires a hostile player, chases to reach and applies an effect on cooldown, attach the shared behavior instead of a graph or a per-game pursuit map:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
ctx.scene.entity.spawn("guard", {
|
|
42
|
+
behaviors: [pursue({ aggroRadius: 12, reach: 1.2, leashRange: 24, speed: 3,
|
|
43
|
+
attack: { effect: "damage", amount: 4, intervalSec: 1.5 }, thinkInterval: 0.1,
|
|
44
|
+
threat: { decayPerSecond: 0.5 } })],
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Import `pursue` from `@jgengine/core/world`; configure the target's `receive` policy in content. The host runs the behavior and GameContext saves its home/cooldown/scheduling state. Game effects can add threat through `behaviorControl(ctx).threat(id)?.add(sourceId, amount)`; explicit `setTarget` has priority. Its movement uses shared solids without route planning. Keep perception memory and navigation in the seams above when the NPC needs occlusion, investigation or a detour.
|
|
49
|
+
|
|
50
|
+
- Hand the graph facts, not objects: the blackboard serializes with the behavior,
|
|
51
|
+
so saves and replays keep an NPC mid-investigation.
|
|
52
|
+
- Judge footstep noise on speed averaged over a window, not one frame; frame-time
|
|
53
|
+
jitter otherwise reads as a sprint.
|
|
54
|
+
- Record the stimulus you handled (`handledAt`) so giving up does not
|
|
55
|
+
re-alert on the same memory next tick.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Recipe — vehicle feel (feel target → metrics → physical knobs)
|
|
2
|
+
|
|
3
|
+
**What this wires:** a ground vehicle that feels the way the brief says, from sim to camera, sound and rumble. There is no default car: every vehicle is a set of physical numbers the game picks for its own feel, checked against metrics it writes down first.
|
|
4
|
+
|
|
5
|
+
## The seams
|
|
6
|
+
|
|
7
|
+
- **Sim.** `createVehicleDynamics(tuning, { surfaceFriction?, clampMove?, groundHeight? })` from `@jgengine/core/physics/vehicleDynamics`. Yaw comes from tire forces, so balance, slides and recovery are outcomes of the numbers, not special cases. It has `snapshot()`/`restore()` for prediction and replay, `retune()` for upgrades and damage, and per-tick modifiers (`driveScale`, `gripScale`, `steerScale`, `brakeScale`, `thrust` for boost).
|
|
8
|
+
- **Terrain.** Add a `suspension` block (spring and damper rates, travel, ride height, anti-roll) and pass `groundHeight` for ramps, hills and jumps. Each wheel then samples the ground, the body heaves, pitches and rolls on real springs, slopes pull the car downhill, and the step reports `airborne`, `landingSpeed` and per-corner `wheelLoads`. Without the block the car stays on a flat plane.
|
|
9
|
+
- **Input.** Put `createAxisShaper` (`@jgengine/core/input/axisShaper`) between `ctx.input.axis(...)` and the sim. Keys get `riseRate`/`returnRate`, so a tap steers a little and a hold builds up; sticks get `deadzone`/`curve` with no lag. Pass `{ analog: analogAxes(bindings, ctx.input.analog()) }` each frame so each axis picks the right profile.
|
|
10
|
+
- **Pad feel.** Set the stick deadzone, curve and trigger deadzone once in `defineGame({ gamepad: { deadzone, curve, triggerDeadzone } })`. The shell applies it before any analog value reaches `ctx.input`, so per-axis shaping in `createAxisShaper` only adds ramps and speed scaling on top.
|
|
11
|
+
- **Jumps and air.** With `suspension`, a `jump` block (`speed`, `count` for double jumps, `window`) enables `car.jump()`, and an `air` block (`pitchAccel`/`yawAccel`/`rollAccel`, `damping`, `maxRate`) rotates the body in flight. Air input comes from `modifiers.air`; without it, throttle−brake pitches and steer yaws, which suits Rocket League-style play. Pass `{ pitch: 0, yaw: axis.steer, roll: 0 }` if W should keep driving instead of nosing down. `applyAngularImpulse` composes dodges and flips. The body stays near level, so full flips and wall-driving wait for the rigid-body backend.
|
|
12
|
+
- **Boats.** `createBoatDynamics` (`@jgengine/core/physics/boatDynamics`) takes the same axis input and returns a step `tickDrivableVehicle` and `measureHandling` accept. Its knobs are mass, length, beam, prop thrust and prop speed, `planingSpeed`, `keel` and `steering` (`rudder` with an area, or `outboard`). A tug and a speedboat differ only in those numbers.
|
|
13
|
+
- **Collision.** In a `PhysicsBackend` world (Rapier or `PhysicsWorld`), `createVehicleBackendLink` gives the car a kinematic chassis that shoves props, and a `clampMove` that stops it at walls. The sim keeps the handling, so the feel numbers above still hold. `createVehicleObstacleClamp` separates overlap recovery from permitted motion so moving solids cannot launch a car or produce false crash damage. Custom vehicle clamps that recover position return an XZ endpoint tuple with optional `motion` (permitted displacement in world units); a plain tuple keeps displacement-based velocity, and the callback runs once per sim step.
|
|
14
|
+
- **Two wheels.** A `lean` block (`maxLean`, `leanRate`, optional `countersteer`) turns the same sim into a motorcycle or bicycle.
|
|
15
|
+
- Steer asks for a lean, and the bar steers the balanced turn that lean needs.
|
|
16
|
+
- The bike tips the other way briefly as the lean starts, which is countersteer.
|
|
17
|
+
- The step reports the balance lean `atan(lateral g)` as `lean`, with `bodyRoll = -lean`. The requested lean is capped at what grip can hold.
|
|
18
|
+
- `wheelie`/`stoppie` flags come from axle loads. `assists.antiWheelie` and `assists.antiStoppie` cap drive and braking just under the pitch-over limit, and on springs they also back off as the axle goes light. A sprung bike needs them to stay playable.
|
|
19
|
+
- `measureLean` reports steady lean, time to lean and counter-lean.
|
|
20
|
+
- **Pose.** `tickDrivableVehicle(car, dt, ctx.input.axis(bindings, ranges), { groundHeight })` returns a `setPose` patch. Pitch and roll come from load transfer, or from the springs when `suspension` is set.
|
|
21
|
+
- **Camera.** `camera: { rig: "chase", chase: { fov, lead, bank, velocityYaw, yawResponse } }`. `velocityYaw` shows the car's side in a slide, and `fov` widens with speed.
|
|
22
|
+
- `headingSource: "body"` (default) follows the vehicle. For camera-relative walking, use `headingSource: "input"` so strafe/backpedal body turns cannot steer the next movement frame; yaw smoothing never overwrites input/aim heading. Board with `ctx.camera.setChaseTuning({ headingSource: "body", ...drivingFeel })`, then restore the authored input mode with `setChaseTuning(null)` on exit. Hood, cockpit and rear seat views always use body heading.
|
|
23
|
+
- Input-owned chase seeds its heading once from the first acquired target. Set `camera.initialYaw` to choose another starting direction; later target turns do not reseed it.
|
|
24
|
+
- Speed comes from the entity's sim `velocity`, so publish poses with `setPose({ ..., dt })` (`tickDrivableVehicle` does). `fov.response` eases the FOV.
|
|
25
|
+
- The boom holds its length at any speed. `distanceBySpeed: { extra }` pulls it back on purpose; `ctx.camera.kickFov(deg)` adds a decaying punch for landings and hits.
|
|
26
|
+
- `pitchFollow` tilts the boom with `rotationX` on ramps and slopes. `lookBackAction` names an input action that looks behind while held.
|
|
27
|
+
- The boom stops short of objects, terrain and walls through `ctx.scene.raycast`; `collision: false` turns that off.
|
|
28
|
+
- Switch views at runtime with `ctx.camera.setChaseTuning({ ...ctx.camera.chaseTuning(), view: nextChaseView(view) })` (`@jgengine/core/runtime/cameraDirector`).
|
|
29
|
+
- **Sound.** Call `ctx.game.audio.loop(id, sound)` once, then `setLoop` every tick:
|
|
30
|
+
- Engine: `rate` from `step.rpm`, `gain` from `step.engineLoad`.
|
|
31
|
+
- Tires: `gain` from how far `max(step.frontSaturation, step.rearSaturation)` exceeds ~0.85.
|
|
32
|
+
- Real engine samples: `createEngineLayers` (`@jgengine/core/audio/engineLayers`) takes N loops keyed by the rpm they were recorded at, crossfades the two around the current rpm, and picks on-load or off-load sets by `engineLoad`. Call `update(dt, { rpm, load })` then `play(ctx.game.audio, { at, velocity, gain, lowpass })` each tick.
|
|
33
|
+
- `setLoop` also takes `lowpass`/`highpass` cutoffs in Hz (muffle off-throttle) and `velocity`; a sound with `doppler: 1` pitches by listener and emitter velocity.
|
|
34
|
+
- **One place for all of it.** `createFeedbackMixer` (`@jgengine/core/vfx/feedbackMixer`) declares these mappings as routes: `{ signal, target, curve, attack, release }`, with `max` or `sum` combining and threshold `events` for one-shots like a landing thud. Update it with the step's telemetry each tick and apply its targets to `setLoop`, the chase camera and `haptics`, instead of hand-writing the glue.
|
|
35
|
+
- **Rumble.** Set continuous channels each tick on `ctx.input.haptics(userId)`: `set("road", { strong: rearSaturation, weak: frontSaturation })`, an `engine` hum from load and rpm, and `pulse("impact", { strong, weak, ms }, 1)` on a landing so it ducks the rest. The shell mixes them by priority onto that player's pad. `ctx.input.rumble` stays for a single fire-and-forget effect.
|
|
36
|
+
- **Sound.** Play a one-shot on `step.landingSpeed` (thud, suspension clunk).
|
|
37
|
+
- **Proof.** For jumps and air control, `measureAir` reports apex height and time, double-jump height, and air rotation rates.
|
|
38
|
+
- **Proof.** `measureHandling(() => createVehicleDynamics(tuning), options?)` from `@jgengine/core/physics/handlingProbe` returns deterministic numbers. Assert them in a test. For a sprung car, `measureRide` adds settle time, brake dive, roll per g and landing bounces.
|
|
39
|
+
|
|
40
|
+
## Workflow
|
|
41
|
+
|
|
42
|
+
1. **Write the feel as numbers before tuning.** Turn the brief into target ranges on the `HandlingReport` fields. Some examples:
|
|
43
|
+
- "Snappy" is `turnIn < 0.2`.
|
|
44
|
+
- "Planted" is `maxLateralG` close to the tire μ with `stepPeakSideslipDeg < 8`.
|
|
45
|
+
- "Drifty but safe" is `handbrakePeakSideslipDeg` 40–70 with `spun === false`.
|
|
46
|
+
- "Keyboard-friendly" is `spun` and `powerSteerSpun` both `false`, including at `cornerSpeed: 40`.
|
|
47
|
+
- "Neutral balance" is `measureCourse(...).understeerGradient` near 0–0.5 deg/g; "tail-happy" is below 0.
|
|
48
|
+
- "Agile" is a high `measureCourse(...).slalomSpeed`; "forgiving" is a short `handbrakeRecoverySeconds`.
|
|
49
|
+
2. **Put those ranges in a test first**, next to the tuning.
|
|
50
|
+
3. **Start from the real vehicle's physical numbers**: mass, wheelbase, weight split, CoM height, driven axle, torque and gears. Then move one knob at a time using the table below, rerunning the test after each change.
|
|
51
|
+
4. **Drive it.**
|
|
52
|
+
- In the engine repo: `bun run drive <game> --key KeyW:4000 --probe a --key KeyW+KeyD:1500 --probe b --record <name>`. Chords hold throttle and steer together.
|
|
53
|
+
- `--record-fps 20` keeps game time at 1:1, because each recorded frame advances at most one 50 ms step.
|
|
54
|
+
- Tests prove the numbers; the drive proves the camera and sound.
|
|
55
|
+
|
|
56
|
+
## Symptom → knob
|
|
57
|
+
|
|
58
|
+
| Symptom | Knob (direction) |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `understeerGradient` too high | front tire `peakSlipAngle` ↓ or rear ↑ (tire stiffness split is what moves it at moderate g) |
|
|
61
|
+
| Pushes wide, won't turn in | `rollStiffnessFront` ↓, rear `peakGrip` ↓ relative to front, `yawInertiaIndex` ↓, `steering.rate` ↑ |
|
|
62
|
+
| Snaps into oversteer | `rollStiffnessFront` ↑, rear `peakGrip` ↑, `slideGrip` ↑ (gentler breakaway), `assists.stability` ↑ |
|
|
63
|
+
| Spins holding throttle and steer | `assists.tractionControl` ↑ (budgets for cornering grip), `driveFront` ↑, peak torque ↓ |
|
|
64
|
+
| Slides feel uncatchable | `steering.selfAlign` ↑ (caster catches the slide), `slideGrip` ↑, `assists.maxSideslip` ↑ with `stability` ↑ |
|
|
65
|
+
| Too twitchy at speed | `steering.highSpeedAngle` ↓, `steering.rate` ↓, `yawInertiaIndex` ↑, shaper steer `digital.riseRate` ↓ |
|
|
66
|
+
| Keyboard steering is all-or-nothing | shaper steer `digital.riseRate` 2–4/s, `returnRate` 5–8/s |
|
|
67
|
+
| Stick feels twitchy on centre | shaper steer `analog.curve` 1.3–2, `analog.deadzone` 0.05–0.12 |
|
|
68
|
+
| Sluggish, boat-like | `yawInertiaIndex` ↓, `peakSlipAngle` ↓ (stiffer tire), `comHeight` ↓ |
|
|
69
|
+
| Handbrake does nothing / spins every time | `handbrakeGrip` ↓ / ↑, with `selfAlign` to set how it recovers |
|
|
70
|
+
| No top speed ceiling / wrong ceiling | `aero.dragArea`, power (`maxPower` or torque × gearing); `speedLimit` only for a hard governor |
|
|
71
|
+
| Boost or supersonic above the cap | `modifiers.thrust` (bypasses tires and the governor) |
|
|
72
|
+
| Floaty, wallowy body | `suspension.springRate` ↑, `damperRate` ↑ |
|
|
73
|
+
| Harsh, skips over bumps | `springRate` ↓, `travel` ↑ |
|
|
74
|
+
| Landings bounce back into the air | `suspension.reboundRate` ↑ (defaults to 1.5 × `damperRate`), `travel` ↑ |
|
|
75
|
+
| Leans too much / feels flat | `suspension.antiRoll` ↑ / ↓; `rollStiffnessFront` still sets which axle takes it |
|
|
76
|
+
| Jump too floaty / too short | `jump.speed` (apex ≈ speed² / 2g); `jump.count: 2` plus `window` for a double jump |
|
|
77
|
+
| Air rotation too twitchy / sluggish | `air.*Accel` for how fast it builds, `air.maxRate` for the ceiling, `air.damping` for how fast it stops |
|
|
78
|
+
| Bike wheelies on launch / flips over the bars braking | `assists.antiWheelie` / `assists.antiStoppie` ↑ (1 keeps both wheels down; 0.6 lets it lift briefly) |
|
|
79
|
+
| Bike tips in lazily / nervously | `lean.leanRate` ↑ / ↓ (2 tourer … 5 sport); `lean.countersteer` sets the wrong-way tip at the start |
|
|
80
|
+
| Rolls over in corners | `comHeight` ↓ or `trackWidth` ↑: it tips over at about `trackWidth / (2 · comHeight)` g |
|
|
81
|
+
|
|
82
|
+
## Traps
|
|
83
|
+
|
|
84
|
+
- Hand-editing `yawRate` or heading to "fix" a turn defeats the model. Change a physical knob instead.
|
|
85
|
+
- `speedLimit` fades drive out and never clamps velocity. A car pinned at its cap can still turn, and thrust can carry it past the cap.
|
|
86
|
+
- A slide that looks right on a gamepad can spin on a keyboard. Assert `powerSteerSpun` and `spun` with the default `stepSteer: 1`, which is a held key.
|
|
87
|
+
- For multiplayer, send inputs, not poses. Predict locally with the same tuning, and on a correction call `restore(serverState)` and replay the buffered inputs.
|