@jgengine/core 0.18.1 → 0.19.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 +158 -7
- package/dist/ai/decisionGraph.d.ts +48 -4
- package/dist/ai/decisionGraph.js +153 -23
- 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 +11 -0
- package/dist/anim/animGraph.js +128 -0
- 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 +9 -0
- package/dist/anim/locomotionGraph.js +29 -0
- package/dist/audio/audioFalloff.d.ts +2 -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/combat/abilityKit.d.ts +1 -0
- package/dist/combat/abilityKit.js +1 -0
- package/dist/combat/death.d.ts +7 -0
- 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/devtools/warnOnce.d.ts +8 -0
- package/dist/devtools/warnOnce.js +18 -0
- package/dist/editor/document.js +3 -1
- package/dist/editor/environment.js +8 -0
- package/dist/editor/types.d.ts +4 -0
- package/dist/faction/reputation.d.ts +6 -0
- package/dist/faction/reputation.js +11 -0
- package/dist/game/cameraConfig.d.ts +51 -1
- package/dist/game/events.d.ts +6 -0
- package/dist/game/lootTable.d.ts +4 -0
- package/dist/game/lootTable.js +16 -0
- package/dist/game/playableGame.d.ts +84 -17
- 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/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/item/affix.d.ts +6 -0
- package/dist/item/affix.js +6 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +112 -1
- 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 +39 -0
- package/dist/movement/movementModel.js +62 -19
- package/dist/movement/movementProbe.d.ts +51 -0
- package/dist/movement/movementProbe.js +119 -0
- package/dist/movement/playerMovement.d.ts +21 -2
- package/dist/movement/playerMovement.js +89 -6
- package/dist/movement/solidObstacles.d.ts +9 -1
- package/dist/movement/solidObstacles.js +44 -2
- package/dist/movement/voxelController.js +7 -7
- package/dist/nav/navFromEnvironment.d.ts +9 -3
- package/dist/nav/navFromEnvironment.js +18 -12
- package/dist/nav/navMesh.d.ts +43 -2
- package/dist/nav/navMesh.js +534 -128
- package/dist/physics/aircraftDynamics.d.ts +390 -0
- package/dist/physics/aircraftDynamics.js +704 -0
- 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/handlingProbe.d.ts +249 -0
- package/dist/physics/handlingProbe.js +606 -0
- package/dist/physics/kinematicVehicle.d.ts +32 -2
- package/dist/physics/kinematicVehicle.js +15 -0
- 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 +33 -0
- package/dist/physics/vehicleBackendLink.d.ts +49 -0
- package/dist/physics/vehicleBackendLink.js +102 -0
- package/dist/physics/vehicleDynamics.d.ts +374 -0
- package/dist/physics/vehicleDynamics.js +737 -0
- 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 +17 -3
- package/dist/runtime/cameraDirector.js +25 -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 +1 -0
- package/dist/runtime/context/combat.js +2 -1
- package/dist/runtime/context/deathLoot.d.ts +2 -0
- package/dist/runtime/context/deathLoot.js +1 -0
- package/dist/runtime/gameContext.js +7 -0
- package/dist/runtime/gameContextTypes.d.ts +19 -3
- package/dist/runtime/gameRuntime.d.ts +3 -1
- package/dist/runtime/gameRuntime.js +4 -0
- package/dist/runtime/inputSnapshot.d.ts +6 -0
- package/dist/runtime/inputSnapshot.js +10 -0
- package/dist/runtime/localPlayers.d.ts +73 -0
- package/dist/runtime/localPlayers.js +119 -0
- package/dist/runtime/transport.d.ts +9 -0
- package/dist/runtime/transport.js +7 -1
- package/dist/scene/behaviorRuntime.d.ts +12 -2
- package/dist/scene/behaviorRuntime.js +30 -14
- package/dist/scene/behaviors.d.ts +5 -1
- package/dist/scene/sceneKinds.d.ts +9 -0
- package/dist/scene/vehicleSeat.d.ts +5 -1
- package/dist/scene/vehicleSeat.js +5 -1
- 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/eventMeter.d.ts +1 -1
- package/dist/stats/eventMeter.js +1 -1
- package/dist/vfx/feedbackMixer.d.ts +61 -0
- package/dist/vfx/feedbackMixer.js +106 -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/world/authoredSolids.d.ts +46 -0
- package/dist/world/authoredSolids.js +75 -0
- package/dist/world/buildingGenerator.js +2 -2
- package/dist/world/buildingKit.d.ts +3 -1
- 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/features.d.ts +29 -2
- package/dist/world/features.js +6 -0
- package/dist/world/footprintGrid.d.ts +7 -0
- package/dist/world/footprintGrid.js +16 -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/terraform.d.ts +8 -0
- package/dist/world/terraform.js +7 -0
- package/dist/world/walls.d.ts +7 -0
- package/dist/world/walls.js +7 -0
- package/dist/world/worldSolids.d.ts +84 -0
- package/dist/world/worldSolids.js +240 -0
- package/dist/world.d.ts +2 -2
- package/dist/world.js +1 -1
- package/package.json +1 -1
- package/skills/ce-handoff/SKILL.md +1 -1
- package/skills/game-design/SKILL.md +2 -2
- package/skills/jgengine/SKILL.md +7 -5
- package/skills/jgengine/api.md +118 -89
- package/skills/jgengine/capabilities.md +28 -0
- package/skills/jgengine/recipes/minimal-game.md +3 -0
- package/skills/jgengine-combat/SKILL.md +5 -0
- package/skills/jgengine-combat/api.md +138 -103
- package/skills/jgengine-combat/capabilities.md +29 -1
- package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
- package/skills/jgengine-gameplay/api.md +462 -436
- package/skills/jgengine-gameplay/capabilities.md +24 -0
- package/skills/jgengine-gameplay/reference-systems.md +4 -0
- package/skills/jgengine-world/SKILL.md +15 -3
- package/skills/jgengine-world/api.md +873 -626
- package/skills/jgengine-world/capabilities.md +130 -6
- package/skills/jgengine-world/recipes/character-animation.md +39 -0
- package/skills/jgengine-world/recipes/controls.md +44 -0
- package/skills/jgengine-world/recipes/flight-feel.md +76 -0
- package/skills/jgengine-world/recipes/npc-senses.md +43 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +85 -0
- package/skills/jgengine-world/reference.md +31 -2
- package/skills/jgengine-world/visibility.md +112 -0
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
Reach for these before hand-rolling. Each row is *the thing you need* → *the primitive that already does it*.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## action-contexts — Swap bindings live for on-foot, driving, menu or build modes by pushing and popping layered action maps.
|
|
8
|
+
|
|
9
|
+
- `createActionContextStack` (function) · `import { createActionContextStack } from "@jgengine/core/input/actionContexts"`
|
|
10
|
+
|
|
11
|
+
## ai-decision-graph — Evaluate serializable selector, sequence, condition, action, utility, timer, and random AI decisions with interrupt hooks.
|
|
8
12
|
|
|
9
13
|
- `createDecisionGraphRuntime` (function) · `import { createDecisionGraphRuntime } from "@jgengine/core/ai/decisionGraph"`
|
|
10
14
|
|
|
@@ -20,13 +24,22 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
20
24
|
|
|
21
25
|
- `createDriverState` (function) · `import { createDriverState } from "@jgengine/core/ai/driver"`
|
|
22
26
|
|
|
27
|
+
## air-metrics — measure a vehicle's jumps and air control — apex height and time, double jump, air rotation rates
|
|
28
|
+
|
|
29
|
+
- `measureAir` (function) · `import { measureAir } from "@jgengine/core/physics/handlingProbe"`
|
|
30
|
+
|
|
23
31
|
## anim-graph — data-first animation state machine with blend trees, crossfades, layers, and clip events
|
|
24
32
|
|
|
25
33
|
- `createAnimGraphRuntime` (function) · `import { createAnimGraphRuntime } from "@jgengine/core/anim/animGraph"`
|
|
34
|
+
- `parseAnimGraph` (function) · `import { parseAnimGraph } from "@jgengine/core/anim/animGraph"`
|
|
26
35
|
|
|
27
|
-
## animation —
|
|
36
|
+
## animation — find thigh, shin and foot bones on a humanoid rig for foot IK
|
|
28
37
|
|
|
38
|
+
- `inferLegChains` (function) · `import { inferLegChains } from "@jgengine/core/anim/footPlacement"`
|
|
29
39
|
- `lookAt` (function) · `import { lookAt } from "@jgengine/core/anim/ikSolver"`
|
|
40
|
+
- `placeFeet` (function) · `import { placeFeet } from "@jgengine/core/anim/footPlacement"`
|
|
41
|
+
- `planBoneTexture` (function) · `import { planBoneTexture } from "@jgengine/core/anim/boneTexture"`
|
|
42
|
+
- `sampleBoneTexture` (function) · `import { sampleBoneTexture } from "@jgengine/core/anim/boneTexture"`
|
|
30
43
|
- `solveFabrik` (function) · `import { solveFabrik } from "@jgengine/core/anim/ikSolver"`
|
|
31
44
|
- `solveTwoBone` (function) · `import { solveTwoBone } from "@jgengine/core/anim/ikSolver"`
|
|
32
45
|
|
|
@@ -101,6 +114,11 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
101
114
|
- `placeAuthoredObjects` (function) · `import { placeAuthoredObjects } from "@jgengine/core/world"`
|
|
102
115
|
- `resolveAuthoredObjects` (function) · `import { resolveAuthoredObjects } from "@jgengine/core/world"`
|
|
103
116
|
|
|
117
|
+
## authored-solids — collision for studio-authored world content such as city volumes
|
|
118
|
+
|
|
119
|
+
- `resolveAuthoredSolids` (function) · `import { resolveAuthoredSolids } from "@jgengine/core/world/authoredSolids"`
|
|
120
|
+
- `syncAuthoredSolids` (function) · `import { syncAuthoredSolids } from "@jgengine/core/world/authoredSolids"`
|
|
121
|
+
|
|
104
122
|
## authored-spawn — read spawn points and markers from the editor document
|
|
105
123
|
|
|
106
124
|
- `authoredSpawnPosition` (function) · `import { authoredSpawnPosition } from "@jgengine/core/world/authoredSpawn"`
|
|
@@ -116,6 +134,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
116
134
|
- `registerBuiltinTriggerActions` (function) · `import { registerBuiltinTriggerActions } from "@jgengine/core/scene/authoredTriggers"`
|
|
117
135
|
- `registerTriggerAction` (function) · `import { registerTriggerAction } from "@jgengine/core/world"`
|
|
118
136
|
|
|
137
|
+
## axis-shaping — shape raw input per axis — deadzone, response curve, keyboard ramp, self-centring, speed-sensitive scaling
|
|
138
|
+
|
|
139
|
+
- `createAxisShaper` (function) · `import { createAxisShaper } from "@jgengine/core/input/axisShaper"`
|
|
140
|
+
|
|
119
141
|
## behavior-actions — register named callbacks for decision graph behavior descriptors
|
|
120
142
|
|
|
121
143
|
- `registerBehaviorActions` (function) · `import { registerBehaviorActions } from "@jgengine/core/world"`
|
|
@@ -128,6 +150,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
128
150
|
|
|
129
151
|
- `advanceBehaviors` (function) · `import { advanceBehaviors } from "@jgengine/core/world"`
|
|
130
152
|
|
|
153
|
+
## boat-dynamics — force-based powered hull — hull-speed drag hump, planing, keel, rudder that needs flow or outboard thrust
|
|
154
|
+
|
|
155
|
+
- `createBoatDynamics` (function) · `import { createBoatDynamics } from "@jgengine/core/physics/boatDynamics"`
|
|
156
|
+
|
|
131
157
|
## body-bind — mirror sim-body snapshots onto scene entities each tick, no per-body setPose
|
|
132
158
|
|
|
133
159
|
- `createBodyBind` (function) · `import { createBodyBind } from "@jgengine/core/world"`
|
|
@@ -167,6 +193,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
167
193
|
## city-district — polygon math for road-derived blocks, parcels, and buildable footprints
|
|
168
194
|
|
|
169
195
|
- `Vec2` (type) · `import { Vec2 } from "@jgengine/core/world/cityGeometry"`
|
|
196
|
+
- `citySolids` (function) · `import { citySolids } from "@jgengine/core/world/cityKind"`
|
|
170
197
|
- `extractGraphBlocks` (function) · `import { extractGraphBlocks } from "@jgengine/core/world/cityBlocks"`
|
|
171
198
|
- `rectClearsPolyline` (function) · `import { rectClearsPolyline } from "@jgengine/core/world/cityGeometry"`
|
|
172
199
|
- `rectsSeparated` (function) · `import { rectsSeparated } from "@jgengine/core/world/cityGeometry"`
|
|
@@ -193,6 +220,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
193
220
|
|
|
194
221
|
- `createTimerSet` (function) · `import { createTimerSet } from "@jgengine/core/world"`
|
|
195
222
|
|
|
223
|
+
## course-metrics — measure a vehicle on driven tests — understeer gradient, skidpad g, slalom speed
|
|
224
|
+
|
|
225
|
+
- `measureCourse` (function) · `import { measureCourse } from "@jgengine/core/physics/handlingProbe"`
|
|
226
|
+
|
|
196
227
|
## crouch-prone — stance/pose transitions that change the hitbox
|
|
197
228
|
|
|
198
229
|
- `createPoseState` (function) · `import { createPoseState } from "@jgengine/core/world"`
|
|
@@ -241,6 +272,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
241
272
|
|
|
242
273
|
- `DIFFICULTY_TIERS` (const) · `import { DIFFICULTY_TIERS } from "@jgengine/core/ai/difficulty"`
|
|
243
274
|
|
|
275
|
+
## doppler-pitch — pitch a moving sound up as it approaches and down as it passes, from listener and emitter velocity
|
|
276
|
+
|
|
277
|
+
- `dopplerRate` (function) · `import { dopplerRate } from "@jgengine/core/audio/doppler"`
|
|
278
|
+
|
|
279
|
+
## drivable-vehicle — drive a car from throttle/steer/handbrake input each tick: step the vehicle sim and pose its entity
|
|
280
|
+
|
|
281
|
+
- `tickDrivableVehicle` (function) · `import { tickDrivableVehicle } from "@jgengine/core/world"`
|
|
282
|
+
|
|
244
283
|
## elevation-readout — cursor height and delta-from-reference feedback
|
|
245
284
|
|
|
246
285
|
- `sampleElevation` (function) · `import { sampleElevation } from "@jgengine/core/world"`
|
|
@@ -249,6 +288,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
249
288
|
|
|
250
289
|
- `summarizeElevation` (function) · `import { summarizeElevation } from "@jgengine/core/world"`
|
|
251
290
|
|
|
291
|
+
## engine-layers — layered engine audio: rpm-keyed samples crossfaded, with on-load and off-load sets picked by engine load
|
|
292
|
+
|
|
293
|
+
- `createEngineLayers` (function) · `import { createEngineLayers } from "@jgengine/core/audio/engineLayers"`
|
|
294
|
+
|
|
252
295
|
## entity-meta — cast-free narrow of SceneEntity.meta via a type guard
|
|
253
296
|
|
|
254
297
|
- `entityMetaOf` (function) · `import { entityMetaOf } from "@jgengine/core/world"`
|
|
@@ -257,9 +300,17 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
257
300
|
|
|
258
301
|
- `createFastTravelNetwork` (function) · `import { createFastTravelNetwork } from "@jgengine/core/world"`
|
|
259
302
|
|
|
260
|
-
##
|
|
303
|
+
## feedback-mixer — map sim telemetry to camera, audio and haptic parameters through curves, smoothing and threshold events
|
|
304
|
+
|
|
305
|
+
- `createFeedbackMixer` (function) · `import { createFeedbackMixer } from "@jgengine/core/vfx/feedbackMixer"`
|
|
306
|
+
|
|
307
|
+
## fire-input — bind a key or mouse button to a named action that runs the same-named command with the aim; repeatMs auto-fires while held
|
|
308
|
+
|
|
309
|
+
- `ActionCodesMap` (type) · `import { ActionCodesMap } from "@jgengine/core/input/actionBindings"`
|
|
261
310
|
|
|
262
|
-
-
|
|
311
|
+
## flight-metrics — measure an aircraft's feel as numbers — roll rate, sustained turn, stall speed, climb, throttle response, hover drift
|
|
312
|
+
|
|
313
|
+
- `measureFlight` (function) · `import { measureFlight } from "@jgengine/core/physics/handlingProbe"`
|
|
263
314
|
|
|
264
315
|
## follow-trail — trailing follower/snake formation that chases a leader
|
|
265
316
|
|
|
@@ -327,10 +378,22 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
327
378
|
|
|
328
379
|
- `grounded` (function) · `import { grounded } from "@jgengine/core/world"`
|
|
329
380
|
|
|
381
|
+
## handling-metrics — measure a vehicle's feel as numbers — 0–100, braking, lateral g, turn-in, sideslip, spin
|
|
382
|
+
|
|
383
|
+
- `measureHandling` (function) · `import { measureHandling } from "@jgengine/core/physics/handlingProbe"`
|
|
384
|
+
|
|
385
|
+
## haptics — Continuous gamepad rumble from named channels (engine, road, impact) mixed by priority, plus fading pulses.
|
|
386
|
+
|
|
387
|
+
- `createHapticChannels` (function) · `import { createHapticChannels } from "@jgengine/core/input/haptics"`
|
|
388
|
+
|
|
330
389
|
## hold-order — stand-ground order that holds position until preempted or canceled
|
|
331
390
|
|
|
332
391
|
- `defineHoldOrder` (function) · `import { defineHoldOrder } from "@jgengine/core/world"`
|
|
333
392
|
|
|
393
|
+
## input-buffer — buffer early presses, coyote time, hold duration and double tap for responsive controls
|
|
394
|
+
|
|
395
|
+
- `createInputBuffer` (function) · `import { createInputBuffer } from "@jgengine/core/input/inputBuffer"`
|
|
396
|
+
|
|
334
397
|
## interaction-prompt — observable nearest-interactable prompt registry over the proximity-prompt resolver — register/resolve/active with priority+range and change-notified snapshot/restore
|
|
335
398
|
|
|
336
399
|
- `createPromptRegistry` (function) · `import { createPromptRegistry } from "@jgengine/core/world"`
|
|
@@ -355,6 +418,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
355
418
|
|
|
356
419
|
- `createRebindSession` (function) · `import { createRebindSession } from "@jgengine/core/input/rebindSession"`
|
|
357
420
|
|
|
421
|
+
## kinematic-vehicle — arcade car/kart driving physics with throttle, brake, steer and retunable handling
|
|
422
|
+
|
|
423
|
+
- `createKinematicVehicle` (function) · `import { createKinematicVehicle } from "@jgengine/core/world"`
|
|
424
|
+
|
|
425
|
+
## lean-metrics — measure a motorcycle's lean — steady lean, time to lean, countersteer tip-in
|
|
426
|
+
|
|
427
|
+
- `measureLean` (function) · `import { measureLean } from "@jgengine/core/physics/handlingProbe"`
|
|
428
|
+
|
|
358
429
|
## limb-health — per-body-part/region health tracked separately
|
|
359
430
|
|
|
360
431
|
- `createMultiRegionHealth` (function) · `import { createMultiRegionHealth } from "@jgengine/core/procedural"`
|
|
@@ -367,8 +438,9 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
367
438
|
|
|
368
439
|
- `generateLock` (function) · `import { generateLock } from "@jgengine/core/world"`
|
|
369
440
|
|
|
370
|
-
## locomotion-graph —
|
|
441
|
+
## locomotion-graph — resolve the animation graph a model config plays
|
|
371
442
|
|
|
443
|
+
- `animGraphFromConfig` (function) · `import { animGraphFromConfig } from "@jgengine/core/anim/locomotionGraph"`
|
|
372
444
|
- `locomotionGraph` (function) · `import { locomotionGraph } from "@jgengine/core/anim/locomotionGraph"`
|
|
373
445
|
|
|
374
446
|
## map-annotations — player-drawn map annotation layer — freehand strokes, area shapes, and pinned notes, projected into the map's routes/zones props and serializable
|
|
@@ -400,6 +472,22 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
400
472
|
|
|
401
473
|
- `defineMoveOrder` (function) · `import { defineMoveOrder } from "@jgengine/core/world"`
|
|
402
474
|
|
|
475
|
+
## movement-metrics — measure a walking character's feel as numbers — time to top speed, stop distance, jump height and apex, air control, turn-around
|
|
476
|
+
|
|
477
|
+
- `measureMovement` (function) · `import { measureMovement } from "@jgengine/core/movement/movementProbe"`
|
|
478
|
+
|
|
479
|
+
## nav-from-solids — block world solids on a nav grid
|
|
480
|
+
|
|
481
|
+
- `populateNavGridFromSolids` (function) · `import { populateNavGridFromSolids } from "@jgengine/core/nav/navFromEnvironment"`
|
|
482
|
+
|
|
483
|
+
## navmesh-bake — voxelize scene triangles into an agent-sized polygon nav mesh
|
|
484
|
+
|
|
485
|
+
- `bakeNavMesh` (function) · `import { bakeNavMesh } from "@jgengine/navbake"`
|
|
486
|
+
|
|
487
|
+
## navmesh-query — route, snap, and raycast on a polygon navmesh with retunable area costs and a bounded search
|
|
488
|
+
|
|
489
|
+
- `createNavMeshQuery` (function) · `import { createNavMeshQuery } from "@jgengine/core/world"`
|
|
490
|
+
|
|
403
491
|
## object-slot-inventory — placed objects hold validated per-instance container contents that replicate and save with the placement
|
|
404
492
|
|
|
405
493
|
- `SceneObjectSlots` (interface) · `import { SceneObjectSlots } from "@jgengine/core/world"`
|
|
@@ -497,7 +585,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
497
585
|
|
|
498
586
|
- `resolveActivePrompt` (function) · `import { resolveActivePrompt } from "@jgengine/core/world"`
|
|
499
587
|
|
|
500
|
-
## pursuit — chase-to-reach cooldown-gated attack
|
|
588
|
+
## pursuit — enemy/mob aggro chase-to-reach with a cooldown-gated attack and optional leash-to-home; owns the attack cooldown
|
|
501
589
|
|
|
502
590
|
- `advancePursuit` (function) · `import { advancePursuit } from "@jgengine/core/ai/pursuit"`
|
|
503
591
|
|
|
@@ -521,6 +609,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
521
609
|
|
|
522
610
|
- `createResourceNodeField` (function) · `import { createResourceNodeField } from "@jgengine/core/world/resourceNode"`
|
|
523
611
|
|
|
612
|
+
## ride-metrics — measure a sprung vehicle's ride — settle time, dive, roll per g, landing bounce
|
|
613
|
+
|
|
614
|
+
- `measureRide` (function) · `import { measureRide } from "@jgengine/core/physics/handlingProbe"`
|
|
615
|
+
|
|
616
|
+
## rigid-aircraft — force-and-torque aircraft — lifting surfaces with stall and drag polar, control surfaces, inertia, loops and rolls
|
|
617
|
+
|
|
618
|
+
- `createRigidAircraft` (function) · `import { createRigidAircraft } from "@jgengine/core/physics/aircraftDynamics"`
|
|
619
|
+
|
|
524
620
|
## rts-control-groups — map Ctrl+digit / digit / double-tap presses onto selection-bookmark bind / recall / focus intents
|
|
525
621
|
|
|
526
622
|
- `controlGroupKey` (function) · `import { controlGroupKey } from "@jgengine/core/world"`
|
|
@@ -560,6 +656,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
560
656
|
|
|
561
657
|
## solid-obstacles — the one blocking-geometry query the player resolver and NPC movers share
|
|
562
658
|
|
|
659
|
+
- `obstacleFromSolid` (function) · `import { obstacleFromSolid } from "@jgengine/core/movement/solidObstacles"`
|
|
563
660
|
- `solidObstaclesNear` (function) · `import { solidObstaclesNear } from "@jgengine/core/movement/solidObstacles"`
|
|
564
661
|
|
|
565
662
|
## spatial-region-box — sample uniformly within an axis-aligned 3D box
|
|
@@ -683,6 +780,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
683
780
|
- `setTouchControlsMode` (function) · `import { setTouchControlsMode } from "@jgengine/core/input/touchControlsMode"`
|
|
684
781
|
- `touchButtonShape` (function) · `import { touchButtonShape } from "@jgengine/core/input/touchScheme"`
|
|
685
782
|
|
|
783
|
+
## vehicle-backend-link — collide a vehicle sim with a physics backend — shove props, stop at walls, slide along them
|
|
784
|
+
|
|
785
|
+
- `createVehicleBackendLink` (function) · `import { createVehicleBackendLink } from "@jgengine/core/physics/vehicleBackendLink"`
|
|
786
|
+
|
|
787
|
+
## vehicle-dynamics — force-based car handling — tires, load transfer, drivetrain, assists — tuned with physical numbers
|
|
788
|
+
|
|
789
|
+
- `createVehicleDynamics` (function) · `import { createVehicleDynamics } from "@jgengine/core/physics/vehicleDynamics"`
|
|
790
|
+
|
|
791
|
+
## vehicle-seats — enter, exit and swap seats in a car or mount (GTA-style boarding) with driver and passenger slots
|
|
792
|
+
|
|
793
|
+
- `createVehicleSeats` (function) · `import { createVehicleSeats } from "@jgengine/core/world"`
|
|
794
|
+
|
|
686
795
|
## visibility-field — per-viewer-group observed/remembered/hidden fog-of-war knowledge with terrain memory and authoritative entity disclosure
|
|
687
796
|
|
|
688
797
|
- `createVisibilityField` (function) · `import { createVisibilityField } from "@jgengine/core/world"`
|
|
@@ -729,3 +838,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
729
838
|
## world-seed — derive a deterministic generator/scatter seed from world id + save/run seed
|
|
730
839
|
|
|
731
840
|
- `seedForPlace` (function) · `import { seedForPlace } from "@jgengine/core/world"`
|
|
841
|
+
|
|
842
|
+
## world-solid-bounds — world AABB and nav footprint of a world solid
|
|
843
|
+
|
|
844
|
+
- `worldSolidBounds` (function) · `import { worldSolidBounds } from "@jgengine/core/world/worldSolids"`
|
|
845
|
+
- `worldSolidFootprint` (function) · `import { worldSolidFootprint } from "@jgengine/core/world/worldSolids"`
|
|
846
|
+
|
|
847
|
+
## world-solids — collision for generated and authored world geometry (buildings, city lots, walls)
|
|
848
|
+
|
|
849
|
+
- `createWorldSolids` (function) · `import { createWorldSolids } from "@jgengine/core/world/worldSolids"`
|
|
850
|
+
|
|
851
|
+
## world-solids-from-data — derive collision boxes from generated buildings and wall runs
|
|
852
|
+
|
|
853
|
+
- `buildingSolids` (function) · `import { buildingSolids } from "@jgengine/core/world/worldSolids"`
|
|
854
|
+
- `structureSolids` (function) · `import { structureSolids } from "@jgengine/core/world/worldSolids"`
|
|
855
|
+
- `wallSolids` (function) · `import { wallSolids } from "@jgengine/core/world/worldSolids"`
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Recipe — character animation (graph → rig → ground)
|
|
2
|
+
|
|
3
|
+
**What this wires:** a rigged character that moves the way the game plays: clips chosen by data, feet that meet the ground, and gameplay moments keyed to animation frames. There is no default hero, soldier or NPC rig: every game picks its clips, parameters and corrections.
|
|
4
|
+
|
|
5
|
+
## The seams
|
|
6
|
+
|
|
7
|
+
- **Clips on a model.** `ModelConfig.animation` on an entity kind. A catalog model with indexed `clips` animates automatically through semantic clip roles; `"none"` holds the bind pose. `{ clip, paused, time }` holds one pose.
|
|
8
|
+
- **Graph.** `animation.graph` is an `AnimGraph` (`@jgengine/core/anim/animGraph`): layers, each a state machine of `clip`, `blend1D` and `blend2D` states joined by transitions with `when` conditions, `trigger`s, crossfade `duration` and `exitTime`. It is plain data, so it saves, diffs and can be authored in the editor.
|
|
9
|
+
- `locomotionGraph({ idle, walk, run, walkSpeed, runSpeed, fadeSec, oneShots })` builds the idle/walk/run blend plus one-shots; `states`/`oneShots` configs are converted through it.
|
|
10
|
+
- Upper-body actions (aim, reload, wave) go on a second layer with `mask: ["spine", "arm", ...]` bone prefixes, `additive` for recoil or breathing on top of the base.
|
|
11
|
+
- `createAnimGraphRuntime(graph)` runs the same graph headless: `advance(dt, params, clips)`, `trigger`, `snapshot`/`restore`, `retune`. Test transitions with it before looking at pixels.
|
|
12
|
+
- **Authoring in the editor.** A placement's graph is inspected, previewed and stored from the editor's Animation workspace (see `jgengine-editor` reference, "Placement animation"). `animGraphFromConfig(config)` gives the graph any config plays, so tools and tests see what the shell plays. `parseAnimGraph(json)` validates a stored one.
|
|
13
|
+
- **Parameters.** The shell feeds the entity's smoothed ground speed as `speed`. Anything else (aiming, crouched, strafe x/y) goes on the entity blackboard under `ANIM_PARAMS_KEY`.
|
|
14
|
+
- **Triggers.** `ctx.game.playEntityAnimation(id, name)` arms a trigger; `hit` and `death` arm from their combat events.
|
|
15
|
+
- **Events.** `graph.events: [{ clip, atSec, name }]` emit `animation.event` when a clip crosses that time. Hook footstep audio, the damage frame of a swing or the magazine swap of a reload to the event, not to a timer.
|
|
16
|
+
- **Root motion.** `rootMotion: true` on a state moves the entity by the clip's horizontal root-bone travel, turned by the entity's facing and scaled with the model, instead of playing it in place. The root bone's vertical motion stays in the clip, and other states keep their root animation untouched. Use it for dodges, vaults and attacks with lunges whose distance must match the feet. `output.rootMotion` reports when a root-motion state is current.
|
|
17
|
+
- **Foot IK.** `ModelConfig.ik` runs after the mixer each frame.
|
|
18
|
+
- `ik: "auto"` finds thigh → shin → foot chains by bone name. Pass `{ feet: [{ root, mid, tip }] }` when the rig's names are unusual, and `feet: []` to keep only `lookAt`.
|
|
19
|
+
- Each foot keeps its animated swing and moves by its ground's height above or below the model origin. The pelvis drops so the lower leg can reach, soles never end under the ground, and planted feet tilt to the ground normal (`alignToGround`, `0..1`). The correction fades out while the feet are clear of the ground.
|
|
20
|
+
- `maxAdjust` caps the correction as a fraction of leg length (default `0.4`). Probes hit terrain and blocking physical objects, never the model's own entity.
|
|
21
|
+
- The pure pieces are `placeFeet` and `inferLegChains` (`@jgengine/core/anim/footPlacement`) and `solveTwoBone`/`solveFabrik`/`lookAt` (`@jgengine/core/anim/ikSolver`), for a custom host or a non-leg chain (a hand on a ledge, a tail).
|
|
22
|
+
- **Crowds.** `<SkinnedInstances url instances clips once fps modelScale />` (`@jgengine/shell/render/SkinnedInstances`) draws hundreds of animated copies of one rig in one instanced draw per material. Each `CrowdInstance` has `position`, `rotationY`, `scale`, `clip`, `timeOffset` and `speed`.
|
|
23
|
+
- Every clip is baked once to a bone-matrix texture (layout from `planBoneTexture`, `@jgengine/core/anim/boneTexture`) and sampled on the GPU. Clips in `once` hold their last pose. A member changes clip when the game passes a new `instances` array.
|
|
24
|
+
- Only skinned meshes are baked. Static props parented to bones (a weapon in a hand slot, a hat) are dropped, and a mesh with several materials keeps its first.
|
|
25
|
+
- Crowd members have no graph, IK or attachments. Promote the few near the camera or near the player to entity models.
|
|
26
|
+
- `bakeSkinnedCrowd` and `patchCrowdMaterial` are exported for a custom renderer.
|
|
27
|
+
|
|
28
|
+
## Symptom → knob
|
|
29
|
+
|
|
30
|
+
| Symptom | Knob |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| Feet slide at walk or run speed | `walkSpeed`/`runSpeed` to the clip's authored stride speed, or blend1D `at` points |
|
|
33
|
+
| Pop between states | transition `duration`; `exitTime` on one-shots returning to locomotion |
|
|
34
|
+
| Feet float or sink on slopes and steps | `ik: "auto"` |
|
|
35
|
+
| Knees buckle on steep ground | lower `ik.maxAdjust` |
|
|
36
|
+
| Feet tilt wrongly on rubble or props | lower `ik.alignToGround` |
|
|
37
|
+
| Attack damage lands before the swing | `graph.events` on the hit frame instead of a timer |
|
|
38
|
+
| Lunge distance doesn't match the feet | `rootMotion: true` on that state |
|
|
39
|
+
| Hundreds of animated characters cost thousands of draw calls | `SkinnedInstances` for the background crowd |
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Recipe — controls (bindings → contexts → devices → feel)
|
|
2
|
+
|
|
3
|
+
**What this wires:** a game's controls from small parts: named actions, layered contexts that swap bindings live, keyboard/mouse and gamepad on the same actions, and per-axis feel. There are no control presets; every game names its own actions and codes.
|
|
4
|
+
|
|
5
|
+
## The seams
|
|
6
|
+
|
|
7
|
+
- **Actions.** `defineGame({ input: { action: codes } })` maps action names to codes. Keyboard codes are `KeyboardEvent.code` (`KeyW`), pad buttons are `pad:<index>` and stick or trigger directions are `padaxis:<index>+`/`-` in the standard mapping. `bindingLabel(code, "xbox")` gives the glyph text for prompts. Sim code reads actions only: `ctx.input.isDown`, `justPressed`, `value` (0..1, analog when a stick or trigger drives it) and `axis(bindings)`.
|
|
8
|
+
- **Contexts.** `actionContextStack(ctx)` (`@jgengine/core/game/controlGate`) layers action maps over the base input. `push({ id, codes, passthrough, axes?, shaping? })` swaps bindings immediately: the shell re-binds keyboard, touch and pad on the next frame, and a key held across the swap keeps counting toward whatever it maps to now. `passthrough: false` hides everything below (menus, a parked vehicle); `passthrough: true` adds or overrides a few actions (a build mode on top of on-foot). The stack has `snapshot`/`restore`, so the current mode survives a save.
|
|
9
|
+
- **Axes and feel per context.** A context can carry `axes` (axis bindings by action name) and `shaping` (serializable `createAxisShaper` profiles). Read them with `activeAxes()` when `version()` changes and `retune` the shaper, so on-foot movement and driving steer each get their own ramp and curve without a branch per mode.
|
|
10
|
+
- **Pad feel.** `defineGame({ gamepad: { deadzone, curve, triggerDeadzone } })` shapes sticks and triggers before values reach `ctx.input`; the axis shaper only adds ramps and speed scaling on top.
|
|
11
|
+
- **Timing.** `createInputBuffer` (`@jgengine/core/input/inputBuffer`) buffers early presses, coyote time, hold duration and double taps.
|
|
12
|
+
- **Local seats (couch co-op).** `defineGame({ localPlayers: { maxSlots: 4 } })` opens seats on one screen. The first pad to press a button shares the primary seat with keyboard and touch (`claimPrimary: "none"` keeps the primary keyboard-only); each further pad hot-joins its own seat, and the shell calls `loop.onNewPlayer(ctx, { userId, isNew: true })` for it, so spawn with `player?.userId ?? ctx.player.userId`. Read a seat with `localPlayers(ctx).local(slotId)` → `{ userId, input }` (`@jgengine/core/runtime/localPlayers`); iterate `slots()` in `onTick`. Seats use the same bindings and contexts as the primary, and the shell's walk controller moves every seat's entity. The table has `snapshot`/`restore`/`retune`.
|
|
13
|
+
- **Split-screen.** `defineGame({ viewports: { layout: "auto", split: "vertical" } })` draws one viewport per joined seat (`@jgengine/core/game/viewports`: `splitViewports`, `resolveViewports`, `viewportPixels`). The primary seat keeps the main camera rig; other seats get a chase or top-down follow camera (`viewports.camera`, defaulting to top-down when the main rig is), and the walk controller moves each seat relative to its own camera. `ViewportHuds` (`@jgengine/shell/camera/Viewports`) gives each seat its own HUD root. Post-processing is skipped while the screen is split. `bun run drive couch --param gamepad=2` shows two seats.
|
|
14
|
+
- **Haptics.** `ctx.input.haptics(userId)` holds named rumble channels: `set(name, { strong, weak }, priority?)` each tick for continuous feel, `pulse(name, { strong, weak, ms }, priority?)` for a fading hit. Higher priority ducks lower channels. The shell drives each seat's own pad; channels are local and never saved or sent.
|
|
15
|
+
- **Rebinding.** `createRebindSession` drives a conflict-aware "press a key" flow; overrides persist per game and apply under the context stack.
|
|
16
|
+
|
|
17
|
+
## Wiring a mode swap
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const DRIVING: ActionContext = {
|
|
21
|
+
id: "driving",
|
|
22
|
+
codes: { exit: ["KeyF", "pad:3"] },
|
|
23
|
+
passthrough: true,
|
|
24
|
+
axes: { steer: { positive: ["steerRight"], negative: ["steerLeft"] } },
|
|
25
|
+
shaping: { steer: { digital: { riseRate: 3, returnRate: 6 }, analog: { deadzone: 0.08, curve: 1.5 } } },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// enter: actionContextStack(ctx).push(DRIVING); leave: actionContextStack(ctx).pop("driving")
|
|
29
|
+
if (state.contextVersion !== contexts.version()) {
|
|
30
|
+
state.contextVersion = contexts.version();
|
|
31
|
+
const layer = contexts.activeAxes();
|
|
32
|
+
state.bindings = { steer: layer.bindings.steer ?? { positive: [] } };
|
|
33
|
+
state.shaper.retune({ axes: { steer: layer.shaping.steer ?? {} } });
|
|
34
|
+
}
|
|
35
|
+
const raw = ctx.input.axis(state.bindings);
|
|
36
|
+
const axis = state.shaper.shape(dt, raw, { analog: analogAxes(state.bindings, ctx.input.analog()) });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`bun run drive handling` runs this: F pops the driving context and a held W stops reaching the engine.
|
|
40
|
+
|
|
41
|
+
## Pitfalls
|
|
42
|
+
|
|
43
|
+
- Actions that exist only inside a context are published to `ctx.input` while the context is active, but the base `input` map is what the touch dock and settings menu list. Keep an action in the base map when touch players need it.
|
|
44
|
+
- `shaping` has no `scale` callback because contexts are serializable; apply speed-sensitive scaling on the shaper the game owns.
|
|
@@ -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,43 @@
|
|
|
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
|
+
- Hand the graph facts, not objects: the blackboard serializes with the behavior,
|
|
39
|
+
so saves and replays keep an NPC mid-investigation.
|
|
40
|
+
- Judge footstep noise on speed averaged over a window, not one frame; frame-time
|
|
41
|
+
jitter otherwise reads as a sprint.
|
|
42
|
+
- Record the stimulus you handled (`handledAt`) so giving up does not
|
|
43
|
+
re-alert on the same memory next tick.
|
|
@@ -0,0 +1,85 @@
|
|
|
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.
|
|
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
|
+
- Speed comes from the entity's sim `velocity`, so publish poses with `setPose({ ..., dt })` (`tickDrivableVehicle` does). `fov.response` eases the FOV.
|
|
23
|
+
- 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.
|
|
24
|
+
- `pitchFollow` tilts the boom with `rotationX` on ramps and slopes. `lookBackAction` names an input action that looks behind while held.
|
|
25
|
+
- The boom stops short of objects, terrain and walls through `ctx.scene.raycast`; `collision: false` turns that off.
|
|
26
|
+
- Switch views at runtime with `ctx.camera.setChaseTuning({ ...ctx.camera.chaseTuning(), view: nextChaseView(view) })` (`@jgengine/core/runtime/cameraDirector`).
|
|
27
|
+
- **Sound.** Call `ctx.game.audio.loop(id, sound)` once, then `setLoop` every tick:
|
|
28
|
+
- Engine: `rate` from `step.rpm`, `gain` from `step.engineLoad`.
|
|
29
|
+
- Tires: `gain` from how far `max(step.frontSaturation, step.rearSaturation)` exceeds ~0.85.
|
|
30
|
+
- 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.
|
|
31
|
+
- `setLoop` also takes `lowpass`/`highpass` cutoffs in Hz (muffle off-throttle) and `velocity`; a sound with `doppler: 1` pitches by listener and emitter velocity.
|
|
32
|
+
- **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.
|
|
33
|
+
- **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.
|
|
34
|
+
- **Sound.** Play a one-shot on `step.landingSpeed` (thud, suspension clunk).
|
|
35
|
+
- **Proof.** For jumps and air control, `measureAir` reports apex height and time, double-jump height, and air rotation rates.
|
|
36
|
+
- **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.
|
|
37
|
+
|
|
38
|
+
## Workflow
|
|
39
|
+
|
|
40
|
+
1. **Write the feel as numbers before tuning.** Turn the brief into target ranges on the `HandlingReport` fields. Some examples:
|
|
41
|
+
- "Snappy" is `turnIn < 0.2`.
|
|
42
|
+
- "Planted" is `maxLateralG` close to the tire μ with `stepPeakSideslipDeg < 8`.
|
|
43
|
+
- "Drifty but safe" is `handbrakePeakSideslipDeg` 40–70 with `spun === false`.
|
|
44
|
+
- "Keyboard-friendly" is `spun` and `powerSteerSpun` both `false`, including at `cornerSpeed: 40`.
|
|
45
|
+
- "Neutral balance" is `measureCourse(...).understeerGradient` near 0–0.5 deg/g; "tail-happy" is below 0.
|
|
46
|
+
- "Agile" is a high `measureCourse(...).slalomSpeed`; "forgiving" is a short `handbrakeRecoverySeconds`.
|
|
47
|
+
2. **Put those ranges in a test first**, next to the tuning.
|
|
48
|
+
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.
|
|
49
|
+
4. **Drive it.**
|
|
50
|
+
- 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.
|
|
51
|
+
- `--record-fps 20` keeps game time at 1:1, because each recorded frame advances at most one 50 ms step.
|
|
52
|
+
- Tests prove the numbers; the drive proves the camera and sound.
|
|
53
|
+
|
|
54
|
+
## Symptom → knob
|
|
55
|
+
|
|
56
|
+
| Symptom | Knob (direction) |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| `understeerGradient` too high | front tire `peakSlipAngle` ↓ or rear ↑ (tire stiffness split is what moves it at moderate g) |
|
|
59
|
+
| Pushes wide, won't turn in | `rollStiffnessFront` ↓, rear `peakGrip` ↓ relative to front, `yawInertiaIndex` ↓, `steering.rate` ↑ |
|
|
60
|
+
| Snaps into oversteer | `rollStiffnessFront` ↑, rear `peakGrip` ↑, `slideGrip` ↑ (gentler breakaway), `assists.stability` ↑ |
|
|
61
|
+
| Spins holding throttle and steer | `assists.tractionControl` ↑ (budgets for cornering grip), `driveFront` ↑, peak torque ↓ |
|
|
62
|
+
| Slides feel uncatchable | `steering.selfAlign` ↑ (caster catches the slide), `slideGrip` ↑, `assists.maxSideslip` ↑ with `stability` ↑ |
|
|
63
|
+
| Too twitchy at speed | `steering.highSpeedAngle` ↓, `steering.rate` ↓, `yawInertiaIndex` ↑, shaper steer `digital.riseRate` ↓ |
|
|
64
|
+
| Keyboard steering is all-or-nothing | shaper steer `digital.riseRate` 2–4/s, `returnRate` 5–8/s |
|
|
65
|
+
| Stick feels twitchy on centre | shaper steer `analog.curve` 1.3–2, `analog.deadzone` 0.05–0.12 |
|
|
66
|
+
| Sluggish, boat-like | `yawInertiaIndex` ↓, `peakSlipAngle` ↓ (stiffer tire), `comHeight` ↓ |
|
|
67
|
+
| Handbrake does nothing / spins every time | `handbrakeGrip` ↓ / ↑, with `selfAlign` to set how it recovers |
|
|
68
|
+
| No top speed ceiling / wrong ceiling | `aero.dragArea`, power (`maxPower` or torque × gearing); `speedLimit` only for a hard governor |
|
|
69
|
+
| Boost or supersonic above the cap | `modifiers.thrust` (bypasses tires and the governor) |
|
|
70
|
+
| Floaty, wallowy body | `suspension.springRate` ↑, `damperRate` ↑ |
|
|
71
|
+
| Harsh, skips over bumps | `springRate` ↓, `travel` ↑ |
|
|
72
|
+
| Landings bounce back into the air | `suspension.reboundRate` ↑ (defaults to 1.5 × `damperRate`), `travel` ↑ |
|
|
73
|
+
| Leans too much / feels flat | `suspension.antiRoll` ↑ / ↓; `rollStiffnessFront` still sets which axle takes it |
|
|
74
|
+
| Jump too floaty / too short | `jump.speed` (apex ≈ speed² / 2g); `jump.count: 2` plus `window` for a double jump |
|
|
75
|
+
| Air rotation too twitchy / sluggish | `air.*Accel` for how fast it builds, `air.maxRate` for the ceiling, `air.damping` for how fast it stops |
|
|
76
|
+
| Bike wheelies on launch / flips over the bars braking | `assists.antiWheelie` / `assists.antiStoppie` ↑ (1 keeps both wheels down; 0.6 lets it lift briefly) |
|
|
77
|
+
| Bike tips in lazily / nervously | `lean.leanRate` ↑ / ↓ (2 tourer … 5 sport); `lean.countersteer` sets the wrong-way tip at the start |
|
|
78
|
+
| Rolls over in corners | `comHeight` ↓ or `trackWidth` ↑: it tips over at about `trackWidth / (2 · comHeight)` g |
|
|
79
|
+
|
|
80
|
+
## Traps
|
|
81
|
+
|
|
82
|
+
- Hand-editing `yawRate` or heading to "fix" a turn defeats the model. Change a physical knob instead.
|
|
83
|
+
- `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.
|
|
84
|
+
- 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.
|
|
85
|
+
- For multiplayer, send inputs, not poses. Predict locally with the same tuning, and on a correction call `restore(serverState)` and replay the buffered inputs.
|