@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
@@ -28,6 +28,8 @@ export interface PoseState {
28
28
  /** Restore every entity's pose and aim from a {@link snapshotAll} payload. */
29
29
  hydrateAll(state: PoseSnapshot): void;
30
30
  }
31
+ /** Restore one owned pose on synchronous failure; external implementations retain their semantic setter policy. @internal */
32
+ export declare function withPoseRollback<T>(state: PoseState, instanceId: string, operation: () => T): T;
31
33
  /**
32
34
  * Stance/pose transitions — stand, crouch, prone — that change the hitbox and movement.
33
35
  *
@@ -23,6 +23,32 @@ export const POSE_HITBOX = {
23
23
  speedMultiplier: MOVEMENT_TUNING.runSpeedMultiplier,
24
24
  },
25
25
  };
26
+ const ownedPoses = new WeakMap();
27
+ /** Restore one owned pose on synchronous failure; external implementations retain their semantic setter policy. @internal */
28
+ export function withPoseRollback(state, instanceId, operation) {
29
+ const poses = ownedPoses.get(state.getPose);
30
+ const previous = poses === undefined ? state.getPose(instanceId) : poses.get(instanceId);
31
+ try {
32
+ return operation();
33
+ }
34
+ catch (error) {
35
+ if (poses !== undefined) {
36
+ if (previous === undefined)
37
+ poses.delete(instanceId);
38
+ else
39
+ poses.set(instanceId, previous);
40
+ }
41
+ else {
42
+ try {
43
+ state.setPose(instanceId, previous);
44
+ }
45
+ finally {
46
+ throw error;
47
+ }
48
+ }
49
+ throw error;
50
+ }
51
+ }
26
52
  /**
27
53
  * Stance/pose transitions — stand, crouch, prone — that change the hitbox and movement.
28
54
  *
@@ -37,7 +63,7 @@ export function createPoseState(resolveAllowed) {
37
63
  function allowedAims(instanceId) {
38
64
  return resolveAllowed(instanceId)?.aim ?? ["hip"];
39
65
  }
40
- return {
66
+ const state = {
41
67
  getPose(instanceId) {
42
68
  return poses.get(instanceId) ?? "standing";
43
69
  },
@@ -74,4 +100,6 @@ export function createPoseState(resolveAllowed) {
74
100
  aims.set(id, aim);
75
101
  },
76
102
  };
103
+ ownedPoses.set(state.getPose, poses);
104
+ return state;
77
105
  }
@@ -1,6 +1,7 @@
1
1
  import type { GameContext } from "../runtime/gameContext.js";
2
2
  import { resolveColliders, type ResolvedCollider } from "../scene/colliders.js";
3
3
  import type { EntityPosition } from "../scene/entityStore.js";
4
+ import type { WorldSolid, WorldSolids } from "../world/worldSolids.js";
4
5
  import { type CollisionObstacle } from "./movementModel.js";
5
6
  /** Largest blocking-collider extent in the scene, split so tower height cannot inflate the XZ query. */
6
7
  export interface ObstacleReach {
@@ -15,6 +16,8 @@ export interface ObstacleReach {
15
16
  * pure rules package with no `GameContext` to import can satisfy it from its own object list.
16
17
  */
17
18
  export interface SolidObstacleSource {
19
+ /** Optional indexed world geometry that is not represented by scene objects. */
20
+ solids?: Pick<WorldSolids, "count" | "inBox">;
18
21
  list(): readonly {
19
22
  instanceId: string;
20
23
  position: EntityPosition;
@@ -49,9 +52,16 @@ export declare function sourceObstacleReach(source: SolidObstacleSource, cache:
49
52
  * never under-covers.
50
53
  * @internal */
51
54
  export declare function obstacleFromCollider(collider: ResolvedCollider, position: EntityPosition, rotationY: number): CollisionObstacle;
55
+ /**
56
+ * A {@link WorldSolid} as a {@link CollisionObstacle}. Axis-aligned solids are one box; a yawed solid is
57
+ * cut into strips along its long axis, each fitted with an AABB, so a turned building does not block
58
+ * the street beside it the way a single enclosing box would.
59
+ * @capability solid-obstacles the one blocking-geometry query the player resolver and NPC movers share
60
+ */
61
+ export declare function obstacleFromSolid(solid: WorldSolid): CollisionObstacle;
52
62
  /**
53
63
  * Every blocking physical obstacle overlapping the box `position` ± `reachX`/`reachZ`, read from the
54
- * scene's resolved colliders.
64
+ * scene's resolved colliders and `ctx.world.solids`.
55
65
  *
56
66
  * This is the engine's single answer to "what is solid here". The player resolver, a walking NPC, and
57
67
  * anything else that has to stop at a wall read it rather than deriving their own set — three
@@ -15,7 +15,7 @@ const OBSTACLE_MAX_HALF_EXTENT = 4;
15
15
  const OBSTACLE_HORIZONTAL_CAP = 22;
16
16
  /**
17
17
  * Vertical broadphase floor/cap. A walker only needs nearby ground props / step-ups — never full tower
18
- * height. Using building height for Y was the Vice Isle pose killer: a 50 m tower turned `inBox` into
18
+ * height. Using building height for Y was the Harbor Heat pose killer: a 50 m tower turned `inBox` into
19
19
  * a 100^3 empty cell walk every frame.
20
20
  */
21
21
  const OBSTACLE_VERTICAL_FLOOR = 3;
@@ -26,6 +26,10 @@ const OBSTACLE_VERTICAL_CAP = 8;
26
26
  * multiply narrowphase cost without changing the blocked footprint.
27
27
  */
28
28
  const MOVEMENT_MESH_BOX_BUDGET = 12;
29
+ /** Most AABB strips an oriented world solid is sliced into, bounding the corner overhang a yawed box leaves. */
30
+ const SOLID_MAX_STRIPS = 8;
31
+ /** Yaw within this of a quarter turn is treated as axis-aligned (radians). */
32
+ const AXIS_ALIGNED_EPSILON = 1e-4;
29
33
  /** Feet-to-head span a walker is tested over — matches the player's, so both agree on what a wall is. */
30
34
  const WALKER_HEIGHT = 1.8;
31
35
  /** A fresh {@link ObstacleReachCache} for a caller that owns its own source. */
@@ -141,9 +145,40 @@ export function obstacleFromCollider(collider, position, rotationY) {
141
145
  halfExtents: [Math.abs(hx * cos) + Math.abs(hz * sin), hy, Math.abs(hx * sin) + Math.abs(hz * cos)],
142
146
  };
143
147
  }
148
+ /**
149
+ * A {@link WorldSolid} as a {@link CollisionObstacle}. Axis-aligned solids are one box; a yawed solid is
150
+ * cut into strips along its long axis, each fitted with an AABB, so a turned building does not block
151
+ * the street beside it the way a single enclosing box would.
152
+ * @capability solid-obstacles the one blocking-geometry query the player resolver and NPC movers share
153
+ */
154
+ export function obstacleFromSolid(solid) {
155
+ const [hx, hy, hz] = solid.halfExtents;
156
+ const yaw = solid.rotationY ?? 0;
157
+ const quarter = yaw / (Math.PI / 2);
158
+ const nearest = Math.round(quarter);
159
+ if (Math.abs(quarter - nearest) < AXIS_ALIGNED_EPSILON) {
160
+ const swapped = Math.abs(nearest) % 2 === 1;
161
+ return { position: solid.center, halfExtents: swapped ? [hz, hy, hx] : [hx, hy, hz] };
162
+ }
163
+ const alongX = hx >= hz;
164
+ const long = alongX ? hx : hz;
165
+ const short = Math.max(alongX ? hz : hx, 1e-6);
166
+ const strips = Math.min(SOLID_MAX_STRIPS, Math.max(1, Math.ceil(long / short)));
167
+ const cos = Math.cos(yaw);
168
+ const sin = Math.sin(yaw);
169
+ const boxes = [];
170
+ for (let i = 0; i < strips; i += 1) {
171
+ const from = -long + (2 * long * i) / strips;
172
+ const to = from + (2 * long) / strips;
173
+ const boxMin = alongX ? [from, -hy, -hz] : [-hx, -hy, from];
174
+ const boxMax = alongX ? [to, hy, hz] : [hx, hy, to];
175
+ boxes.push(yawExpandLocalBox(boxMin, boxMax, cos, sin));
176
+ }
177
+ return { position: solid.center, boxes };
178
+ }
144
179
  /**
145
180
  * Every blocking physical obstacle overlapping the box `position` ± `reachX`/`reachZ`, read from the
146
- * scene's resolved colliders.
181
+ * scene's resolved colliders and `ctx.world.solids`.
147
182
  *
148
183
  * This is the engine's single answer to "what is solid here". The player resolver, a walking NPC, and
149
184
  * anything else that has to stop at a wall read it rather than deriving their own set — three
@@ -153,7 +188,16 @@ export function obstacleFromCollider(collider, position, rotationY) {
153
188
  * @capability solid-obstacles the one blocking-geometry query the player resolver and NPC movers share
154
189
  */
155
190
  export function solidObstaclesNear(ctx, position, reachX, reachZ, height = WALKER_HEIGHT) {
156
- return sourceObstaclesNear(ctx.scene.object, solidObstacleReach(ctx), position, reachX, reachZ, height);
191
+ const obstacles = sourceObstaclesNear(ctx.scene.object, solidObstacleReach(ctx), position, reachX, reachZ, height);
192
+ appendWorldSolidObstacles(obstacles, ctx.world.solids, position, reachX, reachZ, height);
193
+ return obstacles;
194
+ }
195
+ function appendWorldSolidObstacles(obstacles, solids, position, reachX, reachZ, height) {
196
+ if (solids === undefined || solids.count() === 0)
197
+ return;
198
+ for (const solid of solids.inBox([position[0] - reachX, position[1] - height, position[2] - reachZ], [position[0] + reachX, position[1] + 2 * height, position[2] + reachZ])) {
199
+ obstacles.push(obstacleFromSolid(solid));
200
+ }
157
201
  }
158
202
  /** {@link solidObstaclesNear} against a bare source and an already-resolved reach. */
159
203
  export function sourceObstaclesNear(source, reach, position, reachX, reachZ, height = WALKER_HEIGHT) {
@@ -181,6 +225,7 @@ export function sourceObstaclesNear(source, reach, position, reachX, reachZ, hei
181
225
  obstacles.push(obstacleFromCollider(collider, object.position, object.rotationY));
182
226
  }
183
227
  }
228
+ appendWorldSolidObstacles(obstacles, source.solids, position, reachX, reachZ, height);
184
229
  return obstacles;
185
230
  }
186
231
  /**
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Constrain an uphill heightfield step by its sampled rise/run grade. Try the full step, then
3
+ * X-only, then Z-only, preserving downhill travel and sliding along a traversable axis.
4
+ * The sampler can represent a game's terrain policy independently of the ground used for feet.
5
+ * This endpoint test also applies while jumping; it is not continuous terrain collision.
6
+ * @capability uphill-grade-step constrain sampled heightfield ascent with axis sliding and a configurable terrain-policy sampler
7
+ */
8
+ export declare function resolveTerrainGradeStep(sampler: {
9
+ sampleHeight(x: number, z: number): number;
10
+ } | ((x: number, z: number) => number), position: readonly [number, number, number], stepX: number, stepZ: number, maxClimbGrade: number): {
11
+ stepX: number;
12
+ stepZ: number;
13
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Constrain an uphill heightfield step by its sampled rise/run grade. Try the full step, then
3
+ * X-only, then Z-only, preserving downhill travel and sliding along a traversable axis.
4
+ * The sampler can represent a game's terrain policy independently of the ground used for feet.
5
+ * This endpoint test also applies while jumping; it is not continuous terrain collision.
6
+ * @capability uphill-grade-step constrain sampled heightfield ascent with axis sliding and a configurable terrain-policy sampler
7
+ */
8
+ export function resolveTerrainGradeStep(sampler, position, stepX, stepZ, maxClimbGrade) {
9
+ if (!Number.isFinite(maxClimbGrade) || maxClimbGrade < 0) {
10
+ throw new RangeError("maxClimbGrade must be finite and nonnegative.");
11
+ }
12
+ const startX = position[0];
13
+ const startZ = position[2];
14
+ const sampleHeight = (x, z) => {
15
+ const height = typeof sampler === "function" ? sampler(x, z) : sampler.sampleHeight(x, z);
16
+ if (!Number.isFinite(height)) {
17
+ throw new RangeError(`climbGradeHeight/sampleHeight must return a finite height at (${x}, ${z}); received ${height}.`);
18
+ }
19
+ return height;
20
+ };
21
+ const height = sampleHeight(startX, startZ);
22
+ const allowed = (dx, dz) => {
23
+ const distance = Math.hypot(dx, dz);
24
+ return distance < 0.0001 || (sampleHeight(startX + dx, startZ + dz) - height) / distance <= maxClimbGrade;
25
+ };
26
+ if (allowed(stepX, stepZ))
27
+ return { stepX, stepZ };
28
+ if (allowed(stepX, 0))
29
+ return { stepX, stepZ: 0 };
30
+ if (allowed(0, stepZ))
31
+ return { stepX: 0, stepZ };
32
+ return { stepX: 0, stepZ: 0 };
33
+ }
@@ -19,7 +19,7 @@
19
19
  * block at cell y therefore has its top face at world y+1, so a player standing
20
20
  * on the surface layer (cell y=-1) rests with their feet at y=0.
21
21
  */
22
- import { type MovementIntent, type MovementTuningOverrides } from "./movementModel.js";
22
+ import { type MovementIntent, type MovementTuningOverrides, type MotionFrameOptions } from "./movementModel.js";
23
23
  export type SolidQuery = (x: number, y: number, z: number) => boolean;
24
24
  /** Footprint + height of the player box, in world units. */
25
25
  export interface VoxelPlayerDims {
@@ -58,4 +58,4 @@ export type GroundHeightQuery = (x: number, z: number) => number;
58
58
  * supported (resets jump state like landing on a solid cell). Where terrain and
59
59
  * a solid cell's top disagree, the higher of the two wins.
60
60
  */
61
- export declare function advanceVoxelPlayer(body: VoxelPlayerBody, intent: MovementIntent, forwardX: number, forwardZ: number, baseSpeed: number, rawDeltaSeconds: number, isSolid: SolidQuery, dims?: VoxelPlayerDims, tuning?: MovementTuningOverrides, groundHeight?: GroundHeightQuery): void;
61
+ export declare function advanceVoxelPlayer(body: VoxelPlayerBody, intent: MovementIntent, forwardX: number, forwardZ: number, baseSpeed: number, rawDeltaSeconds: number, isSolid: SolidQuery, dims?: VoxelPlayerDims, tuning?: MovementTuningOverrides, groundHeight?: GroundHeightQuery, options?: Pick<MotionFrameOptions, "authoritativeStep">): void;
@@ -19,7 +19,7 @@
19
19
  * block at cell y therefore has its top face at world y+1, so a player standing
20
20
  * on the surface layer (cell y=-1) rests with their feet at y=0.
21
21
  */
22
- import { MOVEMENT_TUNING } from "./movementModel.js";
22
+ import { MOVEMENT_TUNING, motionStepSeconds } from "./movementModel.js";
23
23
  export const DEFAULT_VOXEL_DIMS = {
24
24
  halfWidth: 0.3,
25
25
  height: 1.8,
@@ -97,7 +97,7 @@ function supportTopWithin(x, z, fromY, toY, isSolid, dims) {
97
97
  }
98
98
  return best;
99
99
  }
100
- function targetHorizontalVelocity(intent, forwardX, forwardZ, baseSpeed) {
100
+ function targetHorizontalVelocity(intent, forwardX, forwardZ, baseSpeed, tuning) {
101
101
  if (!intent.moving)
102
102
  return { x: 0, z: 0 };
103
103
  let fx = forwardX;
@@ -120,9 +120,9 @@ function targetHorizontalVelocity(intent, forwardX, forwardZ, baseSpeed) {
120
120
  if (magSq < 1e-6)
121
121
  return { x: 0, z: 0 };
122
122
  const speedMultiplier = intent.crouching
123
- ? MOVEMENT_TUNING.crouchSpeedMultiplier
123
+ ? (tuning?.crouchSpeedMultiplier ?? MOVEMENT_TUNING.crouchSpeedMultiplier)
124
124
  : intent.running
125
- ? MOVEMENT_TUNING.runSpeedMultiplier
125
+ ? (tuning?.runSpeedMultiplier ?? MOVEMENT_TUNING.runSpeedMultiplier)
126
126
  : 1;
127
127
  const targetSpeed = baseSpeed * MOVEMENT_TUNING.walkSpeedMultiplier * speedMultiplier;
128
128
  const scale = targetSpeed / Math.sqrt(magSq);
@@ -172,19 +172,19 @@ function moveAxis(body, axis, delta, isSolid, dims) {
172
172
  * supported (resets jump state like landing on a solid cell). Where terrain and
173
173
  * a solid cell's top disagree, the higher of the two wins.
174
174
  */
175
- export function advanceVoxelPlayer(body, intent, forwardX, forwardZ, baseSpeed, rawDeltaSeconds, isSolid, dims = DEFAULT_VOXEL_DIMS, tuning, groundHeight) {
176
- const dt = Math.min(rawDeltaSeconds, MOVEMENT_TUNING.maxFrameSeconds);
175
+ export function advanceVoxelPlayer(body, intent, forwardX, forwardZ, baseSpeed, rawDeltaSeconds, isSolid, dims = DEFAULT_VOXEL_DIMS, tuning, groundHeight, options) {
176
+ const dt = motionStepSeconds(rawDeltaSeconds, options?.authoritativeStep);
177
177
  const gravityAcceleration = tuning?.gravityAcceleration ?? MOVEMENT_TUNING.gravityAcceleration;
178
178
  const jumpVelocity = tuning?.jumpVelocity ?? MOVEMENT_TUNING.jumpVelocity;
179
- const target = targetHorizontalVelocity(intent, forwardX, forwardZ, baseSpeed);
179
+ const target = targetHorizontalVelocity(intent, forwardX, forwardZ, baseSpeed, tuning);
180
180
  const acceleration = body.grounded
181
- ? MOVEMENT_TUNING.groundAcceleration
182
- : MOVEMENT_TUNING.airAcceleration;
181
+ ? (tuning?.groundAcceleration ?? MOVEMENT_TUNING.groundAcceleration)
182
+ : (tuning?.airAcceleration ?? MOVEMENT_TUNING.airAcceleration);
183
183
  const accelerationBlend = 1 - Math.exp(-acceleration * dt);
184
184
  body.velocityX += (target.x - body.velocityX) * accelerationBlend;
185
185
  body.velocityZ += (target.z - body.velocityZ) * accelerationBlend;
186
186
  if (!intent.moving && body.grounded) {
187
- const friction = Math.exp(-MOVEMENT_TUNING.groundFriction * dt);
187
+ const friction = Math.exp(-(tuning?.groundFriction ?? MOVEMENT_TUNING.groundFriction) * dt);
188
188
  body.velocityX *= friction;
189
189
  body.velocityZ *= friction;
190
190
  }
@@ -1,11 +1,17 @@
1
1
  import type { EnvironmentWorldFeature } from "../world/features.js";
2
2
  import type { Aabb } from "../world/geometry.js";
3
+ import { type WorldSolids } from "../world/worldSolids.js";
3
4
  export interface NavObstacleGrid {
4
5
  blockAabb(aabb: Aabb): void;
5
6
  }
6
7
  /**
7
- * Expands every structure descriptor on an environment world feature into its
8
- * generated buildings and blocks their footprints on `grid`. Returns the
9
- * number of buildings blocked.
8
+ * Blocks the footprint of every solid building an environment feature's `structures` generate — the
9
+ * same set `ctx.world.solids` holds for it. Returns the number of buildings blocked.
10
10
  */
11
11
  export declare function populateNavGridFromEnvironment(grid: NavObstacleGrid, world: EnvironmentWorldFeature): number;
12
+ /**
13
+ * Blocks the XZ footprint of every solid in `solids` (usually `ctx.world.solids`) on `grid`, so NPC
14
+ * paths avoid exactly what movement and physics collide with. Returns the number of solids blocked.
15
+ * @capability nav-from-solids block world solids on a nav grid
16
+ */
17
+ export declare function populateNavGridFromSolids(grid: NavObstacleGrid, solids: Pick<WorldSolids, "all">): number;
@@ -1,16 +1,22 @@
1
- import { resolveStructureBuildings } from "../world/environmentSummary.js";
1
+ import { structureSolids, worldSolidFootprint } from "../world/worldSolids.js";
2
2
  /**
3
- * Expands every structure descriptor on an environment world feature into its
4
- * generated buildings and blocks their footprints on `grid`. Returns the
5
- * number of buildings blocked.
3
+ * Blocks the footprint of every solid building an environment feature's `structures` generate — the
4
+ * same set `ctx.world.solids` holds for it. Returns the number of buildings blocked.
6
5
  */
7
6
  export function populateNavGridFromEnvironment(grid, world) {
8
- let blocked = 0;
9
- for (const descriptor of world.structures ?? []) {
10
- for (const building of resolveStructureBuildings(descriptor)) {
11
- grid.blockAabb(building.bounds);
12
- blocked += 1;
13
- }
14
- }
15
- return blocked;
7
+ const solids = structureSolids(world);
8
+ for (const solid of solids)
9
+ grid.blockAabb(worldSolidFootprint(solid));
10
+ return solids.length;
11
+ }
12
+ /**
13
+ * Blocks the XZ footprint of every solid in `solids` (usually `ctx.world.solids`) on `grid`, so NPC
14
+ * paths avoid exactly what movement and physics collide with. Returns the number of solids blocked.
15
+ * @capability nav-from-solids block world solids on a nav grid
16
+ */
17
+ export function populateNavGridFromSolids(grid, solids) {
18
+ const all = solids.all();
19
+ for (const solid of all)
20
+ grid.blockAabb(worldSolidFootprint(solid));
21
+ return all.length;
16
22
  }
@@ -17,6 +17,10 @@ export interface FindPathOptions {
17
17
  clearance?: number;
18
18
  /** Multiplier (>=1) applied to the base cost of stepping from one cell center to a neighbour's. Use to penalize slopes, terrain, etc. */
19
19
  stepCost?: (from: NavPoint, to: NavPoint) => number;
20
+ /** Additional edge/shortcut policy. Called after grid occupancy checks. */
21
+ canTraverse?: (from: NavPoint, to: NavPoint) => boolean;
22
+ /** Maximum expanded cells. Omit for the legacy whole-grid search. */
23
+ maxNodes?: number;
20
24
  }
21
25
  export interface NavGrid {
22
26
  readonly cols: number;
@@ -44,6 +48,20 @@ export declare function createNavGrid(config: NavGridConfig): NavGrid;
44
48
  * the nearest walkable cell so a click on an obstacle still routes to its edge.
45
49
  */
46
50
  export declare function findPath(grid: NavGrid, from: NavPoint, to: NavPoint, options?: FindPathOptions): NavPoint[] | null;
51
+ /** Bounded grid search distinguishes exhausted work from a proven unreachable goal. */
52
+ export type FindPathResult = {
53
+ status: "path";
54
+ points: NavPoint[];
55
+ visited: number;
56
+ } | {
57
+ status: "no-path" | "budget";
58
+ visited: number;
59
+ };
60
+ /**
61
+ * Same endpoint snapping as {@link findPath}, with explicit search-budget exhaustion.
62
+ * @capability bounded-grid-route distinguish no path from exhausted grid search work
63
+ */
64
+ export declare function findPathResult(grid: NavGrid, from: NavPoint, to: NavPoint, options?: FindPathOptions): FindPathResult;
47
65
  /**
48
66
  * `FindPathOptions.stepCost` factory that penalizes steep terrain: cost is
49
67
  * `1 + weight * |Δheight| / horizontalDistance`, so with the default weight a
@@ -53,4 +71,4 @@ export declare function slopeStepCost(field: {
53
71
  sampleHeight(x: number, z: number): number;
54
72
  }, weight?: number): (from: NavPoint, to: NavPoint) => number;
55
73
  /** Remove waypoints the mover can skip because it has clear line-of-sight past them. */
56
- export declare function smoothPath(grid: NavGrid, points: readonly NavPoint[]): NavPoint[];
74
+ export declare function smoothPath(grid: NavGrid, points: readonly NavPoint[], canTraverse?: (from: NavPoint, to: NavPoint) => boolean): NavPoint[];
@@ -174,14 +174,27 @@ function nearestPassable(grid, point, passable) {
174
174
  * the nearest walkable cell so a click on an obstacle still routes to its edge.
175
175
  */
176
176
  export function findPath(grid, from, to, options = {}) {
177
+ const result = findPathResult(grid, from, to, options);
178
+ return result.status === "path" ? result.points : null;
179
+ }
180
+ /**
181
+ * Same endpoint snapping as {@link findPath}, with explicit search-budget exhaustion.
182
+ * @capability bounded-grid-route distinguish no path from exhausted grid search work
183
+ */
184
+ export function findPathResult(grid, from, to, options = {}) {
185
+ const maxNodes = options.maxNodes ?? Number.POSITIVE_INFINITY;
186
+ if (!(maxNodes >= 0))
187
+ throw new Error("navGrid maxNodes must be nonnegative.");
177
188
  const passable = withClearance(grid, options.clearance ?? 0);
178
189
  const stepCost = options.stepCost;
179
190
  const startCell = grid.cellAt(from);
180
191
  const goalCell = grid.cellAt(to);
181
192
  const start = passable(startCell.col, startCell.row) ? startCell : nearestPassable(grid, from, passable);
182
- const goal = passable(goalCell.col, goalCell.row) ? goalCell : nearestPassable(grid, to, passable);
193
+ const exactGoal = passable(goalCell.col, goalCell.row);
194
+ const goal = exactGoal ? goalCell : nearestPassable(grid, to, passable);
183
195
  if (start === null || goal === null)
184
- return null;
196
+ return { status: "no-path", visited: 0 };
197
+ const pointAt = (col, row) => exactGoal && col === goal.col && row === goal.row ? to : grid.center(col, row);
185
198
  const cols = grid.cols;
186
199
  const keyOf = (col, row) => row * cols + col;
187
200
  const startKey = keyOf(start.col, start.row);
@@ -222,6 +235,8 @@ export function findPath(grid, from, to, options = {}) {
222
235
  found = true;
223
236
  break;
224
237
  }
238
+ if (closed.size >= maxNodes)
239
+ return { status: "budget", visited: closed.size };
225
240
  closed.add(current.key);
226
241
  const col = current.key % cols;
227
242
  const row = (current.key - col) / cols;
@@ -236,7 +251,9 @@ export function findPath(grid, from, to, options = {}) {
236
251
  const nKey = keyOf(nc, nr);
237
252
  if (closed.has(nKey))
238
253
  continue;
239
- const multiplier = stepCost === undefined ? 1 : Math.max(1, stepCost(grid.center(col, row), grid.center(nc, nr)));
254
+ if (options.canTraverse !== undefined && !options.canTraverse(pointAt(col, row), pointAt(nc, nr)))
255
+ continue;
256
+ const multiplier = stepCost === undefined ? 1 : Math.max(1, stepCost(pointAt(col, row), pointAt(nc, nr)));
240
257
  const tentative = g + step * multiplier;
241
258
  if (tentative < (gScore.get(nKey) ?? Number.POSITIVE_INFINITY)) {
242
259
  gScore.set(nKey, tentative);
@@ -246,7 +263,7 @@ export function findPath(grid, from, to, options = {}) {
246
263
  }
247
264
  }
248
265
  if (!found)
249
- return null;
266
+ return { status: "no-path", visited: closed.size };
250
267
  const cells = [];
251
268
  let cursor = goalKey;
252
269
  while (cursor !== undefined) {
@@ -254,10 +271,9 @@ export function findPath(grid, from, to, options = {}) {
254
271
  cells.unshift({ col, row: (cursor - col) / cols });
255
272
  cursor = cameFrom.get(cursor);
256
273
  }
257
- const raw = cells.map((cell) => grid.center(cell.col, cell.row));
258
- if (passable(goalCell.col, goalCell.row))
259
- raw[raw.length - 1] = [to[0], to[1]];
260
- return options.smooth === false ? raw : smoothPath(grid, raw);
274
+ const raw = cells.map((cell) => [...pointAt(cell.col, cell.row)]);
275
+ const points = options.smooth === false ? raw : smoothPath(grid, raw, options.canTraverse);
276
+ return { status: "path", points, visited: closed.size };
261
277
  }
262
278
  const DEFAULT_SLOPE_STEP_WEIGHT = 1;
263
279
  /**
@@ -275,13 +291,13 @@ export function slopeStepCost(field, weight = DEFAULT_SLOPE_STEP_WEIGHT) {
275
291
  };
276
292
  }
277
293
  /** Remove waypoints the mover can skip because it has clear line-of-sight past them. */
278
- export function smoothPath(grid, points) {
294
+ export function smoothPath(grid, points, canTraverse) {
279
295
  if (points.length <= 2)
280
296
  return points.slice();
281
297
  const result = [points[0]];
282
298
  let anchor = 0;
283
299
  for (let i = 2; i < points.length; i += 1) {
284
- if (!grid.lineOfSight(points[anchor], points[i])) {
300
+ if (!grid.lineOfSight(points[anchor], points[i]) || (canTraverse !== undefined && !canTraverse(points[anchor], points[i]))) {
285
301
  result.push(points[i - 1]);
286
302
  anchor = i - 1;
287
303
  }
@@ -4,13 +4,19 @@ export interface NavMeshLink {
4
4
  from: number;
5
5
  to: number;
6
6
  cost?: number;
7
+ /** Where the link leaves `from`; defaults to that polygon's center. */
8
+ start?: Vec3;
9
+ /** Where the link lands in `to`; defaults to that polygon's center. */
10
+ end?: Vec3;
7
11
  }
8
- /** Serializable polygon navigation mesh data. */
12
+ /** Serializable polygon navigation mesh data. Treat it as immutable once queried; queries cache derived geometry per object. */
9
13
  export interface NavMeshData {
10
14
  /** Flat xyz vertex coordinates. Polygon indices refer to triples in this array. */
11
15
  verts: number[];
16
+ /** Convex polygons as vertex index lists. */
12
17
  polys: number[][];
13
18
  links: NavMeshLink[];
19
+ /** Area id per polygon, priced by {@link NavMeshQueryOptions.areaCosts}. */
14
20
  areas?: number[];
15
21
  }
16
22
  /** Neighbor relationship for one navigation polygon. */
@@ -22,12 +28,47 @@ export interface NavMeshAdjacency {
22
28
  export interface NavMeshPath {
23
29
  points: Vec3[];
24
30
  polys: number[];
31
+ /** True when the search budget ran out and the route ends at the polygon nearest the goal. */
32
+ partial?: boolean;
33
+ }
34
+ /** Tunable pricing and bounds for a {@link NavMeshQuery}. */
35
+ export interface NavMeshQueryOptions {
36
+ /** Cost multiplier per area id; unlisted areas cost 1, and a negative or non-finite cost blocks the area. */
37
+ areaCosts?: Readonly<Record<number, number>>;
38
+ /** Most polygons one path search may expand before returning a partial path. Default 2048. */
39
+ maxNodes?: number;
40
+ }
41
+ /** Serializable tuning of a {@link NavMeshQuery}. */
42
+ export interface NavMeshQuerySnapshot {
43
+ areaCosts: Record<number, number>;
44
+ maxNodes: number;
45
+ }
46
+ /** Cached, allocation-light queries over one {@link NavMeshData}. */
47
+ export interface NavMeshQuery {
48
+ readonly mesh: NavMeshData;
49
+ /** Polygon nearest the point in 3D, so stacked floors resolve by height; null for an empty mesh. */
50
+ findPolygon(point: Vec3): number | null;
51
+ /** Closest point on the mesh surface, or null for an empty mesh. */
52
+ closestPoint(point: Vec3): Vec3 | null;
53
+ /** Cheapest polygon corridor by A*, straightened with a portal funnel. */
54
+ findPath(from: Vec3, to: Vec3): NavMeshPath | null;
55
+ /** True when the straight segment stays on walkable, unblocked polygons. */
56
+ raycast(from: Vec3, to: Vec3): boolean;
57
+ retune(patch: NavMeshQueryOptions): void;
58
+ snapshot(): NavMeshQuerySnapshot;
59
+ restore(next: NavMeshQuerySnapshot): void;
25
60
  }
26
61
  /** Build polygon adjacency from shared edges and explicit off-mesh links. */
27
62
  export declare function buildNavAdjacency(mesh: NavMeshData): NavMeshAdjacency[];
63
+ /**
64
+ * Create a cached query over a nav mesh: height-aware polygon lookup through a uniform grid, binary-heap A*
65
+ * priced by area costs and bounded by `maxNodes`, portal-funnel path straightening, and edge-walking raycasts.
66
+ * @capability navmesh-query route, snap, and raycast on a polygon navmesh with retunable area costs and a bounded search
67
+ */
68
+ export declare function createNavMeshQuery(mesh: NavMeshData, options?: NavMeshQueryOptions): NavMeshQuery;
28
69
  /** Return the closest point on the mesh surface, or null for an empty mesh. */
29
70
  export declare function closestPoint(mesh: NavMeshData, point: Vec3): Vec3 | null;
30
- /** A* over polygon centers, followed by deterministic visibility string-pulling. */
71
+ /** A* over polygon portals with default area costs, straightened by a portal funnel. Use {@link createNavMeshQuery} to price areas. */
31
72
  export declare function findPath(mesh: NavMeshData, from: Vec3, to: Vec3): NavMeshPath | null;
32
73
  /** True when the segment remains over walkable polygons. */
33
74
  export declare function raycastNav(mesh: NavMeshData, from: Vec3, to: Vec3): boolean;