@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
@@ -0,0 +1,74 @@
1
+ const clamp01 = (value) => (Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0);
2
+ /**
3
+ * Per-player haptic channels mixed by priority.
4
+ * @capability haptics Continuous gamepad rumble from named channels (engine, road, impact) mixed by priority, plus fading pulses.
5
+ */
6
+ export function createHapticChannels(options = {}) {
7
+ let duck = clamp01(options.duck ?? 0.4);
8
+ const channels = new Map();
9
+ const last = { strong: 0, weak: 0 };
10
+ const write = (name, channel) => {
11
+ if (channel.strong === 0 && channel.weak === 0)
12
+ channels.delete(name);
13
+ else
14
+ channels.set(name, channel);
15
+ };
16
+ return {
17
+ set(name, level, priority = 0) {
18
+ write(name, { strong: clamp01(level.strong), weak: clamp01(level.weak), priority, remainingMs: null, totalMs: null });
19
+ },
20
+ pulse(name, pulse, priority = 0) {
21
+ const ms = Math.max(0, pulse.ms);
22
+ if (ms === 0) {
23
+ channels.delete(name);
24
+ return;
25
+ }
26
+ write(name, { strong: clamp01(pulse.strong), weak: clamp01(pulse.weak), priority, remainingMs: ms, totalMs: ms });
27
+ },
28
+ clear(name) {
29
+ if (name === undefined)
30
+ channels.clear();
31
+ else
32
+ channels.delete(name);
33
+ },
34
+ mix(dt) {
35
+ const stepMs = Math.max(0, dt) * 1000;
36
+ let top = -Infinity;
37
+ for (const [name, channel] of channels) {
38
+ if (channel.remainingMs !== null) {
39
+ channel.remainingMs -= stepMs;
40
+ if (channel.remainingMs <= 0) {
41
+ channels.delete(name);
42
+ continue;
43
+ }
44
+ }
45
+ if (channel.priority > top)
46
+ top = channel.priority;
47
+ }
48
+ let strong = 0;
49
+ let weak = 0;
50
+ for (const channel of channels.values()) {
51
+ const fade = channel.remainingMs === null || channel.totalMs === null ? 1 : channel.remainingMs / channel.totalMs;
52
+ const scale = (channel.priority >= top ? 1 : duck) * fade;
53
+ strong = Math.max(strong, channel.strong * scale);
54
+ weak = Math.max(weak, channel.weak * scale);
55
+ }
56
+ last.strong = strong;
57
+ last.weak = weak;
58
+ return last;
59
+ },
60
+ level: () => last,
61
+ retune(next) {
62
+ duck = clamp01(next.duck ?? duck);
63
+ },
64
+ snapshot() {
65
+ return { duck, channels: [...channels].map(([name, channel]) => ({ name, ...channel })) };
66
+ },
67
+ restore(snapshot) {
68
+ duck = clamp01(snapshot.duck);
69
+ channels.clear();
70
+ for (const { name, ...channel } of snapshot.channels)
71
+ channels.set(name, { ...channel });
72
+ },
73
+ };
74
+ }
@@ -0,0 +1,40 @@
1
+ /** One action's press history inside an {@link InputBuffer}. */
2
+ export interface BufferedAction {
3
+ pressedAt: number;
4
+ previousPressAt: number | null;
5
+ releasedAt: number | null;
6
+ consumed: boolean;
7
+ }
8
+ /** Serializable state for an {@link InputBuffer}. */
9
+ export interface InputBufferSnapshot {
10
+ windowMs: number;
11
+ actions: Record<string, BufferedAction>;
12
+ }
13
+ /** Remembers recent action presses so a press slightly early (a jump before landing) still counts. Times are caller-supplied ms, so it runs the same on a client, a host or in a replay. */
14
+ export interface InputBuffer {
15
+ /** Record a press edge. */
16
+ press(action: string, nowMs: number): void;
17
+ /** Record a release edge; {@link InputBuffer.hold} stops growing. */
18
+ release(action: string, nowMs: number): void;
19
+ /** `true` once for a press no older than `windowMs` (default: the buffer's window), then the press is spent. */
20
+ consume(action: string, nowMs: number, windowMs?: number): boolean;
21
+ /** `true` while `nowMs` is within `windowMs` of `groundedAtMs` — late-jump grace after leaving a ledge. `null` never qualifies. */
22
+ coyote(groundedAtMs: number | null, nowMs: number, windowMs: number): boolean;
23
+ /** How long the action has been held, ms; `0` when released or never pressed. */
24
+ hold(action: string, nowMs: number): number;
25
+ /** `true` when the last two presses were at most `gapMs` apart and the latest is no older than `gapMs`. */
26
+ doubleTap(action: string, nowMs: number, gapMs: number): boolean;
27
+ /** Change the default buffer window. */
28
+ retune(next: {
29
+ windowMs: number;
30
+ }): void;
31
+ snapshot(): InputBufferSnapshot;
32
+ restore(snapshot: InputBufferSnapshot): void;
33
+ }
34
+ /**
35
+ * Creates an input buffer for jump buffering, coyote time, hold duration and double taps.
36
+ * @capability input-buffer buffer early presses, coyote time, hold duration and double tap for responsive controls
37
+ */
38
+ export declare function createInputBuffer(options: {
39
+ windowMs: number;
40
+ }): InputBuffer;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Creates an input buffer for jump buffering, coyote time, hold duration and double taps.
3
+ * @capability input-buffer buffer early presses, coyote time, hold duration and double tap for responsive controls
4
+ */
5
+ export function createInputBuffer(options) {
6
+ let windowMs = options.windowMs;
7
+ let actions = new Map();
8
+ return {
9
+ press(action, nowMs) {
10
+ const previous = actions.get(action);
11
+ actions.set(action, {
12
+ pressedAt: nowMs,
13
+ previousPressAt: previous === undefined ? null : previous.pressedAt,
14
+ releasedAt: null,
15
+ consumed: false,
16
+ });
17
+ },
18
+ release(action, nowMs) {
19
+ const entry = actions.get(action);
20
+ if (entry !== undefined && entry.releasedAt === null)
21
+ entry.releasedAt = nowMs;
22
+ },
23
+ consume(action, nowMs, window = windowMs) {
24
+ const entry = actions.get(action);
25
+ if (entry === undefined || entry.consumed || nowMs - entry.pressedAt > window)
26
+ return false;
27
+ entry.consumed = true;
28
+ return true;
29
+ },
30
+ coyote(groundedAtMs, nowMs, window) {
31
+ return groundedAtMs !== null && nowMs - groundedAtMs <= window;
32
+ },
33
+ hold(action, nowMs) {
34
+ const entry = actions.get(action);
35
+ if (entry === undefined || entry.releasedAt !== null)
36
+ return 0;
37
+ return Math.max(0, nowMs - entry.pressedAt);
38
+ },
39
+ doubleTap(action, nowMs, gapMs) {
40
+ const entry = actions.get(action);
41
+ if (entry === undefined || entry.previousPressAt === null)
42
+ return false;
43
+ return entry.pressedAt - entry.previousPressAt <= gapMs && nowMs - entry.pressedAt <= gapMs;
44
+ },
45
+ retune(next) {
46
+ windowMs = next.windowMs;
47
+ },
48
+ snapshot() {
49
+ const copy = {};
50
+ for (const [action, entry] of actions)
51
+ copy[action] = { ...entry };
52
+ return { windowMs, actions: copy };
53
+ },
54
+ restore(snapshot) {
55
+ windowMs = snapshot.windowMs;
56
+ actions = new Map(Object.entries(snapshot.actions).map(([action, entry]) => [action, { ...entry }]));
57
+ },
58
+ };
59
+ }
@@ -9,6 +9,14 @@ export interface LookDeltas {
9
9
  yaw: number;
10
10
  pitch: number;
11
11
  }
12
+ /** Plain JSON state of a {@link LookChannel}: the pending pixel deltas and the committed pose. */
13
+ export interface LookChannelState {
14
+ deltaX: number;
15
+ deltaY: number;
16
+ yaw: number;
17
+ pitch: number;
18
+ verticalOffset: number;
19
+ }
12
20
  export interface LookChannel {
13
21
  accumulate(dx: number, dy: number): void;
14
22
  /** Yaw/pitch deltas (radians) for this frame; clears the accumulator. */
@@ -19,6 +27,8 @@ export interface LookChannel {
19
27
  readPitch(): number;
20
28
  setVerticalOffset(offset: number): void;
21
29
  readVerticalOffset(): number;
30
+ snapshot(): LookChannelState;
31
+ restore(next: LookChannelState): void;
22
32
  }
23
33
  export interface LookChannelOptions {
24
34
  /** Radians of rotation per pixel of pointer travel. */
@@ -40,5 +40,11 @@ export function createLookChannel({ sensitivity, maxVerticalOffset = Infinity })
40
40
  readVerticalOffset() {
41
41
  return verticalOffset;
42
42
  },
43
+ snapshot() {
44
+ return { deltaX, deltaY, yaw, pitch, verticalOffset };
45
+ },
46
+ restore(next) {
47
+ ({ deltaX, deltaY, yaw, pitch, verticalOffset } = next);
48
+ },
43
49
  };
44
50
  }
@@ -65,5 +65,7 @@ export interface DragCapture {
65
65
  cancel(): void;
66
66
  /** The current drag state, or `null` when idle. */
67
67
  state(): DragState | null;
68
+ /** Resumes the drag a {@link DragCapture.state} described (its `origin` and `current`), or goes idle on `null`. */
69
+ restore(next: DragState | null): void;
68
70
  }
69
71
  export declare function createDragCapture(config?: DragCaptureConfig): DragCapture;
@@ -61,5 +61,9 @@ export function createDragCapture(config = {}) {
61
61
  current = null;
62
62
  },
63
63
  state: snapshot,
64
+ restore(next) {
65
+ origin = next === null ? null : [next.origin[0], next.origin[1], next.origin[2]];
66
+ current = next === null ? null : [next.current[0], next.current[1], next.current[2]];
67
+ },
64
68
  };
65
69
  }
@@ -29,13 +29,14 @@ export interface PromptRegistrySnapshot {
29
29
  export interface PromptRegistry {
30
30
  /**
31
31
  * Add (or replace, by id) a positioned prompt. Replacing an existing id keeps
32
- * its registration slot so tie-break order is stable. Does not re-resolve on
33
- * its own — the next {@link resolve} picks up the change.
32
+ * its registration slot so tie-break order is stable. Changed active content
33
+ * notifies immediately; selection is recalculated by the next {@link resolve}.
34
34
  */
35
35
  register(prompt: PositionedPrompt): void;
36
36
  /**
37
37
  * Patch a registered prompt in place. No-op if `id` is unknown. Only the fields
38
- * present in `patch` change; the rest are preserved.
38
+ * present in `patch` change; the rest are preserved. Changed active content
39
+ * notifies immediately without recalculating selection.
39
40
  */
40
41
  update(id: string, patch: PositionedPromptPatch): void;
41
42
  /** Remove the prompt with this id, if present. Returns whether one was removed. */
@@ -54,7 +55,7 @@ export interface PromptRegistry {
54
55
  resolve(playerPosition: PromptPoint): PositionedPrompt | null;
55
56
  /** The prompt selected by the most recent {@link resolve}, or `null`. */
56
57
  active(): PositionedPrompt | null;
57
- /** Observe active-prompt changes (resolve transitions, clear, restore). Returns an unsubscribe fn. */
58
+ /** Observe active-prompt content changes, selection transitions, clear and restore. Returns an unsubscribe fn. */
58
59
  subscribe(listener: () => void): () => void;
59
60
  /** Serializable state for a save. */
60
61
  snapshot(): PromptRegistrySnapshot;
@@ -1,4 +1,4 @@
1
- import { resolveActivePrompt, } from "./proximityPrompt.js";
1
+ import { positionedPromptsEqual, resolveActivePrompt, } from "./proximityPrompt.js";
2
2
  /** Structural copy of a positioned prompt so external mutation can't corrupt registry state. */
3
3
  function clonePrompt(prompt) {
4
4
  const copy = {
@@ -50,8 +50,12 @@ export function createPromptRegistry() {
50
50
  }
51
51
  return {
52
52
  register(prompt) {
53
- prompts.set(prompt.id, clonePrompt(prompt));
53
+ const existing = prompts.get(prompt.id);
54
+ const next = clonePrompt(prompt);
55
+ prompts.set(prompt.id, next);
54
56
  invalidate();
57
+ if (activeId === prompt.id && existing !== undefined && !positionedPromptsEqual(existing, next))
58
+ notify();
55
59
  },
56
60
  update(id, patch) {
57
61
  const existing = prompts.get(id);
@@ -71,6 +75,8 @@ export function createPromptRegistry() {
71
75
  }
72
76
  prompts.set(id, merged);
73
77
  invalidate();
78
+ if (activeId === id && !positionedPromptsEqual(existing, merged))
79
+ notify();
74
80
  },
75
81
  unregister(id) {
76
82
  const removed = prompts.delete(id);
@@ -14,6 +14,9 @@ function violatesKind(layout, traits, itemId) {
14
14
  return kind === null || !layout.accepts.includes(kind);
15
15
  return kind !== layout.accepts;
16
16
  }
17
+ function validSlot(state, slot) {
18
+ return Number.isInteger(slot) && slot >= 0 && slot < state.slots.length;
19
+ }
17
20
  function computeCapacity(state, traits, itemId) {
18
21
  const limit = traits.stackLimit(itemId);
19
22
  let existingRoom = 0;
@@ -58,7 +61,7 @@ function autoPut(state, layout, traits, itemId, count) {
58
61
  return { status: "ok", state: { slots } };
59
62
  }
60
63
  function explicitPut(state, layout, traits, itemId, count, slot) {
61
- if (slot < 0 || slot >= state.slots.length)
64
+ if (!validSlot(state, slot))
62
65
  return { status: "rejected", reason: "invalid-slot" };
63
66
  if (violatesKind(layout, traits, itemId))
64
67
  return { status: "rejected", reason: "wrong-kind" };
@@ -120,7 +123,7 @@ export function hasItem(state, itemId, count) {
120
123
  }
121
124
  /** Move the stack at `fromSlot` to `toSlot` (or auto-stack when omitted) — swapping, merging with remainder, or relocating across two inventories. Rejects on empty source, wrong kind, or no space. */
122
125
  export function moveItem(from, fromSlot, to, toLayout, traits, toSlot) {
123
- if (fromSlot < 0 || fromSlot >= from.slots.length)
126
+ if (!validSlot(from, fromSlot))
124
127
  return { status: "rejected", reason: "invalid-slot" };
125
128
  const source = from.slots[fromSlot];
126
129
  if (source === null)
@@ -138,7 +141,7 @@ export function moveItem(from, fromSlot, to, toLayout, traits, toSlot) {
138
141
  const toSlots = sameInventory ? fromSlots : to.slots.slice();
139
142
  const limit = traits.stackLimit(source.itemId);
140
143
  if (toSlot !== undefined) {
141
- if (toSlot < 0 || toSlot >= toSlots.length)
144
+ if (!validSlot(to, toSlot))
142
145
  return { status: "rejected", reason: "invalid-slot" };
143
146
  const dest = toSlots[toSlot];
144
147
  if (dest === null) {
@@ -179,12 +182,12 @@ export function moveItem(from, fromSlot, to, toLayout, traits, toSlot) {
179
182
  * @capability inventory-split split a stack into a target or first-empty slot
180
183
  */
181
184
  export function splitStack(state, slot, amount, toSlot) {
182
- if (slot < 0 || slot >= state.slots.length)
185
+ if (!validSlot(state, slot))
183
186
  return { status: "rejected", reason: "invalid-slot" };
184
187
  const source = state.slots[slot];
185
188
  if (source === null)
186
189
  return { status: "rejected", reason: "empty-slot" };
187
- if (amount <= 0 || amount >= source.count)
190
+ if (!Number.isFinite(amount) || amount <= 0 || amount >= source.count)
188
191
  return { status: "rejected", reason: "invalid-amount" };
189
192
  let target = toSlot;
190
193
  if (target === undefined) {
@@ -193,7 +196,7 @@ export function splitStack(state, slot, amount, toSlot) {
193
196
  return { status: "rejected", reason: "no-space" };
194
197
  }
195
198
  else {
196
- if (target < 0 || target >= state.slots.length)
199
+ if (!validSlot(state, target))
197
200
  return { status: "rejected", reason: "invalid-slot" };
198
201
  if (target === slot)
199
202
  return { status: "rejected", reason: "slot-occupied" };
@@ -18,7 +18,7 @@ export interface ShapedGrid<T> {
18
18
  readonly height: number;
19
19
  readonly placements: readonly Placement<T>[];
20
20
  }
21
- export type ShapedRejection = "out-of-bounds" | "overlap" | "duplicate-id" | "unknown-id";
21
+ export type ShapedRejection = "out-of-bounds" | "overlap" | "duplicate-id" | "unknown-id" | "invalid-footprint" | "invalid-origin";
22
22
  export interface ShapedPlaceResult<T> {
23
23
  status: "ok";
24
24
  grid: ShapedGrid<T>;
@@ -36,19 +36,58 @@ export declare function rotateFootprint(footprint: Footprint, rotation: Rotation
36
36
  /** @internal */
37
37
  export declare function occupiedCells(footprint: Footprint, origin: Cell, rotation: Rotation): Cell[];
38
38
  /**
39
- * A spatial grid inventory that holds shaped multi-cell items, Resident-Evil/Tarkov style.
39
+ * An immutable spatial inventory board holding caller-defined multi-cell items.
40
40
  *
41
- * @capability tetris-inventory a spatial grid inventory holding shaped multi-cell items
42
- * @internal
43
- */
41
+ * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
42
+ */
44
43
  export declare function createShapedGrid<T>(width: number, height: number): ShapedGrid<T>;
45
- /** @internal */
44
+ /**
45
+ * Check bounds and overlap for a discrete footprint, optionally ignoring the item being moved.
46
+ * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
47
+ */
46
48
  export declare function canPlace<T>(grid: ShapedGrid<T>, footprint: Footprint, origin: Cell, rotation: Rotation, ignoreId?: string): ShapedRejection | null;
47
- /** @internal */
49
+ /** Search policy: rotation order first, then rows and columns; the work budget counts candidate origins. */
50
+ export interface ShapedPlacementSearchOptions {
51
+ /** Defaults to `[0, 1, 2, 3]`; a game can prefer or forbid rotations. */
52
+ rotations?: readonly Rotation[];
53
+ /** Ignore an existing placement while searching for its next position. */
54
+ ignoreId?: string;
55
+ /** Nonnegative integer candidate limit; defaults to 4096. */
56
+ maxChecks?: number;
57
+ }
58
+ /** A detached placement, a completed no-fit search, or an incomplete search that reached its budget. */
59
+ export type ShapedPlacementSearchResult = {
60
+ status: "found";
61
+ origin: Cell;
62
+ rotation: Rotation;
63
+ checks: number;
64
+ } | {
65
+ status: "no-space";
66
+ checks: number;
67
+ } | {
68
+ status: "budget-exceeded";
69
+ checks: number;
70
+ };
71
+ /**
72
+ * Find the first legal placement without mutating the board; occupancy is indexed once per search.
73
+ *
74
+ * @capability shaped-placement find a shaped inventory item's first fit with ordered rotations, row-major placement, and an explicit search budget
75
+ */
76
+ export declare function findShapedPlacement<T>(grid: ShapedGrid<T>, footprint: Footprint, options?: ShapedPlacementSearchOptions): ShapedPlacementSearchResult;
77
+ /**
78
+ * Place a caller-defined item without mutating the source board; duplicate ids reject.
79
+ * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
80
+ */
48
81
  export declare function placeShaped<T>(grid: ShapedGrid<T>, item: ShapedItem<T>, origin: Cell, rotation?: Rotation): ShapedResult<T>;
49
- /** @internal */
82
+ /**
83
+ * Remove one placement without mutating the source board.
84
+ * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
85
+ */
50
86
  export declare function removeShaped<T>(grid: ShapedGrid<T>, id: string): ShapedResult<T>;
51
- /** @internal */
87
+ /**
88
+ * Move or rotate one placement, ignoring its current cells during overlap checks.
89
+ * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
90
+ */
52
91
  export declare function moveShaped<T>(grid: ShapedGrid<T>, id: string, origin: Cell, rotation?: Rotation): ShapedResult<T>;
53
92
  /** @internal */
54
93
  export declare function cellOccupant<T>(grid: ShapedGrid<T>, cell: Cell): string | null;
@@ -41,16 +41,28 @@ export function occupiedCells(footprint, origin, rotation) {
41
41
  return rotated.map(([c, r]) => [c + origin[0], r + origin[1]]);
42
42
  }
43
43
  /**
44
- * A spatial grid inventory that holds shaped multi-cell items, Resident-Evil/Tarkov style.
44
+ * An immutable spatial inventory board holding caller-defined multi-cell items.
45
45
  *
46
- * @capability tetris-inventory a spatial grid inventory holding shaped multi-cell items
47
- * @internal
48
- */
46
+ * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
47
+ */
49
48
  export function createShapedGrid(width, height) {
50
- if (width <= 0 || height <= 0)
51
- throw new Error("shaped grid needs positive dimensions");
49
+ requireDimensions(width, height);
52
50
  return { width, height, placements: [] };
53
51
  }
52
+ function requireDimensions(width, height) {
53
+ if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width <= 0 || height <= 0) {
54
+ throw new RangeError("shaped grid needs positive integer dimensions");
55
+ }
56
+ }
57
+ function validCell(cell) {
58
+ return Number.isSafeInteger(cell[0]) && Number.isSafeInteger(cell[1]);
59
+ }
60
+ function validFootprint(footprint) {
61
+ return footprint.length > 0 && footprint.every(validCell);
62
+ }
63
+ function validRotation(rotation) {
64
+ return Number.isInteger(rotation) && rotation >= 0 && rotation <= 3;
65
+ }
54
66
  function inBounds(grid, cells) {
55
67
  return cells.every(([c, r]) => c >= 0 && r >= 0 && c < grid.width && r < grid.height);
56
68
  }
@@ -65,8 +77,16 @@ function occupancyMap(grid, ignoreId) {
65
77
  }
66
78
  return map;
67
79
  }
68
- /** @internal */
80
+ /**
81
+ * Check bounds and overlap for a discrete footprint, optionally ignoring the item being moved.
82
+ * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
83
+ */
69
84
  export function canPlace(grid, footprint, origin, rotation, ignoreId) {
85
+ requireDimensions(grid.width, grid.height);
86
+ if (!validFootprint(footprint) || !validRotation(rotation))
87
+ return "invalid-footprint";
88
+ if (!validCell(origin))
89
+ return "invalid-origin";
70
90
  const cells = occupiedCells(footprint, origin, rotation);
71
91
  if (!inBounds(grid, cells))
72
92
  return "out-of-bounds";
@@ -77,7 +97,40 @@ export function canPlace(grid, footprint, origin, rotation, ignoreId) {
77
97
  }
78
98
  return null;
79
99
  }
80
- /** @internal */
100
+ /**
101
+ * Find the first legal placement without mutating the board; occupancy is indexed once per search.
102
+ *
103
+ * @capability shaped-placement find a shaped inventory item's first fit with ordered rotations, row-major placement, and an explicit search budget
104
+ */
105
+ export function findShapedPlacement(grid, footprint, options = {}) {
106
+ requireDimensions(grid.width, grid.height);
107
+ const rotations = options.rotations ?? [0, 1, 2, 3];
108
+ const maxChecks = options.maxChecks ?? 4096;
109
+ if (!validFootprint(footprint) || !rotations.every(validRotation))
110
+ throw new RangeError("invalid footprint or rotation");
111
+ if (!Number.isSafeInteger(maxChecks) || maxChecks < 0)
112
+ throw new RangeError("invalid placement search budget");
113
+ const occupied = occupancyMap(grid, options.ignoreId);
114
+ let checks = 0;
115
+ for (const rotation of rotations) {
116
+ const cells = rotateFootprint(footprint, rotation);
117
+ for (let row = 0; row < grid.height; row += 1) {
118
+ for (let column = 0; column < grid.width; column += 1) {
119
+ if (checks >= maxChecks)
120
+ return { status: "budget-exceeded", checks };
121
+ checks += 1;
122
+ if (cells.every(([c, r]) => column + c < grid.width && row + r < grid.height && !occupied.has(cellKey([column + c, row + r])))) {
123
+ return { status: "found", origin: [column, row], rotation, checks };
124
+ }
125
+ }
126
+ }
127
+ }
128
+ return { status: "no-space", checks };
129
+ }
130
+ /**
131
+ * Place a caller-defined item without mutating the source board; duplicate ids reject.
132
+ * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
133
+ */
81
134
  export function placeShaped(grid, item, origin, rotation = 0) {
82
135
  if (grid.placements.some((p) => p.id === item.id)) {
83
136
  return { status: "rejected", reason: "duplicate-id", detail: item.id };
@@ -94,14 +147,20 @@ export function placeShaped(grid, item, origin, rotation = 0) {
94
147
  };
95
148
  return { status: "ok", grid: { ...grid, placements: [...grid.placements, placement] } };
96
149
  }
97
- /** @internal */
150
+ /**
151
+ * Remove one placement without mutating the source board.
152
+ * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
153
+ */
98
154
  export function removeShaped(grid, id) {
99
155
  if (!grid.placements.some((p) => p.id === id)) {
100
156
  return { status: "rejected", reason: "unknown-id", detail: id };
101
157
  }
102
158
  return { status: "ok", grid: { ...grid, placements: grid.placements.filter((p) => p.id !== id) } };
103
159
  }
104
- /** @internal */
160
+ /**
161
+ * Move or rotate one placement, ignoring its current cells during overlap checks.
162
+ * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
163
+ */
105
164
  export function moveShaped(grid, id, origin, rotation) {
106
165
  const existing = grid.placements.find((p) => p.id === id);
107
166
  if (existing === undefined)
@@ -51,4 +51,10 @@ export interface AffixRoller {
51
51
  roll(base: ItemBaseDef, rarityId: string, rng: () => number): RolledItem;
52
52
  rollRandom(base: ItemBaseDef, rng: () => number): RolledItem;
53
53
  }
54
+ /**
55
+ * Rolls randomized gear from a base item: a weighted rarity tier scales base stats and picks
56
+ * prefix/suffix affixes that name and modify the item. Deterministic for a given `rng`.
57
+ *
58
+ * @capability affix-roller roll randomized gear (guns, armor, loot) with rarity tiers, stat scaling and prefix/suffix affixes from a seed
59
+ */
54
60
  export declare function createAffixRoller(config: RollerConfig): AffixRoller;
@@ -20,6 +20,12 @@ function assertConfig(config) {
20
20
  throw new Error(`rarity "${tier.id}" weight must be positive`);
21
21
  }
22
22
  }
23
+ /**
24
+ * Rolls randomized gear from a base item: a weighted rarity tier scales base stats and picks
25
+ * prefix/suffix affixes that name and modify the item. Deterministic for a given `rng`.
26
+ *
27
+ * @capability affix-roller roll randomized gear (guns, armor, loot) with rarity tiers, stat scaling and prefix/suffix affixes from a seed
28
+ */
23
29
  export function createAffixRoller(config) {
24
30
  assertConfig(config);
25
31
  const poolById = new Map(config.pools.map((p) => [p.id, p]));
@@ -21,6 +21,8 @@ export function wearAmount(spec, kind) {
21
21
  * @capability durability track item wear, breakage, and repair
22
22
  */
23
23
  export function applyWear(state, amount) {
24
+ if (Number.isNaN(amount))
25
+ throw new RangeError("Wear amount must not be NaN.");
24
26
  if (amount <= 0)
25
27
  return state;
26
28
  return { current: clamp(state.current - amount, 0, state.max), max: state.max };
@@ -40,6 +42,8 @@ export function repairQuote(spec, state, options) {
40
42
  return null;
41
43
  if (repair.station !== undefined && repair.station !== options?.station)
42
44
  return null;
45
+ if (options?.to !== undefined && !Number.isFinite(options.to))
46
+ return null;
43
47
  const loss = repair.qualityLossPerRepair ?? 0;
44
48
  const newMax = Math.max(1, state.max - loss);
45
49
  const capped = Math.min(state.current, newMax);
@@ -1,3 +1,26 @@
1
+ /** Save data for a registry; definitions must be structured-cloneable and use the caller's serialization format. */
2
+ export interface ItemInstanceRegistryState<TDef> {
3
+ prefix: string;
4
+ sequence: number;
5
+ entries: [string, TDef][];
6
+ }
7
+ /** Caller-owned synchronous storage; `entries()` must stream without materializing the collection. */
8
+ export interface ItemInstanceStorage<TDef> {
9
+ get(id: string): TDef | undefined;
10
+ has(id: string): boolean;
11
+ set(id: string, def: TDef): void;
12
+ delete(id: string): void;
13
+ count(): number;
14
+ entries(): Iterable<[string, TDef]>;
15
+ /** Consume entries completely and atomically replace storage; any failure must preserve previous data. */
16
+ replace(entries: Iterable<[string, TDef]>): void;
17
+ }
18
+ /** Inject storage and its saved allocator sequence when attaching an existing collection. */
19
+ export interface ItemInstanceRegistryOptions<TDef> {
20
+ storage?: ItemInstanceStorage<TDef>;
21
+ /** Nonnegative safe integer; defaults to zero. Retain it across releases and storage reattachment. */
22
+ sequence?: number;
23
+ }
1
24
  /**
2
25
  * A runtime store for procedurally generated item instances — a rolled unique gun, a rolled
3
26
  * affixed relic — keyed by a generated id distinct from any static catalog id. The counterpart a
@@ -5,6 +28,14 @@
5
28
  * rolls never need a hand-rolled parallel registry (#536.1).
6
29
  */
7
30
  export interface ItemInstanceRegistry<TDef> {
31
+ /** Detached definitions and allocator state, including ids of released instances in the sequence. */
32
+ state(): ItemInstanceRegistryState<TDef>;
33
+ /** Detached batches of at most `pageSize` entries; throws if the registry changes during iteration. Keep external storage and definitions unchanged until finished. */
34
+ statePages(pageSize: number): IterableIterator<ItemInstanceRegistryState<TDef>>;
35
+ /** Replace the registry; returns false without mutation when the prefix or allocator is invalid. */
36
+ restore(next: ItemInstanceRegistryState<TDef>): boolean;
37
+ /** Stream detached pages through atomic storage replacement. All pages must share prefix and sequence; malformed pages or storage failures throw without changing state. Empty saves require one empty page. */
38
+ restorePages(pages: Iterable<ItemInstanceRegistryState<TDef>>): boolean;
8
39
  /** Stores `def` under a fresh generated id derived from `baseId`; returns that id. */
9
40
  register(baseId: string, def: TDef): string;
10
41
  get(id: string): TDef | undefined;
@@ -19,7 +50,7 @@ export interface ItemInstanceRegistry<TDef> {
19
50
  *
20
51
  * @capability item-instance-registry a runtime store for procedurally generated item instances
21
52
  */
22
- export declare function createItemInstanceRegistry<TDef>(prefix?: string): ItemInstanceRegistry<TDef>;
53
+ export declare function createItemInstanceRegistry<TDef>(prefix?: string, options?: ItemInstanceRegistryOptions<TDef>): ItemInstanceRegistry<TDef>;
23
54
  /**
24
55
  * Bridges any procedural roller into a `LootEntry.generate` callback: rolls a `{ baseId, def }` pair
25
56
  * and registers it, returning the runtime id the loot roll hands back as the drop's `item`.