@jgengine/core 0.18.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. package/CHANGELOG.md +504 -7
  2. package/dist/ai/decisionGraph.d.ts +50 -4
  3. package/dist/ai/decisionGraph.js +159 -23
  4. package/dist/ai/flock.d.ts +1 -1
  5. package/dist/ai/flock.js +7 -4
  6. package/dist/ai/groupAssist.d.ts +10 -0
  7. package/dist/ai/groupAssist.js +11 -0
  8. package/dist/ai/pursuit.d.ts +1 -1
  9. package/dist/ai/pursuit.js +1 -1
  10. package/dist/ai/threat.d.ts +8 -0
  11. package/dist/ai/threat.js +10 -0
  12. package/dist/anim/animGraph.d.ts +43 -1
  13. package/dist/anim/animGraph.js +326 -10
  14. package/dist/anim/boneTexture.d.ts +55 -0
  15. package/dist/anim/boneTexture.js +43 -0
  16. package/dist/anim/footPlacement.d.ts +57 -0
  17. package/dist/anim/footPlacement.js +67 -0
  18. package/dist/anim/locomotionGraph.d.ts +18 -1
  19. package/dist/anim/locomotionGraph.js +42 -3
  20. package/dist/audio/audioFalloff.d.ts +6 -0
  21. package/dist/audio/doppler.d.ts +21 -0
  22. package/dist/audio/doppler.js +32 -0
  23. package/dist/audio/engineLayers.d.ts +83 -0
  24. package/dist/audio/engineLayers.js +115 -0
  25. package/dist/audio/voiceAllocator.d.ts +46 -0
  26. package/dist/audio/voiceAllocator.js +76 -0
  27. package/dist/combat/abilityKit.d.ts +20 -0
  28. package/dist/combat/abilityKit.js +41 -4
  29. package/dist/combat/death.d.ts +11 -3
  30. package/dist/combat/death.js +3 -3
  31. package/dist/combat/magazine.d.ts +9 -0
  32. package/dist/combat/magazine.js +24 -2
  33. package/dist/combat/projectiles.d.ts +94 -2
  34. package/dist/combat/projectiles.js +282 -61
  35. package/dist/combat/regenShield.d.ts +14 -0
  36. package/dist/combat/regenShield.js +23 -7
  37. package/dist/combat/shotOrigin.d.ts +6 -0
  38. package/dist/combat/shotOrigin.js +6 -0
  39. package/dist/combat/weaponFeedback.d.ts +36 -0
  40. package/dist/combat/weaponFeedback.js +42 -0
  41. package/dist/combat/weaponHandling.d.ts +161 -0
  42. package/dist/combat/weaponHandling.js +202 -0
  43. package/dist/combat/weaponPresentation.d.ts +115 -0
  44. package/dist/combat/weaponPresentation.js +96 -0
  45. package/dist/crafting/recipeCatalog.d.ts +16 -0
  46. package/dist/crafting/recipeCatalog.js +55 -0
  47. package/dist/devtools/warnOnce.d.ts +8 -0
  48. package/dist/devtools/warnOnce.js +18 -0
  49. package/dist/economy/auctionBook.d.ts +1 -1
  50. package/dist/economy/auctionBook.js +11 -1
  51. package/dist/economy/listingBook.d.ts +1 -1
  52. package/dist/economy/listingBook.js +11 -1
  53. package/dist/economy/resourceLedger.js +3 -3
  54. package/dist/economy/shopStock.js +7 -6
  55. package/dist/economy/techTree.js +7 -6
  56. package/dist/economy/unlockPoints.js +3 -6
  57. package/dist/editor/commandApply.js +89 -16
  58. package/dist/editor/commands.d.ts +38 -1
  59. package/dist/editor/commands.js +320 -3
  60. package/dist/editor/creatorStorage.d.ts +59 -0
  61. package/dist/editor/creatorStorage.js +164 -0
  62. package/dist/editor/document.d.ts +4 -0
  63. package/dist/editor/document.js +119 -18
  64. package/dist/editor/environment.js +8 -0
  65. package/dist/editor/index.d.ts +2 -0
  66. package/dist/editor/liveSync.d.ts +4 -1
  67. package/dist/editor/liveSync.js +26 -10
  68. package/dist/editor/materialAuthoring.d.ts +14 -0
  69. package/dist/editor/materialAuthoring.js +38 -0
  70. package/dist/editor/modelNodeVisibility.d.ts +11 -0
  71. package/dist/editor/modelNodeVisibility.js +29 -0
  72. package/dist/editor/movementCatalog.d.ts +90 -0
  73. package/dist/editor/movementCatalog.js +144 -0
  74. package/dist/editor/simulation.d.ts +60 -0
  75. package/dist/editor/simulation.js +202 -0
  76. package/dist/editor/staticPrefab.d.ts +17 -0
  77. package/dist/editor/staticPrefab.js +51 -0
  78. package/dist/editor/types.d.ts +15 -0
  79. package/dist/faction/reputation.d.ts +6 -0
  80. package/dist/faction/reputation.js +11 -0
  81. package/dist/game/cameraConfig.d.ts +54 -2
  82. package/dist/game/clipRoles.d.ts +2 -1
  83. package/dist/game/clipRoles.js +7 -3
  84. package/dist/game/contentValidation.d.ts +69 -0
  85. package/dist/game/contentValidation.js +118 -0
  86. package/dist/game/controlGate.d.ts +8 -0
  87. package/dist/game/controlGate.js +43 -6
  88. package/dist/game/defineGame.d.ts +32 -3
  89. package/dist/game/dialogueGraph.d.ts +20 -0
  90. package/dist/game/dialogueGraph.js +107 -0
  91. package/dist/game/events.d.ts +11 -0
  92. package/dist/game/keyValueStore.d.ts +17 -4
  93. package/dist/game/keyValueStore.js +35 -23
  94. package/dist/game/lootModifiers.d.ts +17 -0
  95. package/dist/game/lootModifiers.js +26 -0
  96. package/dist/game/lootTable.d.ts +4 -0
  97. package/dist/game/lootTable.js +16 -0
  98. package/dist/game/narrativeAuthoring.d.ts +67 -0
  99. package/dist/game/narrativeAuthoring.js +122 -0
  100. package/dist/game/playableGame.d.ts +134 -35
  101. package/dist/game/quest.d.ts +15 -0
  102. package/dist/game/quest.js +209 -63
  103. package/dist/game/questCatalog.d.ts +50 -0
  104. package/dist/game/questCatalog.js +205 -0
  105. package/dist/game/saveStore.d.ts +6 -5
  106. package/dist/game/saveStore.js +57 -57
  107. package/dist/game/systemRuntime.d.ts +2 -2
  108. package/dist/game/systemRuntime.js +84 -33
  109. package/dist/game/toasts.d.ts +8 -0
  110. package/dist/game/toasts.js +7 -0
  111. package/dist/game/vfxInstance.d.ts +9 -0
  112. package/dist/game/vfxInstance.js +12 -1
  113. package/dist/game/viewports.d.ts +43 -0
  114. package/dist/game/viewports.js +46 -0
  115. package/dist/game/worldItem.d.ts +5 -1
  116. package/dist/game/worldItem.js +0 -1
  117. package/dist/gameplay.d.ts +3 -0
  118. package/dist/gameplay.js +3 -0
  119. package/dist/input/actionBindings.d.ts +15 -1
  120. package/dist/input/actionBindings.js +14 -3
  121. package/dist/input/actionContexts.d.ts +25 -1
  122. package/dist/input/actionContexts.js +82 -25
  123. package/dist/input/axisInput.d.ts +4 -0
  124. package/dist/input/axisInput.js +4 -0
  125. package/dist/input/axisShaper.d.ts +73 -0
  126. package/dist/input/axisShaper.js +105 -0
  127. package/dist/input/gamepadModel.d.ts +36 -2
  128. package/dist/input/gamepadModel.js +69 -30
  129. package/dist/input/haptics.d.ts +50 -0
  130. package/dist/input/haptics.js +74 -0
  131. package/dist/input/inputBuffer.d.ts +40 -0
  132. package/dist/input/inputBuffer.js +59 -0
  133. package/dist/input/lookChannel.d.ts +10 -0
  134. package/dist/input/lookChannel.js +6 -0
  135. package/dist/input/pointer.d.ts +2 -0
  136. package/dist/input/pointer.js +4 -0
  137. package/dist/interaction/promptRegistry.d.ts +5 -4
  138. package/dist/interaction/promptRegistry.js +8 -2
  139. package/dist/inventory/inventoryModel.js +9 -6
  140. package/dist/inventory/shapedGrid.d.ts +48 -9
  141. package/dist/inventory/shapedGrid.js +69 -10
  142. package/dist/item/affix.d.ts +6 -0
  143. package/dist/item/affix.js +6 -0
  144. package/dist/item/durability.js +4 -0
  145. package/dist/item/itemInstanceRegistry.d.ts +32 -1
  146. package/dist/item/itemInstanceRegistry.js +111 -7
  147. package/dist/item/itemgen.d.ts +1 -1
  148. package/dist/item/itemgen.js +1 -1
  149. package/dist/item/modularItem.js +7 -1
  150. package/dist/material/appearanceSignals.d.ts +53 -0
  151. package/dist/material/appearanceSignals.js +47 -0
  152. package/dist/material/hairCards.d.ts +47 -0
  153. package/dist/material/hairCards.js +136 -0
  154. package/dist/material/materialAsset.d.ts +162 -0
  155. package/dist/material/materialAsset.js +382 -0
  156. package/dist/meta/changelog.d.ts +1 -1
  157. package/dist/meta/changelog.js +444 -1
  158. package/dist/movement/characterController.d.ts +5 -2
  159. package/dist/movement/characterController.js +50 -23
  160. package/dist/movement/freeFlight.d.ts +2 -2
  161. package/dist/movement/freeFlight.js +3 -3
  162. package/dist/movement/glideModel.d.ts +3 -0
  163. package/dist/movement/glideModel.js +6 -0
  164. package/dist/movement/grappleSwing.d.ts +2 -0
  165. package/dist/movement/grappleSwing.js +5 -1
  166. package/dist/movement/movementModel.d.ts +63 -1
  167. package/dist/movement/movementModel.js +136 -26
  168. package/dist/movement/movementProbe.d.ts +51 -0
  169. package/dist/movement/movementProbe.js +119 -0
  170. package/dist/movement/playerMovement.d.ts +48 -2
  171. package/dist/movement/playerMovement.js +493 -87
  172. package/dist/movement/poseState.d.ts +2 -0
  173. package/dist/movement/poseState.js +29 -1
  174. package/dist/movement/solidObstacles.d.ts +11 -1
  175. package/dist/movement/solidObstacles.js +48 -3
  176. package/dist/movement/terrainGrade.d.ts +13 -0
  177. package/dist/movement/terrainGrade.js +33 -0
  178. package/dist/movement/voxelController.d.ts +2 -2
  179. package/dist/movement/voxelController.js +10 -10
  180. package/dist/nav/navFromEnvironment.d.ts +9 -3
  181. package/dist/nav/navFromEnvironment.js +18 -12
  182. package/dist/nav/navGrid.d.ts +19 -1
  183. package/dist/nav/navGrid.js +26 -10
  184. package/dist/nav/navMesh.d.ts +43 -2
  185. package/dist/nav/navMesh.js +534 -128
  186. package/dist/nav/solidRoute.d.ts +50 -0
  187. package/dist/nav/solidRoute.js +144 -0
  188. package/dist/physics/aircraftDynamics.d.ts +390 -0
  189. package/dist/physics/aircraftDynamics.js +704 -0
  190. package/dist/physics/ballisticSweep.d.ts +10 -3
  191. package/dist/physics/ballisticSweep.js +53 -29
  192. package/dist/physics/boatDynamics.d.ts +116 -0
  193. package/dist/physics/boatDynamics.js +160 -0
  194. package/dist/physics/drivableVehicle.d.ts +22 -8
  195. package/dist/physics/drivableVehicle.js +4 -2
  196. package/dist/physics/flightDynamics.d.ts +21 -4
  197. package/dist/physics/flightDynamics.js +28 -5
  198. package/dist/physics/forceVolume.d.ts +41 -0
  199. package/dist/physics/forceVolume.js +88 -0
  200. package/dist/physics/handlingProbe.d.ts +249 -0
  201. package/dist/physics/handlingProbe.js +606 -0
  202. package/dist/physics/kinematicVehicle.d.ts +40 -5
  203. package/dist/physics/kinematicVehicle.js +17 -2
  204. package/dist/physics/physicsBackend.d.ts +12 -0
  205. package/dist/physics/physicsBackendConformance.js +34 -0
  206. package/dist/physics/physicsWorldBackend.d.ts +1 -0
  207. package/dist/physics/physicsWorldBackend.js +52 -3
  208. package/dist/physics/spatialGrid.d.ts +1 -1
  209. package/dist/physics/spatialGrid.js +8 -9
  210. package/dist/physics/vehicleBackendLink.d.ts +49 -0
  211. package/dist/physics/vehicleBackendLink.js +102 -0
  212. package/dist/physics/vehicleDynamics.d.ts +375 -0
  213. package/dist/physics/vehicleDynamics.js +737 -0
  214. package/dist/physics/vehicleObstacles.d.ts +3 -2
  215. package/dist/physics/vehicleObstacles.js +29 -5
  216. package/dist/physics/worldColliders.d.ts +1 -1
  217. package/dist/physics/worldColliders.js +35 -3
  218. package/dist/puzzle/cellGrid.d.ts +15 -9
  219. package/dist/puzzle/cellGrid.js +13 -9
  220. package/dist/render/lookPreset.d.ts +9 -2
  221. package/dist/render/lookPreset.js +29 -2
  222. package/dist/render/postProcessing.d.ts +30 -0
  223. package/dist/render/postProcessing.js +1 -1
  224. package/dist/runtime/cameraDirector.d.ts +54 -3
  225. package/dist/runtime/cameraDirector.js +187 -0
  226. package/dist/runtime/commandDispatch.d.ts +14 -0
  227. package/dist/runtime/commandDispatch.js +19 -0
  228. package/dist/runtime/commandInput.d.ts +37 -0
  229. package/dist/runtime/commandInput.js +58 -0
  230. package/dist/runtime/commandRunner.d.ts +27 -0
  231. package/dist/runtime/commandRunner.js +31 -3
  232. package/dist/runtime/context/combat.d.ts +12 -4
  233. package/dist/runtime/context/combat.js +179 -11
  234. package/dist/runtime/context/deathLoot.d.ts +11 -8
  235. package/dist/runtime/context/deathLoot.js +17 -16
  236. package/dist/runtime/context/scene.d.ts +2 -0
  237. package/dist/runtime/context/scene.js +13 -21
  238. package/dist/runtime/descriptors/baseline.js +34 -3
  239. package/dist/runtime/descriptors/features.d.ts +2 -1
  240. package/dist/runtime/descriptors/features.js +18 -0
  241. package/dist/runtime/gameContext.js +86 -13
  242. package/dist/runtime/gameContextTypes.d.ts +33 -3
  243. package/dist/runtime/gameRuntime.d.ts +3 -1
  244. package/dist/runtime/gameRuntime.js +4 -0
  245. package/dist/runtime/headlessRunner.d.ts +3 -1
  246. package/dist/runtime/headlessRunner.js +11 -11
  247. package/dist/runtime/hostedGameRunner.d.ts +24 -1
  248. package/dist/runtime/hostedGameRunner.js +156 -10
  249. package/dist/runtime/hostedWorldSession.d.ts +17 -5
  250. package/dist/runtime/hostedWorldSession.js +154 -16
  251. package/dist/runtime/hostedWorldStore.d.ts +2 -2
  252. package/dist/runtime/inputRecorder.d.ts +1 -1
  253. package/dist/runtime/inputRecorder.js +21 -1
  254. package/dist/runtime/inputSnapshot.d.ts +22 -4
  255. package/dist/runtime/inputSnapshot.js +41 -3
  256. package/dist/runtime/localPlayers.d.ts +73 -0
  257. package/dist/runtime/localPlayers.js +119 -0
  258. package/dist/runtime/runtimeSave.d.ts +10 -6
  259. package/dist/runtime/runtimeSave.js +11 -5
  260. package/dist/runtime/simContext.d.ts +8 -3
  261. package/dist/runtime/simContext.js +7 -3
  262. package/dist/runtime/simSnapshot.js +2 -0
  263. package/dist/runtime/transport.d.ts +9 -0
  264. package/dist/runtime/transport.js +7 -1
  265. package/dist/runtime/worldReplication.d.ts +2 -0
  266. package/dist/runtime/worldReplication.js +1 -1
  267. package/dist/scene/assetCatalog.d.ts +2 -0
  268. package/dist/scene/behaviorRuntime.d.ts +38 -7
  269. package/dist/scene/behaviorRuntime.js +293 -30
  270. package/dist/scene/behaviors.d.ts +35 -2
  271. package/dist/scene/behaviors.js +29 -0
  272. package/dist/scene/entityStore.d.ts +14 -3
  273. package/dist/scene/entityStore.js +45 -9
  274. package/dist/scene/mount.d.ts +9 -0
  275. package/dist/scene/mount.js +35 -0
  276. package/dist/scene/possession.d.ts +2 -0
  277. package/dist/scene/possession.js +21 -1
  278. package/dist/scene/sceneKinds.d.ts +9 -0
  279. package/dist/scene/sceneRaycast.d.ts +7 -0
  280. package/dist/scene/sceneRaycast.js +55 -0
  281. package/dist/scene/spatial.d.ts +20 -1
  282. package/dist/scene/spatial.js +94 -2
  283. package/dist/scene/vehicleSeat.d.ts +41 -3
  284. package/dist/scene/vehicleSeat.js +94 -3
  285. package/dist/sensor/concealment.d.ts +6 -0
  286. package/dist/sensor/concealment.js +8 -0
  287. package/dist/sensor/freezeMonitor.d.ts +6 -0
  288. package/dist/sensor/freezeMonitor.js +8 -0
  289. package/dist/sensor/perception.d.ts +11 -1
  290. package/dist/sensor/perception.js +57 -10
  291. package/dist/sensor/recordingBuffer.d.ts +3 -0
  292. package/dist/sensor/recordingBuffer.js +8 -0
  293. package/dist/session/roundState.d.ts +4 -0
  294. package/dist/session/roundState.js +11 -0
  295. package/dist/settings/frameRateLimit.d.ts +16 -0
  296. package/dist/settings/frameRateLimit.js +37 -0
  297. package/dist/settings/graphicsProfile.d.ts +2 -0
  298. package/dist/settings/graphicsProfile.js +12 -0
  299. package/dist/settings/graphicsSettings.d.ts +40 -0
  300. package/dist/settings/graphicsSettings.js +81 -0
  301. package/dist/settings/settingsModel.d.ts +9 -1
  302. package/dist/settings/settingsModel.js +10 -0
  303. package/dist/stats/accumulatorMeter.d.ts +10 -0
  304. package/dist/stats/accumulatorMeter.js +6 -0
  305. package/dist/stats/eventMeter.d.ts +10 -3
  306. package/dist/stats/eventMeter.js +6 -3
  307. package/dist/store/defineStore.d.ts +3 -1
  308. package/dist/store/defineStore.js +3 -1
  309. package/dist/store/observableKeyedStore.d.ts +1 -1
  310. package/dist/store/observableKeyedStore.js +8 -6
  311. package/dist/time/simClock.d.ts +6 -2
  312. package/dist/time/simClock.js +5 -0
  313. package/dist/vfx/feedbackMixer.d.ts +61 -0
  314. package/dist/vfx/feedbackMixer.js +106 -0
  315. package/dist/vfx/particleDirector.d.ts +58 -6
  316. package/dist/vfx/particleDirector.js +113 -22
  317. package/dist/vfx/particles.d.ts +68 -5
  318. package/dist/vfx/particles.js +284 -32
  319. package/dist/visibility/assetStreaming.d.ts +21 -3
  320. package/dist/visibility/assetStreaming.js +108 -24
  321. package/dist/visibility/settings.d.ts +8 -2
  322. package/dist/visibility/settings.js +3 -0
  323. package/dist/visibility/simulationCulling.d.ts +8 -1
  324. package/dist/visibility/simulationCulling.js +7 -0
  325. package/dist/visibility/spatialIndex.d.ts +21 -0
  326. package/dist/visibility/spatialIndex.js +16 -0
  327. package/dist/visibility/visibilitySystem.js +1 -0
  328. package/dist/work/expedition.d.ts +110 -0
  329. package/dist/work/expedition.js +195 -0
  330. package/dist/work/staffedStation.d.ts +29 -0
  331. package/dist/work/staffedStation.js +27 -0
  332. package/dist/world/authoredAnimation.d.ts +38 -0
  333. package/dist/world/authoredAnimation.js +99 -0
  334. package/dist/world/authoredObjects.d.ts +24 -4
  335. package/dist/world/authoredObjects.js +43 -16
  336. package/dist/world/authoredSimulation.d.ts +74 -0
  337. package/dist/world/authoredSimulation.js +302 -0
  338. package/dist/world/authoredSolids.d.ts +46 -0
  339. package/dist/world/authoredSolids.js +75 -0
  340. package/dist/world/authoredWeather.d.ts +120 -0
  341. package/dist/world/authoredWeather.js +314 -0
  342. package/dist/world/buildingGenerator.js +2 -2
  343. package/dist/world/buildingKit.d.ts +11 -4
  344. package/dist/world/buildingKit.js +21 -8
  345. package/dist/world/buildings.d.ts +34 -2
  346. package/dist/world/buildings.js +11 -1
  347. package/dist/world/cityKind.d.ts +7 -0
  348. package/dist/world/cityKind.js +52 -1
  349. package/dist/world/environmentForces.d.ts +21 -0
  350. package/dist/world/environmentForces.js +42 -0
  351. package/dist/world/features.d.ts +32 -5
  352. package/dist/world/features.js +6 -0
  353. package/dist/world/footprintGrid.d.ts +62 -0
  354. package/dist/world/footprintGrid.js +217 -0
  355. package/dist/world/interiors.d.ts +1 -0
  356. package/dist/world/interiors.js +5 -3
  357. package/dist/world/lod.d.ts +6 -0
  358. package/dist/world/lod.js +8 -0
  359. package/dist/world/pathTerrain.d.ts +1 -1
  360. package/dist/world/terraform.d.ts +9 -1
  361. package/dist/world/terraform.js +53 -18
  362. package/dist/world/walls.d.ts +7 -0
  363. package/dist/world/walls.js +7 -0
  364. package/dist/world/weather.d.ts +17 -0
  365. package/dist/world/weather.js +116 -20
  366. package/dist/world/worldSolids.d.ts +95 -0
  367. package/dist/world/worldSolids.js +351 -0
  368. package/dist/world.d.ts +6 -6
  369. package/dist/world.js +5 -5
  370. package/package.json +1 -1
  371. package/skills/ce-handoff/SKILL.md +2 -2
  372. package/skills/game-design/SKILL.md +2 -2
  373. package/skills/jgengine/SKILL.md +28 -6
  374. package/skills/jgengine/api.md +139 -99
  375. package/skills/jgengine/capabilities.md +28 -0
  376. package/skills/jgengine/recipes/minimal-game.md +10 -2
  377. package/skills/jgengine-combat/SKILL.md +13 -2
  378. package/skills/jgengine-combat/api.md +150 -103
  379. package/skills/jgengine-combat/capabilities.md +29 -1
  380. package/skills/jgengine-combat/recipes/portable-runtime-state.md +60 -6
  381. package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +37 -3
  382. package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
  383. package/skills/jgengine-combat/reference.md +24 -2
  384. package/skills/jgengine-gameplay/SKILL.md +63 -0
  385. package/skills/jgengine-gameplay/api.md +583 -463
  386. package/skills/jgengine-gameplay/capabilities.md +92 -0
  387. package/skills/jgengine-gameplay/recipes/device-save-status.md +46 -0
  388. package/skills/jgengine-gameplay/recipes/expeditions.md +108 -0
  389. package/skills/jgengine-gameplay/recipes/narrative-authoring.md +76 -0
  390. package/skills/jgengine-gameplay/recipes/paged-item-storage.md +101 -0
  391. package/skills/jgengine-gameplay/recipes/quest-authoring.md +63 -0
  392. package/skills/jgengine-gameplay/recipes/shaped-inventory.md +50 -0
  393. package/skills/jgengine-gameplay/reference-content-validation.md +74 -0
  394. package/skills/jgengine-gameplay/reference-systems.md +123 -0
  395. package/skills/jgengine-world/SKILL.md +25 -4
  396. package/skills/jgengine-world/api.md +1045 -655
  397. package/skills/jgengine-world/capabilities.md +246 -9
  398. package/skills/jgengine-world/recipes/character-animation.md +62 -0
  399. package/skills/jgengine-world/recipes/controls.md +48 -0
  400. package/skills/jgengine-world/recipes/flight-feel.md +76 -0
  401. package/skills/jgengine-world/recipes/npc-senses.md +55 -0
  402. package/skills/jgengine-world/recipes/vehicle-feel.md +87 -0
  403. package/skills/jgengine-world/reference.md +180 -4
  404. package/skills/jgengine-world/visibility.md +139 -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
- ## ai-decision-graph — Evaluate serializable selector, sequence, condition, action, and utility AI decisions.
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,23 @@ 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"`
35
+ - `validateAnimGraph` (function) · `import { validateAnimGraph } from "@jgengine/core/anim/animGraph"`
26
36
 
27
- ## animation — inverse kinematics orient a bone toward a target with constrained yaw and pitch
37
+ ## animation — find thigh, shin and foot bones on a humanoid rig for foot IK
28
38
 
39
+ - `inferLegChains` (function) · `import { inferLegChains } from "@jgengine/core/anim/footPlacement"`
29
40
  - `lookAt` (function) · `import { lookAt } from "@jgengine/core/anim/ikSolver"`
41
+ - `placeFeet` (function) · `import { placeFeet } from "@jgengine/core/anim/footPlacement"`
42
+ - `planBoneTexture` (function) · `import { planBoneTexture } from "@jgengine/core/anim/boneTexture"`
43
+ - `sampleBoneTexture` (function) · `import { sampleBoneTexture } from "@jgengine/core/anim/boneTexture"`
30
44
  - `solveFabrik` (function) · `import { solveFabrik } from "@jgengine/core/anim/ikSolver"`
31
45
  - `solveTwoBone` (function) · `import { solveTwoBone } from "@jgengine/core/anim/ikSolver"`
32
46
 
@@ -89,10 +103,24 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
89
103
 
90
104
  - `defineAttackMoveOrder` (function) · `import { defineAttackMoveOrder } from "@jgengine/core/world"`
91
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
+
92
116
  ## authored-entities — spawn authored mob/boss markers from the editor document at runtime
93
117
 
94
118
  - `authoredEntitySpawns` (function) · `import { authoredEntitySpawns } from "@jgengine/core/world/authoredEntities"`
95
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
+
96
124
  ## authored-objects — marker kinds resolved as entity spawns, not static props
97
125
 
98
126
  - `ENTITY_MARKER_KINDS` (const) · `import { ENTITY_MARKER_KINDS } from "@jgengine/core/world/authoredObjects"`
@@ -100,6 +128,12 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
100
128
  - `markerCatalogId` (function) · `import { markerCatalogId } from "@jgengine/core/world"`
101
129
  - `placeAuthoredObjects` (function) · `import { placeAuthoredObjects } from "@jgengine/core/world"`
102
130
  - `resolveAuthoredObjects` (function) · `import { resolveAuthoredObjects } from "@jgengine/core/world"`
131
+ - `resolveAuthoredObjectsWithDiagnostics` (function) · `import { resolveAuthoredObjectsWithDiagnostics } from "@jgengine/core/world/authoredObjects"`
132
+
133
+ ## authored-solids — collision for studio-authored world content such as city volumes
134
+
135
+ - `resolveAuthoredSolids` (function) · `import { resolveAuthoredSolids } from "@jgengine/core/world/authoredSolids"`
136
+ - `syncAuthoredSolids` (function) · `import { syncAuthoredSolids } from "@jgengine/core/world/authoredSolids"`
103
137
 
104
138
  ## authored-spawn — read spawn points and markers from the editor document
105
139
 
@@ -116,6 +150,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
116
150
  - `registerBuiltinTriggerActions` (function) · `import { registerBuiltinTriggerActions } from "@jgengine/core/scene/authoredTriggers"`
117
151
  - `registerTriggerAction` (function) · `import { registerTriggerAction } from "@jgengine/core/world"`
118
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
+
161
+ ## axis-shaping — shape raw input per axis — deadzone, response curve, keyboard ramp, self-centring, speed-sensitive scaling
162
+
163
+ - `createAxisShaper` (function) · `import { createAxisShaper } from "@jgengine/core/input/axisShaper"`
164
+
119
165
  ## behavior-actions — register named callbacks for decision graph behavior descriptors
120
166
 
121
167
  - `registerBehaviorActions` (function) · `import { registerBehaviorActions } from "@jgengine/core/world"`
@@ -124,14 +170,26 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
124
170
 
125
171
  - `behaviorControl` (function) · `import { behaviorControl } from "@jgengine/core/world"`
126
172
 
127
- ## 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
128
174
 
129
175
  - `advanceBehaviors` (function) · `import { advanceBehaviors } from "@jgengine/core/world"`
130
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
+
181
+ ## boat-dynamics — force-based powered hull — hull-speed drag hump, planing, keel, rudder that needs flow or outboard thrust
182
+
183
+ - `createBoatDynamics` (function) · `import { createBoatDynamics } from "@jgengine/core/physics/boatDynamics"`
184
+
131
185
  ## body-bind — mirror sim-body snapshots onto scene entities each tick, no per-body setPose
132
186
 
133
187
  - `createBodyBind` (function) · `import { createBodyBind } from "@jgengine/core/world"`
134
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
+
135
193
  ## build-footprint-cells — grid cells a snapped build piece footprint covers (footprintGrid occupancy bridge)
136
194
 
137
195
  - `footprintCells` (function) · `import { footprintCells } from "@jgengine/core/world/buildSockets"`
@@ -167,6 +225,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
167
225
  ## city-district — polygon math for road-derived blocks, parcels, and buildable footprints
168
226
 
169
227
  - `Vec2` (type) · `import { Vec2 } from "@jgengine/core/world/cityGeometry"`
228
+ - `citySolids` (function) · `import { citySolids } from "@jgengine/core/world/cityKind"`
170
229
  - `extractGraphBlocks` (function) · `import { extractGraphBlocks } from "@jgengine/core/world/cityBlocks"`
171
230
  - `rectClearsPolyline` (function) · `import { rectClearsPolyline } from "@jgengine/core/world/cityGeometry"`
172
231
  - `rectsSeparated` (function) · `import { rectsSeparated } from "@jgengine/core/world/cityGeometry"`
@@ -193,6 +252,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
193
252
 
194
253
  - `createTimerSet` (function) · `import { createTimerSet } from "@jgengine/core/world"`
195
254
 
255
+ ## course-metrics — measure a vehicle on driven tests — understeer gradient, skidpad g, slalom speed
256
+
257
+ - `measureCourse` (function) · `import { measureCourse } from "@jgengine/core/physics/handlingProbe"`
258
+
196
259
  ## crouch-prone — stance/pose transitions that change the hitbox
197
260
 
198
261
  - `createPoseState` (function) · `import { createPoseState } from "@jgengine/core/world"`
@@ -241,6 +304,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
241
304
 
242
305
  - `DIFFICULTY_TIERS` (const) · `import { DIFFICULTY_TIERS } from "@jgengine/core/ai/difficulty"`
243
306
 
307
+ ## doppler-pitch — pitch a moving sound up as it approaches and down as it passes, from listener and emitter velocity
308
+
309
+ - `dopplerRate` (function) · `import { dopplerRate } from "@jgengine/core/audio/doppler"`
310
+
311
+ ## drivable-vehicle — drive a car from throttle/steer/handbrake input each tick: step the vehicle sim and pose its entity
312
+
313
+ - `tickDrivableVehicle` (function) · `import { tickDrivableVehicle } from "@jgengine/core/world"`
314
+
244
315
  ## elevation-readout — cursor height and delta-from-reference feedback
245
316
 
246
317
  - `sampleElevation` (function) · `import { sampleElevation } from "@jgengine/core/world"`
@@ -249,17 +320,33 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
249
320
 
250
321
  - `summarizeElevation` (function) · `import { summarizeElevation } from "@jgengine/core/world"`
251
322
 
323
+ ## engine-layers — layered engine audio: rpm-keyed samples crossfaded, with on-load and off-load sets picked by engine load
324
+
325
+ - `createEngineLayers` (function) · `import { createEngineLayers } from "@jgengine/core/audio/engineLayers"`
326
+
252
327
  ## entity-meta — cast-free narrow of SceneEntity.meta via a type guard
253
328
 
254
329
  - `entityMetaOf` (function) · `import { entityMetaOf } from "@jgengine/core/world"`
255
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
+
256
335
  ## fast-travel — network of fast-travel destinations with discovery, distance-sorted queries, nearest lookup, a travel gate, and serializable state
257
336
 
258
337
  - `createFastTravelNetwork` (function) · `import { createFastTravelNetwork } from "@jgengine/core/world"`
259
338
 
260
- ## flight-dynamics — simulate fixed-wing, helicopter, and VTOL aircraft
339
+ ## feedback-mixer — map sim telemetry to camera, audio and haptic parameters through curves, smoothing and threshold events
340
+
341
+ - `createFeedbackMixer` (function) · `import { createFeedbackMixer } from "@jgengine/core/vfx/feedbackMixer"`
261
342
 
262
- - `createAircraftDynamics` (function) · `import { createAircraftDynamics } from "@jgengine/core/world"`
343
+ ## 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
344
+
345
+ - `ActionCodesMap` (type) · `import { ActionCodesMap } from "@jgengine/core/input/actionBindings"`
346
+
347
+ ## flight-metrics — measure an aircraft's feel as numbers — roll rate, sustained turn, stall speed, climb, throttle response, hover drift
348
+
349
+ - `measureFlight` (function) · `import { measureFlight } from "@jgengine/core/physics/handlingProbe"`
263
350
 
264
351
  ## follow-trail — trailing follower/snake formation that chases a leader
265
352
 
@@ -269,6 +356,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
269
356
 
270
357
  - `createFootprintGrid` (function) · `import { createFootprintGrid } from "@jgengine/core/world"`
271
358
 
359
+ ## force-field-validation — reject invalid localized force authoring before simulation
360
+
361
+ - `validateForceField` (function) · `import { validateForceField } from "@jgengine/core/physics/forceVolume"`
362
+
272
363
  ## formation-assignment — stable deterministic member-to-slot matching for a group formation
273
364
 
274
365
  - `assignFormationSlots` (function) · `import { assignFormationSlots } from "@jgengine/core/world"`
@@ -327,10 +418,22 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
327
418
 
328
419
  - `grounded` (function) · `import { grounded } from "@jgengine/core/world"`
329
420
 
421
+ ## handling-metrics — measure a vehicle's feel as numbers — 0–100, braking, lateral g, turn-in, sideslip, spin
422
+
423
+ - `measureHandling` (function) · `import { measureHandling } from "@jgengine/core/physics/handlingProbe"`
424
+
425
+ ## haptics — Continuous gamepad rumble from named channels (engine, road, impact) mixed by priority, plus fading pulses.
426
+
427
+ - `createHapticChannels` (function) · `import { createHapticChannels } from "@jgengine/core/input/haptics"`
428
+
330
429
  ## hold-order — stand-ground order that holds position until preempted or canceled
331
430
 
332
431
  - `defineHoldOrder` (function) · `import { defineHoldOrder } from "@jgengine/core/world"`
333
432
 
433
+ ## input-buffer — buffer early presses, coyote time, hold duration and double tap for responsive controls
434
+
435
+ - `createInputBuffer` (function) · `import { createInputBuffer } from "@jgengine/core/input/inputBuffer"`
436
+
334
437
  ## interaction-prompt — observable nearest-interactable prompt registry over the proximity-prompt resolver — register/resolve/active with priority+range and change-notified snapshot/restore
335
438
 
336
439
  - `createPromptRegistry` (function) · `import { createPromptRegistry } from "@jgengine/core/world"`
@@ -355,6 +458,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
355
458
 
356
459
  - `createRebindSession` (function) · `import { createRebindSession } from "@jgengine/core/input/rebindSession"`
357
460
 
461
+ ## kinematic-vehicle — arcade car/kart driving physics with throttle, brake, steer and retunable handling
462
+
463
+ - `createKinematicVehicle` (function) · `import { createKinematicVehicle } from "@jgengine/core/world"`
464
+
465
+ ## lean-metrics — measure a motorcycle's lean — steady lean, time to lean, countersteer tip-in
466
+
467
+ - `measureLean` (function) · `import { measureLean } from "@jgengine/core/physics/handlingProbe"`
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
+
358
473
  ## limb-health — per-body-part/region health tracked separately
359
474
 
360
475
  - `createMultiRegionHealth` (function) · `import { createMultiRegionHealth } from "@jgengine/core/procedural"`
@@ -363,18 +478,36 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
363
478
 
364
479
  - `resolveLocalAvoidance` (function) · `import { resolveLocalAvoidance } from "@jgengine/core/world"`
365
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
+
366
485
  ## lockpick — a solvable grid depth-puzzle with fog-of-war, gates, and hidden traps
367
486
 
368
487
  - `generateLock` (function) · `import { generateLock } from "@jgengine/core/world"`
369
488
 
370
- ## locomotion-graph — build the default idle/walk/run plus one-shot graph from clip names
489
+ ## locomotion-graph — resolve the animation graph a model config plays
371
490
 
491
+ - `animGraphFromConfig` (function) · `import { animGraphFromConfig } from "@jgengine/core/anim/locomotionGraph"`
372
492
  - `locomotionGraph` (function) · `import { locomotionGraph } from "@jgengine/core/anim/locomotionGraph"`
373
493
 
374
494
  ## map-annotations — player-drawn map annotation layer — freehand strokes, area shapes, and pinned notes, projected into the map's routes/zones props and serializable
375
495
 
376
496
  - `createAnnotationLayer` (function) · `import { createAnnotationLayer } from "@jgengine/core/world"`
377
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
+
378
511
  ## mesh-hitboxes — shots pass through holes in concave models — opted-in catalog assets raycast their actual triangles instead of the fitted box.
379
512
 
380
513
  - `ColliderShape` (type) · `import { ColliderShape } from "@jgengine/core/scene/colliders"`
@@ -400,6 +533,34 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
400
533
 
401
534
  - `defineMoveOrder` (function) · `import { defineMoveOrder } from "@jgengine/core/world"`
402
535
 
536
+ ## movement-metrics — measure a walking character's feel as numbers — time to top speed, stop distance, jump height and apex, air control, turn-around
537
+
538
+ - `measureMovement` (function) · `import { measureMovement } from "@jgengine/core/movement/movementProbe"`
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
+
552
+ ## nav-from-solids — block world solids on a nav grid
553
+
554
+ - `populateNavGridFromSolids` (function) · `import { populateNavGridFromSolids } from "@jgengine/core/nav/navFromEnvironment"`
555
+
556
+ ## navmesh-bake — voxelize scene triangles into an agent-sized polygon nav mesh
557
+
558
+ - `bakeNavMesh` (function) · `import { bakeNavMesh } from "@jgengine/navbake"`
559
+
560
+ ## navmesh-query — route, snap, and raycast on a polygon navmesh with retunable area costs and a bounded search
561
+
562
+ - `createNavMeshQuery` (function) · `import { createNavMeshQuery } from "@jgengine/core/world"`
563
+
403
564
  ## object-slot-inventory — placed objects hold validated per-instance container contents that replicate and save with the placement
404
565
 
405
566
  - `SceneObjectSlots` (interface) · `import { SceneObjectSlots } from "@jgengine/core/world"`
@@ -412,11 +573,19 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
412
573
 
413
574
  - `createOrderRegistry` (function) · `import { createOrderRegistry } from "@jgengine/core/world"`
414
575
 
415
- ## 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
416
581
 
417
582
  - `createParticleDirector` (function) · `import { createParticleDirector } from "@jgengine/core/vfx/particleDirector"`
418
583
 
419
- ## 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
420
589
 
421
590
  - `createParticleSystem` (function) · `import { createParticleSystem } from "@jgengine/core/world"`
422
591
 
@@ -497,7 +666,11 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
497
666
 
498
667
  - `resolveActivePrompt` (function) · `import { resolveActivePrompt } from "@jgengine/core/world"`
499
668
 
500
- ## pursuit — chase-to-reach cooldown-gated attack with optional leash-to-home; owns the attack cooldown
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
+
673
+ ## pursuit — enemy/mob aggro chase-to-reach with a cooldown-gated attack and optional leash-to-home; owns the attack cooldown
501
674
 
502
675
  - `advancePursuit` (function) · `import { advancePursuit } from "@jgengine/core/ai/pursuit"`
503
676
 
@@ -521,6 +694,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
521
694
 
522
695
  - `createResourceNodeField` (function) · `import { createResourceNodeField } from "@jgengine/core/world/resourceNode"`
523
696
 
697
+ ## ride-metrics — measure a sprung vehicle's ride — settle time, dive, roll per g, landing bounce
698
+
699
+ - `measureRide` (function) · `import { measureRide } from "@jgengine/core/physics/handlingProbe"`
700
+
701
+ ## rigid-aircraft — force-and-torque aircraft — lifting surfaces with stall and drag polar, control surfaces, inertia, loops and rolls
702
+
703
+ - `createRigidAircraft` (function) · `import { createRigidAircraft } from "@jgengine/core/physics/aircraftDynamics"`
704
+
524
705
  ## rts-control-groups — map Ctrl+digit / digit / double-tap presses onto selection-bookmark bind / recall / focus intents
525
706
 
526
707
  - `controlGroupKey` (function) · `import { controlGroupKey } from "@jgengine/core/world"`
@@ -560,8 +741,17 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
560
741
 
561
742
  ## solid-obstacles — the one blocking-geometry query the player resolver and NPC movers share
562
743
 
744
+ - `obstacleFromSolid` (function) · `import { obstacleFromSolid } from "@jgengine/core/movement/solidObstacles"`
563
745
  - `solidObstaclesNear` (function) · `import { solidObstaclesNear } from "@jgengine/core/movement/solidObstacles"`
564
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
+
565
755
  ## spatial-region-box — sample uniformly within an axis-aligned 3D box
566
756
 
567
757
  - `boxRegion` (function) · `import { boxRegion } from "@jgengine/core/world"`
@@ -602,6 +792,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
602
792
 
603
793
  - `sampleStratified` (function) · `import { sampleStratified } from "@jgengine/core/world"`
604
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
+
605
799
  ## stop-order — halt-and-complete order that brings a unit to an immediate standstill
606
800
 
607
801
  - `defineStopOrder` (function) · `import { defineStopOrder } from "@jgengine/core/world"`
@@ -622,6 +816,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
622
816
 
623
817
  - `surfaceRing` (function) · `import { surfaceRing } from "@jgengine/core/world"`
624
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
+
625
823
  ## tactical-cover-points — derive cover candidates from navigation-mesh boundaries
626
824
 
627
825
  - `coverPoints` (function) · `import { coverPoints } from "@jgengine/core/ai/tacticalQueries"`
@@ -683,6 +881,22 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
683
881
  - `setTouchControlsMode` (function) · `import { setTouchControlsMode } from "@jgengine/core/input/touchControlsMode"`
684
882
  - `touchButtonShape` (function) · `import { touchButtonShape } from "@jgengine/core/input/touchScheme"`
685
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
+
888
+ ## vehicle-backend-link — collide a vehicle sim with a physics backend — shove props, stop at walls, slide along them
889
+
890
+ - `createVehicleBackendLink` (function) · `import { createVehicleBackendLink } from "@jgengine/core/physics/vehicleBackendLink"`
891
+
892
+ ## vehicle-dynamics — force-based car handling — tires, load transfer, drivetrain, assists — tuned with physical numbers
893
+
894
+ - `createVehicleDynamics` (function) · `import { createVehicleDynamics } from "@jgengine/core/physics/vehicleDynamics"`
895
+
896
+ ## vehicle-seats — enter, exit and swap seats in a car or mount (GTA-style boarding) with driver and passenger slots
897
+
898
+ - `createVehicleSeats` (function) · `import { createVehicleSeats } from "@jgengine/core/world"`
899
+
686
900
  ## visibility-field — per-viewer-group observed/remembered/hidden fog-of-war knowledge with terrain memory and authoritative entity disclosure
687
901
 
688
902
  - `createVisibilityField` (function) · `import { createVisibilityField } from "@jgengine/core/world"`
@@ -707,6 +921,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
707
921
 
708
922
  - `createWaypointStore` (function) · `import { createWaypointStore } from "@jgengine/core/world"`
709
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
+
710
932
  ## world-environment — compose editor/preset-derived outdoor dressing — terrain, weather, water, structures — into a legacy environment feature
711
933
 
712
934
  - `environment` (function) · `import { environment } from "@jgengine/core/world"`
@@ -729,3 +951,18 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
729
951
  ## world-seed — derive a deterministic generator/scatter seed from world id + save/run seed
730
952
 
731
953
  - `seedForPlace` (function) · `import { seedForPlace } from "@jgengine/core/world"`
954
+
955
+ ## world-solid-bounds — world AABB and nav footprint of a world solid
956
+
957
+ - `worldSolidBounds` (function) · `import { worldSolidBounds } from "@jgengine/core/world/worldSolids"`
958
+ - `worldSolidFootprint` (function) · `import { worldSolidFootprint } from "@jgengine/core/world/worldSolids"`
959
+
960
+ ## world-solids — collision for generated and authored world geometry (buildings, city lots, walls)
961
+
962
+ - `createWorldSolids` (function) · `import { createWorldSolids } from "@jgengine/core/world/worldSolids"`
963
+
964
+ ## world-solids-from-data — derive collision boxes from generated buildings and wall runs
965
+
966
+ - `buildingSolids` (function) · `import { buildingSolids } from "@jgengine/core/world/worldSolids"`
967
+ - `structureSolids` (function) · `import { structureSolids } from "@jgengine/core/world/worldSolids"`
968
+ - `wallSolids` (function) · `import { wallSolids } from "@jgengine/core/world/worldSolids"`
@@ -0,0 +1,62 @@
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. 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.
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.
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.
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.
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.
28
+ - **Triggers.** `ctx.game.playEntityAnimation(id, name)` arms a trigger; `hit` and `death` arm from their combat events.
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.
37
+ - **Foot IK.** `ModelConfig.ik` runs after the mixer each frame.
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.
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.
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.
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).
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`.
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.
47
+ - 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.
48
+ - Crowd members have no graph, IK or attachments. Promote the few near the camera or near the player to entity models.
49
+ - `bakeSkinnedCrowd` and `patchCrowdMaterial` are exported for a custom renderer.
50
+
51
+ ## Symptom → knob
52
+
53
+ | Symptom | Knob |
54
+ | --- | --- |
55
+ | Feet slide at walk or run speed | `walkSpeed`/`runSpeed` to the clip's authored stride speed, or blend1D `at` points |
56
+ | Pop between states | transition `duration`; `exitTime` on one-shots returning to locomotion |
57
+ | Feet float or sink on slopes and steps | `ik: "auto"` |
58
+ | Knees buckle on steep ground | lower `ik.maxAdjust` |
59
+ | Feet tilt wrongly on rubble or props | lower `ik.alignToGround` |
60
+ | Attack damage lands before the swing | `graph.events` on the hit frame instead of a timer |
61
+ | Lunge distance doesn't match the feet | extract `rootMotion: true` travel and resolve it through the collision owner |
62
+ | Hundreds of animated characters cost thousands of draw calls | `SkinnedInstances` for the background crowd |
@@ -0,0 +1,48 @@
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
+ - **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.
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.
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.
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.
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`.
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.
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.
19
+ - **Rebinding.** `createRebindSession` drives a conflict-aware "press a key" flow; overrides persist per game and apply under the context stack.
20
+
21
+ ## Wiring a mode swap
22
+
23
+ ```ts
24
+ const DRIVING: ActionContext = {
25
+ id: "driving",
26
+ codes: { exit: ["KeyF", "pad:3"] },
27
+ passthrough: true,
28
+ axes: { steer: { positive: ["steerRight"], negative: ["steerLeft"] } },
29
+ shaping: { steer: { digital: { riseRate: 3, returnRate: 6 }, analog: { deadzone: 0.08, curve: 1.5 } } },
30
+ };
31
+
32
+ // enter: actionContextStack(ctx).push(DRIVING); leave: actionContextStack(ctx).pop("driving")
33
+ if (state.contextVersion !== contexts.version()) {
34
+ state.contextVersion = contexts.version();
35
+ const layer = contexts.activeAxes();
36
+ state.bindings = { steer: layer.bindings.steer ?? { positive: [] } };
37
+ state.shaper.retune({ axes: { steer: layer.shaping.steer ?? {} } });
38
+ }
39
+ const raw = ctx.input.axis(state.bindings);
40
+ const axis = state.shaper.shape(dt, raw, { analog: analogAxes(state.bindings, ctx.input.analog()) });
41
+ ```
42
+
43
+ `bun run drive handling` runs this: F pops the driving context and a held W stops reaching the engine.
44
+
45
+ ## Pitfalls
46
+
47
+ - 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.
48
+ - `shaping` has no `scale` callback because contexts are serializable; apply speed-sensitive scaling on the shaper the game owns.