@jgengine/core 0.19.0 → 0.22.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.
Files changed (266) hide show
  1. package/CHANGELOG.md +364 -0
  2. package/dist/ai/decisionGraph.d.ts +2 -0
  3. package/dist/ai/decisionGraph.js +6 -0
  4. package/dist/ai/flock.d.ts +1 -1
  5. package/dist/ai/flock.js +7 -4
  6. package/dist/anim/animGraph.d.ts +33 -2
  7. package/dist/anim/animGraph.js +200 -12
  8. package/dist/anim/locomotionGraph.d.ts +11 -3
  9. package/dist/anim/locomotionGraph.js +25 -15
  10. package/dist/audio/audioFalloff.d.ts +4 -0
  11. package/dist/audio/voiceAllocator.d.ts +46 -0
  12. package/dist/audio/voiceAllocator.js +76 -0
  13. package/dist/combat/abilityKit.d.ts +19 -0
  14. package/dist/combat/abilityKit.js +40 -4
  15. package/dist/combat/death.d.ts +4 -3
  16. package/dist/combat/death.js +3 -3
  17. package/dist/combat/magazine.d.ts +9 -0
  18. package/dist/combat/magazine.js +24 -2
  19. package/dist/combat/projectiles.d.ts +94 -2
  20. package/dist/combat/projectiles.js +282 -61
  21. package/dist/combat/regenShield.d.ts +14 -0
  22. package/dist/combat/regenShield.js +23 -7
  23. package/dist/crafting/recipeCatalog.d.ts +16 -0
  24. package/dist/crafting/recipeCatalog.js +55 -0
  25. package/dist/economy/auctionBook.d.ts +1 -1
  26. package/dist/economy/auctionBook.js +11 -1
  27. package/dist/economy/listingBook.d.ts +1 -1
  28. package/dist/economy/listingBook.js +11 -1
  29. package/dist/economy/resourceLedger.js +3 -3
  30. package/dist/economy/shopStock.js +7 -6
  31. package/dist/economy/techTree.js +7 -6
  32. package/dist/economy/unlockPoints.js +3 -6
  33. package/dist/editor/commandApply.js +89 -16
  34. package/dist/editor/commands.d.ts +38 -1
  35. package/dist/editor/commands.js +320 -3
  36. package/dist/editor/creatorStorage.d.ts +59 -0
  37. package/dist/editor/creatorStorage.js +164 -0
  38. package/dist/editor/document.d.ts +4 -0
  39. package/dist/editor/document.js +116 -17
  40. package/dist/editor/index.d.ts +3 -0
  41. package/dist/editor/index.js +1 -0
  42. package/dist/editor/liveSync.d.ts +4 -1
  43. package/dist/editor/liveSync.js +26 -10
  44. package/dist/editor/materialAuthoring.d.ts +14 -0
  45. package/dist/editor/materialAuthoring.js +38 -0
  46. package/dist/editor/modelNodeVisibility.d.ts +11 -0
  47. package/dist/editor/modelNodeVisibility.js +29 -0
  48. package/dist/editor/movementCatalog.d.ts +90 -0
  49. package/dist/editor/movementCatalog.js +144 -0
  50. package/dist/editor/simulation.d.ts +60 -0
  51. package/dist/editor/simulation.js +202 -0
  52. package/dist/editor/staticPrefab.d.ts +17 -0
  53. package/dist/editor/staticPrefab.js +51 -0
  54. package/dist/editor/terrainMaterials.d.ts +10 -0
  55. package/dist/editor/terrainMaterials.js +13 -0
  56. package/dist/editor/types.d.ts +11 -0
  57. package/dist/game/cameraConfig.d.ts +3 -1
  58. package/dist/game/clipRoles.d.ts +2 -1
  59. package/dist/game/clipRoles.js +7 -3
  60. package/dist/game/contentValidation.d.ts +69 -0
  61. package/dist/game/contentValidation.js +118 -0
  62. package/dist/game/controlGate.d.ts +8 -0
  63. package/dist/game/controlGate.js +43 -6
  64. package/dist/game/defineGame.d.ts +32 -3
  65. package/dist/game/dialogueGraph.d.ts +20 -0
  66. package/dist/game/dialogueGraph.js +107 -0
  67. package/dist/game/events.d.ts +5 -0
  68. package/dist/game/keyValueStore.d.ts +17 -4
  69. package/dist/game/keyValueStore.js +35 -23
  70. package/dist/game/lootModifiers.d.ts +17 -0
  71. package/dist/game/lootModifiers.js +26 -0
  72. package/dist/game/narrativeAuthoring.d.ts +67 -0
  73. package/dist/game/narrativeAuthoring.js +122 -0
  74. package/dist/game/playableGame.d.ts +52 -20
  75. package/dist/game/quest.d.ts +15 -0
  76. package/dist/game/quest.js +209 -63
  77. package/dist/game/questCatalog.d.ts +50 -0
  78. package/dist/game/questCatalog.js +205 -0
  79. package/dist/game/saveStore.d.ts +6 -5
  80. package/dist/game/saveStore.js +57 -57
  81. package/dist/game/systemRuntime.d.ts +2 -2
  82. package/dist/game/systemRuntime.js +84 -33
  83. package/dist/gameplay.d.ts +3 -0
  84. package/dist/gameplay.js +3 -0
  85. package/dist/interaction/promptRegistry.d.ts +5 -4
  86. package/dist/interaction/promptRegistry.js +8 -2
  87. package/dist/inventory/inventoryModel.js +9 -6
  88. package/dist/inventory/shapedGrid.d.ts +48 -9
  89. package/dist/inventory/shapedGrid.js +69 -10
  90. package/dist/item/durability.js +4 -0
  91. package/dist/item/itemInstanceRegistry.d.ts +32 -1
  92. package/dist/item/itemInstanceRegistry.js +111 -7
  93. package/dist/item/itemgen.d.ts +1 -1
  94. package/dist/item/itemgen.js +1 -1
  95. package/dist/item/modularItem.js +7 -1
  96. package/dist/material/appearanceSignals.d.ts +53 -0
  97. package/dist/material/appearanceSignals.js +47 -0
  98. package/dist/material/hairCards.d.ts +47 -0
  99. package/dist/material/hairCards.js +136 -0
  100. package/dist/material/materialAsset.d.ts +162 -0
  101. package/dist/material/materialAsset.js +382 -0
  102. package/dist/meta/changelog.d.ts +1 -1
  103. package/dist/meta/changelog.js +348 -1
  104. package/dist/movement/characterController.d.ts +5 -2
  105. package/dist/movement/characterController.js +50 -23
  106. package/dist/movement/freeFlight.d.ts +2 -2
  107. package/dist/movement/freeFlight.js +3 -3
  108. package/dist/movement/movementModel.d.ts +24 -1
  109. package/dist/movement/movementModel.js +74 -7
  110. package/dist/movement/playerMovement.d.ts +28 -1
  111. package/dist/movement/playerMovement.js +425 -102
  112. package/dist/movement/poseState.d.ts +2 -0
  113. package/dist/movement/poseState.js +29 -1
  114. package/dist/movement/solidObstacles.d.ts +3 -1
  115. package/dist/movement/solidObstacles.js +8 -5
  116. package/dist/movement/terrainGrade.d.ts +13 -0
  117. package/dist/movement/terrainGrade.js +33 -0
  118. package/dist/movement/voxelController.d.ts +2 -2
  119. package/dist/movement/voxelController.js +3 -3
  120. package/dist/nav/navGrid.d.ts +19 -1
  121. package/dist/nav/navGrid.js +26 -10
  122. package/dist/nav/solidRoute.d.ts +50 -0
  123. package/dist/nav/solidRoute.js +144 -0
  124. package/dist/physics/ballisticSweep.d.ts +10 -3
  125. package/dist/physics/ballisticSweep.js +53 -29
  126. package/dist/physics/forceVolume.d.ts +41 -0
  127. package/dist/physics/forceVolume.js +88 -0
  128. package/dist/physics/kinematicVehicle.d.ts +8 -3
  129. package/dist/physics/kinematicVehicle.js +2 -2
  130. package/dist/physics/physicsWorldBackend.js +19 -3
  131. package/dist/physics/spatialGrid.d.ts +1 -1
  132. package/dist/physics/spatialGrid.js +8 -9
  133. package/dist/physics/vehicleDynamics.d.ts +3 -2
  134. package/dist/physics/vehicleDynamics.js +2 -2
  135. package/dist/physics/vehicleObstacles.d.ts +3 -2
  136. package/dist/physics/vehicleObstacles.js +29 -5
  137. package/dist/runtime/cameraDirector.d.ts +39 -2
  138. package/dist/runtime/cameraDirector.js +162 -0
  139. package/dist/runtime/commandDispatch.d.ts +14 -0
  140. package/dist/runtime/commandDispatch.js +19 -0
  141. package/dist/runtime/context/combat.d.ts +11 -4
  142. package/dist/runtime/context/combat.js +178 -11
  143. package/dist/runtime/context/deathLoot.d.ts +9 -8
  144. package/dist/runtime/context/deathLoot.js +16 -16
  145. package/dist/runtime/context/scene.d.ts +2 -0
  146. package/dist/runtime/context/scene.js +13 -21
  147. package/dist/runtime/descriptors/baseline.js +34 -3
  148. package/dist/runtime/descriptors/features.d.ts +2 -1
  149. package/dist/runtime/descriptors/features.js +18 -0
  150. package/dist/runtime/gameContext.js +79 -13
  151. package/dist/runtime/gameContextTypes.d.ts +14 -0
  152. package/dist/runtime/headlessRunner.d.ts +3 -1
  153. package/dist/runtime/headlessRunner.js +11 -11
  154. package/dist/runtime/hostedGameRunner.d.ts +24 -1
  155. package/dist/runtime/hostedGameRunner.js +156 -10
  156. package/dist/runtime/hostedWorldSession.d.ts +17 -5
  157. package/dist/runtime/hostedWorldSession.js +154 -16
  158. package/dist/runtime/hostedWorldStore.d.ts +2 -2
  159. package/dist/runtime/inputRecorder.d.ts +1 -1
  160. package/dist/runtime/inputRecorder.js +21 -1
  161. package/dist/runtime/inputSnapshot.d.ts +16 -4
  162. package/dist/runtime/inputSnapshot.js +31 -3
  163. package/dist/runtime/runtimeSave.d.ts +10 -6
  164. package/dist/runtime/runtimeSave.js +11 -5
  165. package/dist/runtime/simContext.d.ts +8 -3
  166. package/dist/runtime/simContext.js +7 -3
  167. package/dist/runtime/simSnapshot.js +2 -0
  168. package/dist/runtime/worldReplication.d.ts +2 -0
  169. package/dist/runtime/worldReplication.js +1 -1
  170. package/dist/scene/assetCatalog.d.ts +2 -0
  171. package/dist/scene/behaviorRuntime.d.ts +26 -5
  172. package/dist/scene/behaviorRuntime.js +263 -16
  173. package/dist/scene/behaviors.d.ts +30 -1
  174. package/dist/scene/behaviors.js +29 -0
  175. package/dist/scene/entityStore.d.ts +14 -3
  176. package/dist/scene/entityStore.js +45 -9
  177. package/dist/scene/mount.d.ts +9 -0
  178. package/dist/scene/mount.js +35 -0
  179. package/dist/scene/possession.d.ts +2 -0
  180. package/dist/scene/possession.js +21 -1
  181. package/dist/scene/sceneRaycast.d.ts +7 -0
  182. package/dist/scene/sceneRaycast.js +55 -0
  183. package/dist/scene/spatial.d.ts +20 -1
  184. package/dist/scene/spatial.js +94 -2
  185. package/dist/scene/vehicleSeat.d.ts +36 -2
  186. package/dist/scene/vehicleSeat.js +89 -2
  187. package/dist/stats/accumulatorMeter.d.ts +10 -0
  188. package/dist/stats/accumulatorMeter.js +6 -0
  189. package/dist/stats/eventMeter.d.ts +9 -2
  190. package/dist/stats/eventMeter.js +5 -2
  191. package/dist/store/defineStore.d.ts +3 -1
  192. package/dist/store/defineStore.js +3 -1
  193. package/dist/store/observableKeyedStore.d.ts +1 -1
  194. package/dist/store/observableKeyedStore.js +8 -6
  195. package/dist/time/simClock.d.ts +6 -2
  196. package/dist/time/simClock.js +5 -0
  197. package/dist/vfx/particleDirector.d.ts +58 -6
  198. package/dist/vfx/particleDirector.js +113 -22
  199. package/dist/vfx/particles.d.ts +68 -5
  200. package/dist/vfx/particles.js +284 -32
  201. package/dist/visibility/assetStreaming.d.ts +21 -3
  202. package/dist/visibility/assetStreaming.js +108 -24
  203. package/dist/visibility/settings.d.ts +8 -2
  204. package/dist/visibility/settings.js +3 -0
  205. package/dist/visibility/visibilitySystem.js +1 -0
  206. package/dist/work/expedition.d.ts +110 -0
  207. package/dist/work/expedition.js +195 -0
  208. package/dist/work/staffedStation.d.ts +29 -0
  209. package/dist/work/staffedStation.js +27 -0
  210. package/dist/world/authoredAnimation.d.ts +38 -0
  211. package/dist/world/authoredAnimation.js +99 -0
  212. package/dist/world/authoredObjects.d.ts +24 -4
  213. package/dist/world/authoredObjects.js +43 -16
  214. package/dist/world/authoredSimulation.d.ts +74 -0
  215. package/dist/world/authoredSimulation.js +302 -0
  216. package/dist/world/authoredWeather.d.ts +120 -0
  217. package/dist/world/authoredWeather.js +314 -0
  218. package/dist/world/buildingKit.d.ts +8 -3
  219. package/dist/world/buildingKit.js +21 -8
  220. package/dist/world/environmentForces.d.ts +21 -0
  221. package/dist/world/environmentForces.js +42 -0
  222. package/dist/world/features.d.ts +3 -3
  223. package/dist/world/footprintGrid.d.ts +55 -0
  224. package/dist/world/footprintGrid.js +201 -0
  225. package/dist/world/grassKind.d.ts +10 -0
  226. package/dist/world/grassKind.js +22 -0
  227. package/dist/world/pathTerrain.d.ts +1 -1
  228. package/dist/world/terraform.d.ts +2 -2
  229. package/dist/world/terraform.js +46 -18
  230. package/dist/world/weather.d.ts +17 -0
  231. package/dist/world/weather.js +116 -20
  232. package/dist/world/worldSolids.d.ts +11 -0
  233. package/dist/world/worldSolids.js +112 -1
  234. package/dist/world.d.ts +5 -5
  235. package/dist/world.js +4 -4
  236. package/package.json +1 -1
  237. package/skills/ce-handoff/SKILL.md +1 -1
  238. package/skills/game-design/SKILL.md +2 -0
  239. package/skills/jgengine/SKILL.md +23 -2
  240. package/skills/jgengine/api.md +28 -17
  241. package/skills/jgengine/recipes/minimal-game.md +7 -2
  242. package/skills/jgengine-combat/SKILL.md +8 -2
  243. package/skills/jgengine-combat/api.md +22 -10
  244. package/skills/jgengine-combat/recipes/portable-runtime-state.md +60 -6
  245. package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +37 -3
  246. package/skills/jgengine-combat/reference.md +24 -2
  247. package/skills/jgengine-gameplay/SKILL.md +63 -0
  248. package/skills/jgengine-gameplay/api.md +131 -37
  249. package/skills/jgengine-gameplay/capabilities.md +68 -0
  250. package/skills/jgengine-gameplay/recipes/device-save-status.md +46 -0
  251. package/skills/jgengine-gameplay/recipes/expeditions.md +108 -0
  252. package/skills/jgengine-gameplay/recipes/narrative-authoring.md +76 -0
  253. package/skills/jgengine-gameplay/recipes/paged-item-storage.md +101 -0
  254. package/skills/jgengine-gameplay/recipes/quest-authoring.md +63 -0
  255. package/skills/jgengine-gameplay/recipes/shaped-inventory.md +50 -0
  256. package/skills/jgengine-gameplay/reference-content-validation.md +74 -0
  257. package/skills/jgengine-gameplay/reference-systems.md +119 -0
  258. package/skills/jgengine-world/SKILL.md +12 -3
  259. package/skills/jgengine-world/api.md +201 -58
  260. package/skills/jgengine-world/capabilities.md +116 -3
  261. package/skills/jgengine-world/recipes/character-animation.md +31 -8
  262. package/skills/jgengine-world/recipes/controls.md +4 -0
  263. package/skills/jgengine-world/recipes/npc-senses.md +12 -0
  264. package/skills/jgengine-world/recipes/vehicle-feel.md +3 -1
  265. package/skills/jgengine-world/reference.md +167 -5
  266. package/skills/jgengine-world/visibility.md +28 -1
@@ -32,6 +32,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
32
32
 
33
33
  - `createAnimGraphRuntime` (function) · `import { createAnimGraphRuntime } from "@jgengine/core/anim/animGraph"`
34
34
  - `parseAnimGraph` (function) · `import { parseAnimGraph } from "@jgengine/core/anim/animGraph"`
35
+ - `validateAnimGraph` (function) · `import { validateAnimGraph } from "@jgengine/core/anim/animGraph"`
35
36
 
36
37
  ## animation — find thigh, shin and foot bones on a humanoid rig for foot IK
37
38
 
@@ -102,10 +103,24 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
102
103
 
103
104
  - `defineAttackMoveOrder` (function) · `import { defineAttackMoveOrder } from "@jgengine/core/world"`
104
105
 
106
+ ## audio-voice-budget — bound simultaneous sound playback with per-sound caps, priorities, and deterministic stealing
107
+
108
+ - `createVoiceAllocator` (function) · `import { createVoiceAllocator } from "@jgengine/core/audio/voiceAllocator"`
109
+
110
+ ## authored-animation — consume live authored character animation with bounded per-frame work
111
+
112
+ - `createAuthoredAnimationReader` (function) · `import { createAuthoredAnimationReader } from "@jgengine/core/world/authoredAnimation"`
113
+ - `readAuthoredAnimation` (function) · `import { readAuthoredAnimation } from "@jgengine/core/world/authoredAnimation"`
114
+ - `readAuthoredAnimationValue` (function) · `import { readAuthoredAnimationValue } from "@jgengine/core/world/authoredAnimation"`
115
+
105
116
  ## authored-entities — spawn authored mob/boss markers from the editor document at runtime
106
117
 
107
118
  - `authoredEntitySpawns` (function) · `import { authoredEntitySpawns } from "@jgengine/core/world/authoredEntities"`
108
119
 
120
+ ## authored-environment — compose authored weather, shelter, accumulation, fire, flocks and effects using one simulation clock
121
+
122
+ - `createAuthoredSimulation` (function) · `import { createAuthoredSimulation } from "@jgengine/core/world/authoredSimulation"`
123
+
109
124
  ## authored-objects — marker kinds resolved as entity spawns, not static props
110
125
 
111
126
  - `ENTITY_MARKER_KINDS` (const) · `import { ENTITY_MARKER_KINDS } from "@jgengine/core/world/authoredObjects"`
@@ -113,6 +128,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
113
128
  - `markerCatalogId` (function) · `import { markerCatalogId } from "@jgengine/core/world"`
114
129
  - `placeAuthoredObjects` (function) · `import { placeAuthoredObjects } from "@jgengine/core/world"`
115
130
  - `resolveAuthoredObjects` (function) · `import { resolveAuthoredObjects } from "@jgengine/core/world"`
131
+ - `resolveAuthoredObjectsWithDiagnostics` (function) · `import { resolveAuthoredObjectsWithDiagnostics } from "@jgengine/core/world/authoredObjects"`
116
132
 
117
133
  ## authored-solids — collision for studio-authored world content such as city volumes
118
134
 
@@ -134,6 +150,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
134
150
  - `registerBuiltinTriggerActions` (function) · `import { registerBuiltinTriggerActions } from "@jgengine/core/scene/authoredTriggers"`
135
151
  - `registerTriggerAction` (function) · `import { registerTriggerAction } from "@jgengine/core/world"`
136
152
 
153
+ ## authored-weather — Sample scheduled precipitation and local wind from one authored document and simulation clock.
154
+
155
+ - `createAuthoredWeather` (function) · `import { createAuthoredWeather } from "@jgengine/core/world/authoredWeather"`
156
+
157
+ ## authored-weather-validation — Reject invalid weather profiles, schedules, zones and wind before authoring mutations.
158
+
159
+ - `validateAuthoredWeather` (function) · `import { validateAuthoredWeather } from "@jgengine/core/world/authoredWeather"`
160
+
137
161
  ## axis-shaping — shape raw input per axis — deadzone, response curve, keyboard ramp, self-centring, speed-sensitive scaling
138
162
 
139
163
  - `createAxisShaper` (function) · `import { createAxisShaper } from "@jgengine/core/input/axisShaper"`
@@ -146,10 +170,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
146
170
 
147
171
  - `behaviorControl` (function) · `import { behaviorControl } from "@jgengine/core/world"`
148
172
 
149
- ## behavior-tick — auto-advance patrol/wander behaviors on spawned entities, no per-game route loop
173
+ ## behavior-tick — auto-advance spawned patrol, wander, pursuit and decision graph behaviors with lifecycle control
150
174
 
151
175
  - `advanceBehaviors` (function) · `import { advanceBehaviors } from "@jgengine/core/world"`
152
176
 
177
+ ## board-vehicle — board a rideable, freeze/hide its rider and apply caller camera policy
178
+
179
+ - `boardVehicle` (function) · `import { boardVehicle } from "@jgengine/core/world"`
180
+
153
181
  ## boat-dynamics — force-based powered hull — hull-speed drag hump, planing, keel, rudder that needs flow or outboard thrust
154
182
 
155
183
  - `createBoatDynamics` (function) · `import { createBoatDynamics } from "@jgengine/core/physics/boatDynamics"`
@@ -158,6 +186,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
158
186
 
159
187
  - `createBodyBind` (function) · `import { createBodyBind } from "@jgengine/core/world"`
160
188
 
189
+ ## bounded-grid-route — distinguish no path from exhausted grid search work
190
+
191
+ - `findPathResult` (function) · `import { findPathResult } from "@jgengine/core/nav/navGrid"`
192
+
161
193
  ## build-footprint-cells — grid cells a snapped build piece footprint covers (footprintGrid occupancy bridge)
162
194
 
163
195
  - `footprintCells` (function) · `import { footprintCells } from "@jgengine/core/world/buildSockets"`
@@ -296,6 +328,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
296
328
 
297
329
  - `entityMetaOf` (function) · `import { entityMetaOf } from "@jgengine/core/world"`
298
330
 
331
+ ## environment-motion — apply capped spatial wind and vortex forces to game-selected actors through shared motion intents
332
+
333
+ - `installEnvironmentMotion` (function) · `import { installEnvironmentMotion } from "@jgengine/core/world/environmentForces"`
334
+
299
335
  ## fast-travel — network of fast-travel destinations with discovery, distance-sorted queries, nearest lookup, a travel gate, and serializable state
300
336
 
301
337
  - `createFastTravelNetwork` (function) · `import { createFastTravelNetwork } from "@jgengine/core/world"`
@@ -320,6 +356,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
320
356
 
321
357
  - `createFootprintGrid` (function) · `import { createFootprintGrid } from "@jgengine/core/world"`
322
358
 
359
+ ## force-field-validation — reject invalid localized force authoring before simulation
360
+
361
+ - `validateForceField` (function) · `import { validateForceField } from "@jgengine/core/physics/forceVolume"`
362
+
323
363
  ## formation-assignment — stable deterministic member-to-slot matching for a group formation
324
364
 
325
365
  - `assignFormationSlots` (function) · `import { assignFormationSlots } from "@jgengine/core/world"`
@@ -426,6 +466,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
426
466
 
427
467
  - `measureLean` (function) · `import { measureLean } from "@jgengine/core/physics/handlingProbe"`
428
468
 
469
+ ## leave-vehicle — leave a rideable beside its current pose and restore on-foot camera/movement
470
+
471
+ - `leaveVehicle` (function) · `import { leaveVehicle } from "@jgengine/core/world"`
472
+
429
473
  ## limb-health — per-body-part/region health tracked separately
430
474
 
431
475
  - `createMultiRegionHealth` (function) · `import { createMultiRegionHealth } from "@jgengine/core/procedural"`
@@ -434,6 +478,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
434
478
 
435
479
  - `resolveLocalAvoidance` (function) · `import { resolveLocalAvoidance } from "@jgengine/core/world"`
436
480
 
481
+ ## localized-forces — sample signed attraction, directional force, vortex spin and axial lift with target masks and caps
482
+
483
+ - `sampleForceField` (function) · `import { sampleForceField } from "@jgengine/core/physics/forceVolume"`
484
+
437
485
  ## lockpick — a solvable grid depth-puzzle with fog-of-war, gates, and hidden traps
438
486
 
439
487
  - `generateLock` (function) · `import { generateLock } from "@jgengine/core/world"`
@@ -447,6 +495,19 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
447
495
 
448
496
  - `createAnnotationLayer` (function) · `import { createAnnotationLayer } from "@jgengine/core/world"`
449
497
 
498
+ ## material-appearance-signals — adapt selected surface response to sampled wetness and local card exposure
499
+
500
+ - `resolveMaterialAppearance` (function) · `import { resolveMaterialAppearance } from "@jgengine/core/material/appearanceSignals"`
501
+ - `sampleMaterialAppearance` (function) · `import { sampleMaterialAppearance } from "@jgengine/core/material/appearanceSignals"`
502
+
503
+ ## merge-footprint-regions — pool adjacent compatible footprints with serializable capacity provenance
504
+
505
+ - `mergeFootprints` (function) · `import { mergeFootprints } from "@jgengine/core/world"`
506
+
507
+ ## mergeable-footprint-regions — check compatible adjacent regions within caller work budgets
508
+
509
+ - `canMerge` (function) · `import { canMerge } from "@jgengine/core/world"`
510
+
450
511
  ## mesh-hitboxes — shots pass through holes in concave models — opted-in catalog assets raycast their actual triangles instead of the fitted box.
451
512
 
452
513
  - `ColliderShape` (type) · `import { ColliderShape } from "@jgengine/core/scene/colliders"`
@@ -476,6 +537,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
476
537
 
477
538
  - `measureMovement` (function) · `import { measureMovement } from "@jgengine/core/movement/movementProbe"`
478
539
 
540
+ ## movement-telemetry — read physical grounded, vertical velocity and crouch state without animation-store writes
541
+
542
+ - `playerMovementTelemetry` (function) · `import { playerMovementTelemetry } from "@jgengine/core/movement/playerMovement"`
543
+
544
+ ## moving-box-sweep — detect contact with boxes crossing a projectile segment between simulation poses
545
+
546
+ - `sweepMovingBounds` (function) · `import { sweepMovingBounds } from "@jgengine/core/physics/ballisticSweep"`
547
+
548
+ ## moving-sphere-sweep — detect contact with spheres crossing a projectile segment between simulation poses
549
+
550
+ - `sweepMovingSphere` (function) · `import { sweepMovingSphere } from "@jgengine/core/physics/ballisticSweep"`
551
+
479
552
  ## nav-from-solids — block world solids on a nav grid
480
553
 
481
554
  - `populateNavGridFromSolids` (function) · `import { populateNavGridFromSolids } from "@jgengine/core/nav/navFromEnvironment"`
@@ -500,11 +573,19 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
500
573
 
501
574
  - `createOrderRegistry` (function) · `import { createOrderRegistry } from "@jgengine/core/world"`
502
575
 
503
- ## particle-director — game-reachable VFX seam: queue one-shot particle bursts and keyed follow-capable standing emitters as serializable data for the shell to render
576
+ ## particle-binding-validation — validate named particle follow spaces, render outputs, environment influence, and bounded collision child effects
577
+
578
+ - `validateParticleAttachOptions` (function) · `import { validateParticleAttachOptions } from "@jgengine/core/vfx/particleDirector"`
579
+
580
+ ## particle-director — named particle start/stop/burst/retune lifecycle, local/world following, render outputs, bounded cosmetic event exchange, and serializable emitter definitions
504
581
 
505
582
  - `createParticleDirector` (function) · `import { createParticleDirector } from "@jgengine/core/vfx/particleDirector"`
506
583
 
507
- ## particle-system — deterministic pooled particle emitter with SoA render buffers, burst/continuous emission, gravity/drag, size/color/alpha-over-life, and serializable state
584
+ ## particle-emitter-validation — validate finite spawn volumes, force fields, lifetimes, appearance curves, and bounded particle collision policies
585
+
586
+ - `validateEmitterConfig` (function) · `import { validateEmitterConfig } from "@jgengine/core/vfx/particles"`
587
+
588
+ ## particle-system — deterministic pooled particle emitter with spawn volumes, force fields, bounded collision/death events, live tuning, SoA render buffers, and serializable state
508
589
 
509
590
  - `createParticleSystem` (function) · `import { createParticleSystem } from "@jgengine/core/world"`
510
591
 
@@ -585,6 +666,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
585
666
 
586
667
  - `resolveActivePrompt` (function) · `import { resolveActivePrompt } from "@jgengine/core/world"`
587
668
 
669
+ ## pursue-behavior — scheduled pursuit with bounded target acquisition and saved cooldown/leash state
670
+
671
+ - `pursue` (function) · `import { pursue } from "@jgengine/core/world"`
672
+
588
673
  ## pursuit — enemy/mob aggro chase-to-reach with a cooldown-gated attack and optional leash-to-home; owns the attack cooldown
589
674
 
590
675
  - `advancePursuit` (function) · `import { advancePursuit } from "@jgengine/core/ai/pursuit"`
@@ -659,6 +744,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
659
744
  - `obstacleFromSolid` (function) · `import { obstacleFromSolid } from "@jgengine/core/movement/solidObstacles"`
660
745
  - `solidObstaclesNear` (function) · `import { solidObstaclesNear } from "@jgengine/core/movement/solidObstacles"`
661
746
 
747
+ ## solid-route — bounded local planning over indexed collision geometry
748
+
749
+ - `planSolidRoute` (function) · `import { planSolidRoute } from "@jgengine/core/nav/solidRoute"`
750
+
751
+ ## spatial-incremental — Update one entity's spatial cell after a committed write without scanning the population
752
+
753
+ - `createSpatialApi` (function) · `import { createSpatialApi } from "@jgengine/core/scene/spatial"`
754
+
662
755
  ## spatial-region-box — sample uniformly within an axis-aligned 3D box
663
756
 
664
757
  - `boxRegion` (function) · `import { boxRegion } from "@jgengine/core/world"`
@@ -699,6 +792,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
699
792
 
700
793
  - `sampleStratified` (function) · `import { sampleStratified } from "@jgengine/core/world"`
701
794
 
795
+ ## split-footprint-regions — split pooled regions along retained contributions without area-based capacity loss
796
+
797
+ - `splitRegion` (function) · `import { splitRegion } from "@jgengine/core/world"`
798
+
702
799
  ## stop-order — halt-and-complete order that brings a unit to an immediate standstill
703
800
 
704
801
  - `defineStopOrder` (function) · `import { defineStopOrder } from "@jgengine/core/world"`
@@ -719,6 +816,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
719
816
 
720
817
  - `surfaceRing` (function) · `import { surfaceRing } from "@jgengine/core/world"`
721
818
 
819
+ ## sync-authored-objects — synchronize edited catalog prop poses, elevation and removal through the existing object store
820
+
821
+ - `syncAuthoredObjects` (function) · `import { syncAuthoredObjects } from "@jgengine/core/world/authoredObjects"`
822
+
722
823
  ## tactical-cover-points — derive cover candidates from navigation-mesh boundaries
723
824
 
724
825
  - `coverPoints` (function) · `import { coverPoints } from "@jgengine/core/ai/tacticalQueries"`
@@ -780,6 +881,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
780
881
  - `setTouchControlsMode` (function) · `import { setTouchControlsMode } from "@jgengine/core/input/touchControlsMode"`
781
882
  - `touchButtonShape` (function) · `import { touchButtonShape } from "@jgengine/core/input/touchScheme"`
782
883
 
884
+ ## uphill-grade-step — constrain sampled heightfield ascent with axis sliding and a configurable terrain-policy sampler
885
+
886
+ - `resolveTerrainGradeStep` (function) · `import { resolveTerrainGradeStep } from "@jgengine/core/movement/terrainGrade"`
887
+
783
888
  ## vehicle-backend-link — collide a vehicle sim with a physics backend — shove props, stop at walls, slide along them
784
889
 
785
890
  - `createVehicleBackendLink` (function) · `import { createVehicleBackendLink } from "@jgengine/core/physics/vehicleBackendLink"`
@@ -816,6 +921,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
816
921
 
817
922
  - `createWaypointStore` (function) · `import { createWaypointStore } from "@jgengine/core/world"`
818
923
 
924
+ ## weather-environment-field — Connect authored precipitation and temperature changes to spatial environment queries.
925
+
926
+ - `createWeatherEnvironmentField` (function) · `import { createWeatherEnvironmentField } from "@jgengine/core/world/authoredWeather"`
927
+
928
+ ## weather-surface-accumulation — Persist and retune bounded surface wetness, snow, temperature and exposure independently of rendering.
929
+
930
+ - `createWeatherExposure` (function) · `import { createWeatherExposure } from "@jgengine/core/world/authoredWeather"`
931
+
819
932
  ## world-environment — compose editor/preset-derived outdoor dressing — terrain, weather, water, structures — into a legacy environment feature
820
933
 
821
934
  - `environment` (function) · `import { environment } from "@jgengine/core/world"`
@@ -4,20 +4,43 @@
4
4
 
5
5
  ## The seams
6
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.
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. Equivalent same-order animation data survives ordinary React rerenders without restarting gait, pending triggers or visual variant choices. The hook retains a private serialized snapshot; intentional clip, playback or graph edits restart that model’s playback. Keep authored one-shot order: it decides priority among simultaneous non-death triggers. Changing the scene, imported clips, context or instance identity starts a new animation lifecycle.
8
+ - **Playback clock.** An entity-bound model (`instanceId` with a game context) holds its animation while game time is paused or has zero timescale. Resume continues the existing mixer/graph and queued triggers. Omitted `animation.clock` or `clock: "real"` retains real-time playback. Choose `{ auto: true, clock: "game" }` for a character whose gait and clip progress should follow locally accepted simulation time. The shell samples `ctx.time.advancedSeconds()` rather than multiplying the raw render delta: clamped frames, fixed steps with no accepted tick and dropped catch-up time cannot advance clips ahead of movement. Accepted seconds already include play speed and timescale, exclude calendar compression, and never hydrate or serialize with saved clock offsets. Held frames checkpoint progress so resume does not replay their elapsed time; `animation.timeScale` remains a separate authored visual rate. Context-hosted unbound previews and previews without a context keep using real time.
9
+ - `createSimClock` supplies the allocation-free progress getter. The getter is optional on external structural clocks; selecting game playback without one produces a repair warning and holds the pose. Provide that clock capability or choose real-time playback.
10
+ - This is local accepted progress. Hosted prediction with `advanceTime: false` contributes no progress; it does not supply an accepted remote timeline. Keep real-time playback for those clients unless their clock supplies the intended progress. No snapshot, persistence or signaling format changes are required by the local policy.
11
+ - **Automatic roles with controls.** `animation: "auto"` retains the existing defaults. `{ auto: true, clock: "game" }` derives those same roles and one-shot variants from the loaded rig, then applies explicit configuration fields. Explicit `states`, `oneShots` and `graph` replace their whole fields rather than merging mappings; empty maps retain their meaning. A rig without an identifiable idle does not acquire a guessed first-clip fallback. In the inspector's Custom playback controls, select the clock and optionally derive loaded clip roles; selecting a single clip clears automatic derivation. Both settings are ordinary `meta.animation` data and use the same strict reader, undo, save and reload path as other animation controls.
12
+ - **Modular visibility.** `ModelConfig.hiddenNodes: ["Knife", "Rogue_Cape"]` hides named visual subtrees on that model instance. Ordinary entity models and custom `useModelInstance` hosts share the selection. Names resolve through `resolveRigNode`; missing or ambiguous names warn and leave the body intact. Malformed lists reject before model or texture allocation. Imported invisible nodes stay invisible, and omitting the field or passing `[]` preserves imported visibility. This does not delete bones, retarget clips or mask skinned vertices. The selected visible bind bounds drive `targetHeight` and grounding before animation; catalog dimensions remain source metadata. Reordered or duplicate names retain the clone; a changed selection replaces only that instance and restarts its visual animation lifecycle. Choose names from the actual asset; instanced crowd/scatter renderers do not implement this selection.
13
+ - **Placement.** `targetHeight` normalizes skin-applied imported bind bounds and grounds them at `ModelConfig.y`, then `scale` multiplies that height. The renderer caches skin-applied vertices before animation and shares them between clones, so skin deformation cannot change normalization or fitted collision triangles. Imported root transforms stay inside a separate placement frame shared by render bounds and collision triangles; clips keep binding to the imported root.
14
+ - Editor clip and graph previews use this same placement frame through `useModelInstance`; they pose its `content` and mount its `scene` with the returned `scale` and `position`. Clip metadata uses the shared loader cache, and closing a preview releases only its own materials and bone textures.
15
+ - Heightfield player collision has its own `movement.collisionHeight` (positive meters from feet; default 1.8). Choose it for the game's physical body and author it through a supplied `createMovementSchema` catalog field. Rendered `targetHeight`/`scale` and camera target/eye height remain independent; they do not silently resize the motor. Capsule and voxel controllers keep their declared dimensions.
16
+ - **Movement authoring.** A game-named movement catalog can expose the motor's existing jump buffer, coyote grace, release factor, apex/fall gravity and landing recovery controls through supplied `createMovementSchema` defaults. Deeper controls appear in a collapsed **Jump response** group; the common setup does not acquire unused controls. Consume the saved row with `bindAuthoredMovement` to tune `movement.feel` live while retaining the game's callbacks and physics. These are physical movement controls; graph clips and transition choices remain authored separately.
8
17
  - **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.
18
+ - `locomotionGraph({ idle, walk, run, walkSpeed, runSpeed, fadeSec, oneShots })` builds the idle/walk/run blend plus one-shots; `states`/`oneShots` configs are converted through it. One-shot arrays retain every variant. `death` takes priority over simultaneous triggers and holds until the graph is reset; later hit/attack triggers cannot release it.
10
19
  - 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`.
20
+ - Each layer owns its clip times and weights. Within one layer, two states using the same clip still resolve to one clip time; blending separate times of that same clip is unsupported.
21
+ - `createAnimGraphRuntime(graph, { rng })` runs the same graph headless: `advance(dt, params, clips)`, `trigger`, `snapshot`/`restore`, `retune`. A clip state's `variants` are chosen once on entry; snapshots preserve that choice. Omit `rng` for the first variant. Runtime models use independent seeded visual streams keyed by instance id and imported clip names, so rendering or culling models cannot advance gameplay randomness. Remounting restarts that visual stream; gameplay-critical clip choices belong in a caller-owned headless runtime and its saved state. Outgoing clips keep advancing during crossfades. Test transitions with it before looking at pixels.
22
+ - **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. A missing/blank idle role leaves a named single clip active (or the bind pose if none is named); a missing/blank walk role holds the named idle at walking speeds. The inspector seeds supported semantic roles from the actual asset when enabling locomotion; imported assets without such roles need explicit choices. `validateAnimGraph(json)` rejects malformed saved graphs with located repairs, preserving valid graph data and transition order. `parseAnimGraph(json)` remains the permissive import path that drops malformed entries.
23
+ - **Static character placements.** Catalog placements validate saved `meta.animation` with the same strict value reader as dynamic characters and the inspector. A malformed override is omitted so the original catalog animation remains active; the saved document and placement stay unchanged. `resolveAuthoredObjectsWithDiagnostics(document)` returns the ordinary objects plus located repairs in one scan, and `<AuthoredObjects>` reports those repairs at runtime. Valid partial roles, empty maps, signed rates, ordered variants and extension fields keep their authored meaning. Asset clip and rig compatibility still belongs to the model diagnostics.
24
+ - **Dynamic character authoring.** Store animation on the game-selected marker at `meta.animation` through ordinary editor commands, then use `createAuthoredAnimationReader(() => getDocumentLiveSync()?.getDocument() ?? editorLayers, markerId, gameDefault)` (`@jgengine/core/world/authoredAnimation`). A game-owned `ModelConfig.animation` getter returns `read().animation`; the game chooses the actor-to-marker association, rig and defaults. The reader validates only after immutable document identity changes. Authored objects replace defaults rather than merging their roles, graph or combat triggers; explicit `"auto"`/`"none"` and in-progress empty/partial roles survive save/reload. Omitted or malformed overrides retain the exact game default, and `read().diagnostics` supplies document paths and repairs. Surface those diagnostics during inspection; asset clip and rig compatibility still use `diagnoseModelAnimation`.
25
+ - **Parameters.** The shell feeds the entity's smoothed ground speed as `speed`. For shared player movement it also reads physically resolved `grounded`, `verticalSpeed` and `crouched` through `playerMovementTelemetry(ctx, entityId)`; these values override conflicting blackboard extras. Custom movers retain their authored extras under `ANIM_PARAMS_KEY` when telemetry is unavailable. `readModelAnimationParams(ctx, entityId, speed, out)` gives custom render hosts the same rule, clearing and reusing their own dictionary without writing game state. The names are exported as `LOCOMOTION_SPEED_PARAM`, `LOCOMOTION_GROUNDED_PARAM`, `LOCOMOTION_VERTICAL_SPEED_PARAM` and `LOCOMOTION_CROUCHED_PARAM`.
26
+ - Jump clips remain explicit rig choices in the game's graph. A supported rig can enter its start clip on `!grounded && verticalSpeed > 0`, its air loop at the physical apex, and its landing clip on ground contact. Let an airborne or landing state return to takeoff when vertical speed becomes positive so a buffered re-jump is visible even if rendering misses the grounded frame. Use `verticalSpeed < 0` for falling from locomotion so unavailable movement data does not invent a fall. Verify those transitions against that rig and the game's physical motor; no automatic jump mapping or universal compatibility is supplied.
27
+ - **Placement discontinuities.** The shell uses the live simulation `snapDistance` to discard inferred gait speed when a character moves farther than that distance in three dimensions between visual samples. Retune it through `ctx.sim.retune({ snapDistance })` with the game's existing interpolation policy; `Infinity` permits continuous fast motion. Sampling resets the speed history while preserving graph time, triggers, variants and terminal states. The shared motor clears its retained state when its driven actor despawns. Raw position updates retain physical motor state; a reconstruction policy can call `forgetPlayerMovement` explicitly.
14
28
  - **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.
29
+ - **Events.** `graph.events: [{ clip, atSec, name }]` emit `animation.event` when an influencing clip crosses that time. Full-cycle frames still fire; multiple missed cycles coalesce to one event per advance. Hook footstep audio, the damage frame of a swing or the magazine swap of a reload to the event, not to a timer.
30
+ - **Root motion.** `rootMotion: true` extracts local root-bone travel into `output.rootDelta` and plays the rig in place horizontally. The in-place flag remains active while a fading-out root-motion clip influences the pose, including interrupted fades and restored graph snapshots. The renderer never changes entity position: the movement/collision owner must consume the headless delta, transform it through the imported rig's parent/facing/scale with `takeRootMotion`, and resolve it through its controller. The bone's vertical motion remains animated. `createGraphPose.apply(output.clips, output.rootMotion)` gives editor/custom previews the same in-place policy. Existing graphs that relied on renderer-driven movement must migrate to a collision-authoritative host; no automatic vault or dodge motor is supplied. Extraction currently assumes the first bone has a matching `.position` track. Root deltas sample the current state's travel; they do not integrate outgoing travel or transition fade weights. Hosts needing collision-authoritative blended root travel must implement that policy explicitly; the in-place visual guarantee does not imply blended root-delta support.
31
+ - **Diagnostics.** `diagnoseModelAnimation(scene, config, clips)` checks incomplete locomotion roles, missing active clip mappings, masks that match no used tracks and unsupported root-track assumptions. Explicit graphs take precedence over dormant role/single-clip settings, so inactive settings do not produce misleading warnings. Runtime and graph previews warn with the same repair guidance. An explicit invalid single clip retains the bind pose rather than playing an arbitrary first clip.
32
+ - Inspector and Graph panels validate saved `meta.animation` with the same strict reader. A malformed override shows its document path and repair, leaves the saved data intact, and blocks ordinary playback edits and graph preview. Diagnostics remain visible without imported clip metadata. Explicit replacement discards the invalid override through an undoable marker edit; Custom derivation requires the rig's real clip list. Valid partial roles, signed playback values, variant order and extension fields survive unrelated edits.
33
+ - **Imported rig slots.** `ModelConfig.attachments[].slot` uses an exact runtime node name first, then a unique original imported name (`userData.name`). GLTFLoader can sanitize `handslot.r` to `handslotr`; authored slot references keep working without renaming animated bones.
34
+ - `resolveRigNode(rig, name)` (`@jgengine/shell/render/rigNode`) returns the instance node or a missing/ambiguous-name diagnostic. Duplicate references need a unique runtime name or repaired imported names; no arbitrary target is selected.
35
+ - Resolve on model load or slot changes and keep the node with its own instance. Attachments retain their local offsets under the animated slot and the model's placement, facing and scale; cleanup removes only that instance's attachment.
36
+ - This resolves node identity; it does not retarget animations or make different rigs share attachment proportions.
17
37
  - **Foot IK.** `ModelConfig.ik` runs after the mixer each frame.
18
38
  - `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`.
39
+ - Explicit joint, `pelvis` and `lookAt.bone` references use exact runtime names first, then the same unique original imported-name lookup as attachments. A name such as `upperleg.r` can resolve after GLTFLoader sanitizes it to `upperlegr`; this preserves node identity and does not supply rig retargeting.
40
+ - Each named joint must resolve uniquely and descend from the preceding joint; intermediate helper transforms are allowed. Missing, ambiguous, disconnected or overlapping chains produce a resolution warning and are skipped. Missing `pelvis` and `lookAt.bone` names also warn. Automatic pelvis correction uses the resolved legs' common ancestor; an explicit `pelvis` retains the authored selection. Auto detection is a naming convention, not compatibility with every rig.
19
41
  - 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
42
  - `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.
43
+ - Disabling or replacing IK clears its previous corrections and resets smoothing; unmount restores untouched joint poses while preserving newer animation writes. This keeps editor play/adjust loops from leaving an offset pelvis after IK is removed.
21
44
  - 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
45
  - **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
46
  - 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.
@@ -35,5 +58,5 @@
35
58
  | Knees buckle on steep ground | lower `ik.maxAdjust` |
36
59
  | Feet tilt wrongly on rubble or props | lower `ik.alignToGround` |
37
60
  | 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 |
61
+ | Lunge distance doesn't match the feet | extract `rootMotion: true` travel and resolve it through the collision owner |
39
62
  | Hundreds of animated characters cost thousands of draw calls | `SkinnedInstances` for the background crowd |
@@ -6,9 +6,13 @@
6
6
 
7
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
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
+ - **Overlapping menus.** `suspendPlayControls(ctx)` returns one idempotent release function per owner. Acquire while a menu is open and release on close, context replacement or unmount; controls resume only after the final owner releases and the existing play gate permits it. Shell settings own this lease automatically. Binding snapshots omit leases, and restoring bindings preserves the current context's live owners. Suspension clears held keyboard, pointer and analog input, so resuming requires fresh input; it does not pause a shared realm. Ordinary binding-context swaps retain held input.
9
10
  - **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
11
  - **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
12
  - **Timing.** `createInputBuffer` (`@jgengine/core/input/inputBuffer`) buffers early presses, coyote time, hold duration and double taps.
13
+ - **Simulation edges.** The shell retains a press released before the next fixed step; `ctx.input.justPressed` samples it on that step while `isDown` already reflects release. Headless callers can supply `presses` to `publishInput`; record/replay applies those edges only on the recorded tick. Canvas mouse bindings use `mouse0`/`mouse1`/`mouse2`, including native button chords; overlays do not begin gameplay presses. Focus loss, suspension and owner teardown clear pending local presses.
14
+ - **Hosted saves.** Persist `HostedGameRunner.state()` for reconstruction. Its host-only input admission and pending-edge state prevents retransmitted stored input from replaying a consumed press; `snapshot()` and client replication omit that bookkeeping.
15
+ - **Hosted retention.** Join or resume a member before publishing input; nonmember frames are ignored. The runner compacts elapsed input in memory while retaining continuous intent and every unconsumed future replay tick; a standalone `InputRecorder` keeps its full log. `inputRetention` configures departed admission marks (default 256 users, 5 minutes; either limit can be zero), with `nowMs` in wall-clock milliseconds. Recent reconnect protection is process-local and ends on expiry or oldest-entry eviction. `inputStats()` reports actual retained recorders, frames and admission marks for host health checks.
12
16
  - **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
17
  - **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
18
  - **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.
@@ -35,6 +35,18 @@ Working example: the `guard-probe` dev demo (`apps/dev/src/demo/guardProbe.ts`),
35
35
 
36
36
  ## Gotchas
37
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
+
38
50
  - Hand the graph facts, not objects: the blackboard serializes with the behavior,
39
51
  so saves and replays keep an NPC mid-investigation.
40
52
  - Judge footstep noise on speed averaged over a window, not one frame; frame-time
@@ -10,7 +10,7 @@
10
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
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
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.
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
14
  - **Two wheels.** A `lean` block (`maxLean`, `leanRate`, optional `countersteer`) turns the same sim into a motorcycle or bicycle.
15
15
  - Steer asks for a lean, and the bar steers the balanced turn that lean needs.
16
16
  - The bike tips the other way briefly as the lean starts, which is countersteer.
@@ -19,6 +19,8 @@
19
19
  - `measureLean` reports steady lean, time to lean and counter-lean.
20
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
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.
22
24
  - Speed comes from the entity's sim `velocity`, so publish poses with `setPose({ ..., dt })` (`tickDrivableVehicle` does). `fov.response` eases the FOV.
23
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.
24
26
  - `pitchFollow` tilts the boom with `rotationX` on ramps and slopes. `lookBackAction` names an input action that looks behind while held.