@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
@@ -1,6 +1,8 @@
1
+ import { createAuthoredSimulation } from "../world/authoredSimulation.js";
1
2
  import { createCommandRegistry } from "../commands/commandRegistry.js";
2
3
  import {} from "../game/cosmetics.js";
3
4
  import { groundFieldFor } from "../world/terrain.js";
5
+ import { createWorldSolids, structureSolids } from "../world/worldSolids.js";
4
6
  import { createGameEvents } from "../game/events.js";
5
7
  import { createGameFeed } from "../game/feed.js";
6
8
  import { setGamePhase } from "../game/gamePhase.js";
@@ -11,6 +13,7 @@ import { createItemUse } from "../item/use.js";
11
13
  import { createWeaponStats } from "../item/weapon.js";
12
14
  import {} from "../scene/entityStats.js";
13
15
  import {} from "../scene/entityStore.js";
16
+ import { installPursuitPersistence } from "../scene/behaviorRuntime.js";
14
17
  import { createChangeSignal, notifyAfter } from "../store/changeSignal.js";
15
18
  import { createObservableKeyedStore } from "../store/observableKeyedStore.js";
16
19
  import { applyWorldSnapshot, composeWorldSnapshot, } from "./worldSnapshot.js";
@@ -21,7 +24,7 @@ import { localSaveBackend, memorySaveBackend } from "../game/saveStore.js";
21
24
  import { createSimClock } from "../time/simClock.js";
22
25
  import { createSimContext } from "./simContext.js";
23
26
  import { seededRng } from "../random/rng.js";
24
- import { createCameraDirector } from "./cameraDirector.js";
27
+ import { createCameraDirector, installCameraPersistence } from "./cameraDirector.js";
25
28
  import { createParticleDirector } from "../vfx/particleDirector.js";
26
29
  import { createInputSnapshot } from "./inputSnapshot.js";
27
30
  import { baselineDescriptors } from "./descriptors/baseline.js";
@@ -43,6 +46,10 @@ export function createGameContext(options) {
43
46
  const activeUserId = () => actingUserId ?? player.userId;
44
47
  const time = createSimClock({ config: definition.time, onChange: signal.notify });
45
48
  const ground = groundFieldFor(definition.world);
49
+ const solids = createWorldSolids();
50
+ if (definition.world?.kind === "environment") {
51
+ solids.set("environment:structures", structureSolids(definition.world, ground.sampleHeight));
52
+ }
46
53
  const rawEvents = createGameEvents();
47
54
  const events = {
48
55
  on: rawEvents.on,
@@ -57,6 +64,7 @@ export function createGameContext(options) {
57
64
  content,
58
65
  signalNotify: signal.notify,
59
66
  ground,
67
+ solids,
60
68
  events,
61
69
  time,
62
70
  ...(occluder !== undefined ? { occluder } : {}),
@@ -190,12 +198,26 @@ export function createGameContext(options) {
190
198
  // --- Combat installer (effects, projectiles, death, combat FX) ---
191
199
  // Command runner closes over `ctx` once assembled (same late-bind pattern as before).
192
200
  let ctxRef = null;
201
+ const { sweep, acceleration, targets, ...projectileBudgets } = definition.projectileTravel ?? {};
202
+ function projectileContext() {
203
+ if (ctxRef === null)
204
+ throw new Error("Projectile providers require an assembled GameContext");
205
+ return ctxRef;
206
+ }
193
207
  const combat = createCombatSubsystem({
194
208
  content,
195
209
  signalNotify: signal.notify,
196
210
  now,
197
211
  events,
198
212
  time,
213
+ projectileTravel: {
214
+ ...projectileBudgets,
215
+ ...(sweep === undefined ? {} : { sweep: (from, to, step) => sweep(projectileContext(), from, to, step) }),
216
+ ...(targets === undefined ? {} : { targets: () => targets(projectileContext()) }),
217
+ acceleration: acceleration === undefined
218
+ ? (position, _velocity, at, input) => authoredEnvironment.accelerationAt(position, at, input.travel?.windResponse ?? 0, input.travel?.maxAcceleration ?? 0, input.travel?.forceMask ?? 0)
219
+ : (position, velocity, at, input) => acceleration(projectileContext(), position, velocity, at, input),
220
+ },
199
221
  entities,
200
222
  objects,
201
223
  combatSpatial,
@@ -209,20 +231,26 @@ export function createGameContext(options) {
209
231
  lootRegistry,
210
232
  spawnWorldItem,
211
233
  despawnEntity,
212
- runCommand(name, args) {
234
+ runCommand(name, args, actorUserId) {
213
235
  if (ctxRef === null)
214
236
  return;
215
- commandRegistry.run(ctxRef, name, args);
237
+ if (actorUserId === undefined)
238
+ ctxRef.game.commands.run(name, args);
239
+ else
240
+ ctxRef.game.commands.runAs(actorUserId, name, args);
216
241
  },
217
- localUserId: player.userId,
242
+ userIdOf: (instanceId) => instanceId === player.userId || featureValue("players")?.has(instanceId)
243
+ ? instanceId : possession.ownerOf(instanceId),
244
+ rng,
218
245
  ...(definition.physics !== undefined ? { physics: definition.physics } : {}),
219
246
  });
220
247
  scene.setOnAfterSpawn((instanceId) => combat.death.revive(instanceId));
221
248
  const { projectiles, combatFx, vfxInstances, effects } = combat;
222
249
  const store = notifyAfter(createObservableKeyedStore(), ["set", "delete", "hydrate"], signal.notify);
223
250
  const { pile, loop, raceState, cardPiles, turnLoops } = createContextRegistries(signal.notify);
224
- const camera = notifyAfter(createCameraDirector(), ["follow", "setCinematic", "setChaseTuning"], signal.notify);
251
+ const camera = notifyAfter(createCameraDirector(), ["follow", "setCinematic", "setChaseTuning", "setRig", "restore", "reset"], signal.notify);
225
252
  const particles = createParticleDirector();
253
+ const authoredEnvironment = createAuthoredSimulation({ document: definition.authoredDocument, timeSeconds: time.now, particles });
226
254
  const input = createInputSnapshot();
227
255
  // --- Descriptor install (single feature enable path) ---
228
256
  const featureDeps = {
@@ -240,6 +268,7 @@ export function createGameContext(options) {
240
268
  walletOf,
241
269
  setWallet: (userId, state) => wallets.set(userId, state),
242
270
  layouts,
271
+ itemTraits: playerSys.itemTraits,
243
272
  inventoryFor,
244
273
  ensureInstanceStats,
245
274
  seedUserPool,
@@ -336,6 +365,20 @@ export function createGameContext(options) {
336
365
  });
337
366
  saveModules.push(module);
338
367
  }
368
+ function reviveRestoredEntities(snapshot) {
369
+ if (!Object.hasOwn(snapshot, "entities") || !Array.isArray(snapshot["entities"]))
370
+ return;
371
+ for (const entity of entities.list()) {
372
+ const receive = content.entityById?.(entity.name)?.receive;
373
+ const alive = Object.values(receive ?? {}).every((rule) => {
374
+ const terminal = rule.order[rule.order.length - 1];
375
+ const pool = terminal === undefined ? undefined : statsByInstance.get(entity.id)?.[terminal];
376
+ return pool === undefined || pool.current > pool.min;
377
+ });
378
+ if (alive)
379
+ combat.death.revive(entity.id);
380
+ }
381
+ }
339
382
  // --- Assemble public GameContext ---
340
383
  const ctx = {
341
384
  rng,
@@ -346,11 +389,13 @@ export function createGameContext(options) {
346
389
  despawn: despawnEntity,
347
390
  update: entities.update,
348
391
  setPose: entities.setPose,
392
+ setVelocity: entities.setVelocity,
349
393
  setPoseConstraint: entities.setPoseConstraint,
350
394
  get: entities.get,
351
395
  list: entities.list,
352
396
  ids: entities.ids,
353
397
  subscribeMembership: entities.subscribeMembership,
398
+ subscribeBehaviors: entities.subscribeBehaviors,
354
399
  spawnPoseOf: entities.spawnPoseOf,
355
400
  resetToSpawn: entities.resetToSpawn,
356
401
  resetAllToSpawn,
@@ -370,6 +415,9 @@ export function createGameContext(options) {
370
415
  willHitProjectile: projectiles.willHitProjectile,
371
416
  fireProjectile: projectiles.fireProjectile,
372
417
  settleProjectile: projectiles.settleProjectile,
418
+ activeProjectiles: projectiles.activeProjectiles,
419
+ projectileState: projectiles.snapshot,
420
+ restoreProjectiles: projectiles.restore,
373
421
  distance: spatial.distance,
374
422
  inRadius: spatial.inRadius,
375
423
  hasLineOfSight: spatial.hasLineOfSight,
@@ -403,9 +451,11 @@ export function createGameContext(options) {
403
451
  raycast: (input) => sceneRaycast.raycast(input),
404
452
  raycastAll: (input) => sceneRaycast.raycastAll(input),
405
453
  },
454
+ environment: authoredEnvironment,
406
455
  world: {
407
456
  ground,
408
457
  groundHeightAt: ground.sampleHeight,
458
+ solids,
409
459
  },
410
460
  game: {
411
461
  territory: options.territory,
@@ -500,7 +550,7 @@ export function createGameContext(options) {
500
550
  weapon,
501
551
  },
502
552
  time,
503
- sim: createSimContext({ config: definition.simulation, entities }),
553
+ sim: createSimContext({ config: definition.simulation, entities, time }),
504
554
  camera,
505
555
  particles,
506
556
  input,
@@ -510,24 +560,46 @@ export function createGameContext(options) {
510
560
  snapshot: (viewer) => composeWorldSnapshot(replicationModules, viewer),
511
561
  hydrate(snapshot) {
512
562
  applyWorldSnapshot(replicationModules, snapshot);
563
+ reviveRestoredEntities(snapshot);
564
+ signal.notify();
565
+ },
566
+ state: () => structuredClone(composeWorldSnapshot(saveModules)),
567
+ restore(state) {
568
+ applyWorldSnapshot(saveModules, structuredClone(state));
569
+ reviveRestoredEntities(state);
513
570
  signal.notify();
514
571
  },
515
572
  replicationVersion,
516
573
  replicatesPerViewer: () => projectsViewers,
517
574
  };
518
575
  ctxRef = ctx;
576
+ ctx.sim.addStage({ id: "authored-environment", phase: "afterTick", run: (_dt, _tick, gameDt) => authoredEnvironment.step(gameDt) });
577
+ ctx.sim.addStage({ id: "projectile-targets", phase: "beforeMovement", run: (_dt, _tick, gameDt) => combat.captureProjectileTargets(gameDt) });
578
+ ctx.sim.addStage({ id: "projectile-travel", phase: "afterTick", run: (_dt, _tick, gameDt) => projectiles.advanceProjectiles(gameDt, time.now()) });
579
+ registerSave({ key: "environment", snapshot: authoredEnvironment.snapshot, hydrate: (state) => authoredEnvironment.restore(state) });
580
+ registerSave({ key: "projectiles", snapshot: projectiles.snapshot, hydrate: (state) => projectiles.restore(state) });
581
+ registerSave({
582
+ key: "sim",
583
+ snapshot: () => ctx.sim.snapshot(),
584
+ hydrate: (state) => ctx.sim.restore(state),
585
+ decode: (raw) => {
586
+ if (raw === null || typeof raw !== "object" || !("loop" in raw) || !("poses" in raw))
587
+ return null;
588
+ return raw;
589
+ },
590
+ });
519
591
  const physicsBackend = definition.physics?.backend;
520
592
  if (physicsBackend !== undefined) {
521
- ctx.sim.addStage({ id: "physics", phase: "afterMovement", run: (dt) => physicsBackend.step(dt) });
593
+ ctx.sim.addStage({ id: "physics", phase: "afterMovement", run: (_dt, _tick, gameDt) => { if (gameDt > 0)
594
+ physicsBackend.step(gameDt); } });
522
595
  }
596
+ installPursuitPersistence(ctx, aoiRadius, definition.pursuit?.eligible);
597
+ installCameraPersistence(ctx);
523
598
  const saveOptions = resolveSaveOptions(definition, options);
524
599
  if (saveOptions !== undefined) {
525
600
  const saveTarget = {
526
- snapshot: () => composeWorldSnapshot(saveModules),
527
- hydrate: (snapshot) => {
528
- applyWorldSnapshot(saveModules, snapshot);
529
- signal.notify();
530
- },
601
+ snapshot: ctx.state,
602
+ hydrate: ctx.restore,
531
603
  subscribe: signal.subscribe,
532
604
  };
533
605
  ctx.game.save = createRuntimeSave({ target: saveTarget, ...saveOptions });
@@ -554,10 +626,11 @@ function resolveSaveOptions(definition, options) {
554
626
  return undefined;
555
627
  const config = persist === undefined || persist === true ? {} : persist;
556
628
  return {
557
- backend: config.storage === "memory" ? memorySaveBackend() : localSaveBackend(),
629
+ backend: config.storage === "memory" ? memorySaveBackend() : localSaveBackend(undefined, { errorMode: "throw" }),
558
630
  mode: config.mode,
559
631
  key: saveKey(definition.name),
560
632
  version: config.version,
633
+ migrate: config.migrate,
561
634
  autosaveMs: config.autosaveMs,
562
635
  now: options.now,
563
636
  };
@@ -1,3 +1,4 @@
1
+ import type { AuthoredSimulationRuntime } from "../world/authoredSimulation.js";
1
2
  import type { Territory } from "../world/territory.js";
2
3
  import type { CurrencyDefinition } from "../economy/currency.js";
3
4
  import type { CardPile, CardPileConfig } from "../cards/cardPile.js";
@@ -11,6 +12,7 @@ import type { ChargeOptions as WalletChargeOptions } from "../economy/wallet.js"
11
12
  import type { Cosmetics } from "../game/cosmetics.js";
12
13
  import type { GameDefinition } from "../game/defineGame.js";
13
14
  import type { TerrainField } from "../world/terrain.js";
15
+ import type { WorldSolids } from "../world/worldSolids.js";
14
16
  import type { GameEventMap, GameEvents, VfxKind } from "../game/events.js";
15
17
  import type { VfxInstanceStore } from "../game/vfxInstance.js";
16
18
  import type { GameFeed } from "../game/feed.js";
@@ -256,11 +258,14 @@ export interface SceneEntityContext {
256
258
  despawn(instanceId: string): boolean;
257
259
  update: EntityStore["update"];
258
260
  setPose(instanceId: string, pose: EntityPose): boolean;
261
+ /** Set finite world velocity without changing pose; invalid tuples and missing entities return false unchanged. */
262
+ setVelocity: EntityStore["setVelocity"];
259
263
  setPoseConstraint: EntityStore["setPoseConstraint"];
260
264
  get(instanceId: string): SceneEntity | null;
261
265
  list(): readonly SceneEntity[];
262
266
  ids: EntityStore["ids"];
263
267
  subscribeMembership: EntityStore["subscribeMembership"];
268
+ subscribeBehaviors: EntityStore["subscribeBehaviors"];
264
269
  spawnPoseOf(instanceId: string): SpawnPose | null;
265
270
  resetToSpawn(instanceId: string): boolean;
266
271
  resetAllToSpawn(filter?: (entity: SceneEntity) => boolean): number;
@@ -286,6 +291,10 @@ export interface SceneEntityContext {
286
291
  willHitProjectile: ProjectileSystem["willHitProjectile"];
287
292
  fireProjectile: ProjectileSystem["fireProjectile"];
288
293
  settleProjectile: ProjectileSystem["settleProjectile"];
294
+ /** Live travel poses for game-owned arrow/projectile appearance. */
295
+ activeProjectiles: ProjectileSystem["activeProjectiles"];
296
+ projectileState: ProjectileSystem["snapshot"];
297
+ restoreProjectiles: ProjectileSystem["restore"];
289
298
  distance: SpatialApi["distance"];
290
299
  inRadius: SpatialApi["inRadius"];
291
300
  hasLineOfSight: SpatialApi["hasLineOfSight"];
@@ -344,6 +353,11 @@ export type WorldItemPickupResult = {
344
353
  status: "rejected";
345
354
  reason: string;
346
355
  };
356
+ /**
357
+ * `ctx.scene.worldItem`: items lying in the world — spawn, list, find the nearest, and pick up into a player's bag.
358
+ *
359
+ * @capability world-item-pickup pick up dropped items: ctx.scene.worldItem nearestInRadius → pickup into the player's inventory
360
+ */
347
361
  export interface SceneWorldItemContext {
348
362
  spawn(input: WorldItemSpawnInput): WorldItemRecord;
349
363
  get(instanceId: string): WorldItemRecord | null;
@@ -390,6 +404,11 @@ export interface GameContextItemUse {
390
404
  export interface GameContextWorld {
391
405
  ground: TerrainField;
392
406
  groundHeightAt(x: number, z: number): number;
407
+ /**
408
+ * Static collision for world geometry that is not a scene object. `environment({ structures })`
409
+ * fills the `environment:structures` layer; `syncAuthoredSolids` fills one layer per studio object.
410
+ */
411
+ solids: WorldSolids;
393
412
  }
394
413
  export interface GameContextCards {
395
414
  /** Lazily creates (on first call, `config` required) or returns the existing notify-wrapped pile for `id`. */
@@ -420,14 +439,19 @@ export interface GameAudio {
420
439
  }): void;
421
440
  /**
422
441
  * Live-update retained loop `id` via `audio.loopSet`: `rate` re-pitches it (1 = authored pitch, clamped
423
- * 0.25–4 by the shell), `gain` rescales volume (0–1), `at` repositions its emitter. Cheap to call every
424
- * tick (~60 Hz) — the shell ramps rate/gain over ~20 ms to avoid zipper noise. A no-op when `id` is not a
425
- * live loop (an update may race a stop) (#1051).
442
+ * 0.25–4 by the shell), `gain` rescales volume (0–1), `at` repositions its emitter. `lowpass`/`highpass`
443
+ * set filter cutoffs in Hz (muffle an engine off-throttle or behind a wall); `velocity` is the emitter's
444
+ * world velocity, which pitches the loop by doppler when its sound declares `doppler`. Cheap to call every
445
+ * tick (~60 Hz) — the shell ramps rate/gain/cutoffs over ~20 ms to avoid zipper noise. A no-op when `id`
446
+ * is not a live loop (an update may race a stop) (#1051).
426
447
  */
427
448
  setLoop(id: string, params: {
428
449
  rate?: number;
429
450
  gain?: number;
430
451
  at?: readonly [number, number, number];
452
+ velocity?: readonly [number, number, number];
453
+ lowpass?: number;
454
+ highpass?: number;
431
455
  }): void;
432
456
  /** Stop and dispose retained loop `id` (emits `audio.loopStop`); an unknown `id` is ignored (#1051). */
433
457
  stopLoop(id: string): void;
@@ -442,6 +466,8 @@ export interface GameContext {
442
466
  raycastAll(input: SceneRaycastInput): readonly SceneRaycastHit[];
443
467
  };
444
468
  world: GameContextWorld;
469
+ /** Authored weather, shelter, accumulation and fire sampled from ctx.time. */
470
+ environment: AuthoredSimulationRuntime;
445
471
  /**
446
472
  * Deterministic `[0,1)` stream for this world — loot, AI, combat rolls, and any sim path that
447
473
  * needs randomness. Same seed → same sequence. Never fall back to `Math.random` in systems;
@@ -555,6 +581,10 @@ export interface GameContext {
555
581
  snapshot(viewer?: SnapshotViewer): WorldSnapshot;
556
582
  /** Apply a {@link WorldSnapshot} from an authoritative host, hydrating each subsystem key present in it. */
557
583
  hydrate(snapshot: WorldSnapshot): void;
584
+ /** Detached authoritative state, including save-only modules. Use for persistence, never client replication; available without `persist` or a save backend. */
585
+ state(): WorldSnapshot;
586
+ /** Restore authoritative state after initialization. Missing keys retain their initialized values, including in legacy replication-only saves. */
587
+ restore(state: WorldSnapshot): void;
558
588
  /** Aggregate world-dirty version summed across replicated modules; the host replicator skips an unchanged commit. @internal */
559
589
  replicationVersion(): number;
560
590
  /** True when a {@link GameContextOptions.replication} policy makes {@link snapshot} viewer-dependent (private/AOI projection is active). @internal */
@@ -1,5 +1,5 @@
1
1
  import type { SaveConfig } from "./save.js";
2
- import type { CommandDef, CommandScope } from "./commandRunner.js";
2
+ import type { CommandAccess, CommandDef, CommandScope } from "./commandRunner.js";
3
3
  import { runCommand } from "./commandRunner.js";
4
4
  import { type GameRuntimeSnapshot, type RuntimeChunkRow, type RuntimePlayerRow, type RuntimeProfileRow, type RuntimeServerRow } from "./snapshot.js";
5
5
  export type ServerLoopHooks = {
@@ -69,6 +69,8 @@ export type GameRuntime = {
69
69
  * and nothing else. Missing scopes load only the actor and no chunks; `{}` explicitly requests all state.
70
70
  */
71
71
  commandScope: (commandName: string, input: unknown, actorUserId: string) => CommandScope | undefined;
72
+ /** Who may send a command, or `undefined` when the runtime has no command by that name. */
73
+ commandAccess: (commandName: string) => CommandAccess | undefined;
72
74
  /** What a join has to hydrate. `undefined` means the whole world, as when `onNewPlayer` is unscoped. */
73
75
  joinScope: (userId: string, isNew: boolean) => CommandScope | undefined;
74
76
  /** `nowMs` is the host wall clock at the start of the tick, in ms; defaults to `Date.now()`. */
@@ -38,6 +38,10 @@ export function createGameRuntime(definition) {
38
38
  commandScope(commandName, input, actorUserId) {
39
39
  return resolveCommandScope(definition.commands, commandName, input, actorUserId);
40
40
  },
41
+ commandAccess(commandName) {
42
+ const command = definition.commands[commandName];
43
+ return command === undefined ? undefined : (command.access ?? "client");
44
+ },
41
45
  joinScope(userId, isNew) {
42
46
  if (!loop?.onNewPlayer)
43
47
  return { players: [userId], chunkKeys: [] };
@@ -2,10 +2,12 @@ import type { GameDefinition, GameLoop } from "../game/defineGame.js";
2
2
  import type { PointerAxisState } from "../input/pointerAxis.js";
3
3
  import type { ModelAssetRef } from "../scene/assetCatalog.js";
4
4
  import { type GameContext, type GameContextContent, type GameContextModels } from "./gameContext.js";
5
- import type { InputSnapshot } from "./inputSnapshot.js";
5
+ import type { InputFrame, InputSnapshot } from "./inputSnapshot.js";
6
6
  import { type SimSnapshot } from "./simSnapshot.js";
7
7
  /** One step's worth of player intent handed to {@link HeadlessRunner.step} — the held-action set and pointer state the shell would otherwise publish from the browser each frame. */
8
8
  export interface HeadlessInput {
9
+ /** Discrete presses on this recorded step, including taps released before its held-state sample. */
10
+ presses?: InputFrame["presses"];
9
11
  /** Replaces the held-action set for this step; edge detection (`justPressed`/`justReleased`) rolls the previous set forward. Omit to keep the last published set. */
10
12
  held?: readonly string[];
11
13
  /** Replaces the normalized pointer state; omit to leave the last pointer unchanged (pass `null` to clear it). */
@@ -18,15 +18,14 @@ export function createHeadlessRunner(options) {
18
18
  loop?.onNewPlayer?.(ctx);
19
19
  const simSnapshot = createContextSimSnapshot(ctx);
20
20
  const tuning = options.playerMovement === true
21
- ? resolvePlayerMovementTuning({
22
- physics: definition.physics,
23
- world: definition.world,
24
- movement: options.movement,
25
- })
21
+ ? { ...resolvePlayerMovementTuning({
22
+ physics: definition.physics,
23
+ world: definition.world,
24
+ movement: options.movement,
25
+ }), authoritativeStep: true }
26
26
  : null;
27
27
  function publishInput(input) {
28
- if (input.held !== undefined)
29
- ctx.input.publish(input.held);
28
+ ctx.input.publish(input.held ?? ctx.input.held(), { pressed: input.presses?.map(press => press.action) ?? [] });
30
29
  if (input.pointer !== undefined)
31
30
  ctx.input.publishPointer(input.pointer);
32
31
  }
@@ -54,12 +53,12 @@ export function createHeadlessRunner(options) {
54
53
  publishInput(input);
55
54
  const dt = Math.min(dtSeconds, maxStep);
56
55
  let total = 0;
57
- ctx.sim.advance(dt, (stepDt) => {
58
- const gameDt = ctx.time.advance(stepDt);
56
+ ctx.sim.advance(dt, (stepDt, _tick, gameDt) => {
57
+ ctx.input.beginStep();
59
58
  total += gameDt;
60
59
  ctx.sim.runStages("beforeMovement", stepDt);
61
- if (tuning !== null) {
62
- stepPlayerMovement(ctx, player.userId, { held: ctx.input.held(), pointer: ctx.input.pointer() }, stepDt, tuning, options.heading, options.pitch);
60
+ if (tuning !== null && gameDt > 0) {
61
+ stepPlayerMovement(ctx, player.userId, { held: ctx.input.held(), pointer: ctx.input.pointer() }, gameDt, tuning, options.heading, options.pitch);
63
62
  }
64
63
  ctx.sim.runStages("afterMovement", stepDt);
65
64
  loop?.onTick?.(ctx, gameDt);
@@ -71,6 +70,7 @@ export function createHeadlessRunner(options) {
71
70
  snapshot: () => simSnapshot.capture(),
72
71
  restore(snapshot) {
73
72
  simSnapshot.restore(snapshot);
73
+ ctx.input.publish([], { reset: true });
74
74
  },
75
75
  tick: () => ctx.sim.tick(),
76
76
  };
@@ -21,8 +21,17 @@ export interface HostedGameRunnerOptions<TAssetRef extends ModelAssetRef, TMulti
21
21
  * snapshot overlays the world state it seeded. Omit for a long-lived stateful host (ws) that keeps one runner.
22
22
  */
23
23
  restore?: WorldSnapshot;
24
+ /** Persisted replication cursor; restored hosts continue above it. */
25
+ revision?: number;
24
26
  /** Render-model lookup for collider auto-fit — pass the same lookup the shell derives from `entityModels`/`objectModels` so host and clients resolve identical hitboxes. */
25
27
  models?: GameContextModels;
28
+ /** Process-local reconnect protection. Departed admission marks expire after 5 minutes or the oldest is evicted above 256 users; zero disables retention. Live members and staged replay ticks are never evicted. */
29
+ inputRetention?: {
30
+ maxDepartedUsers?: number;
31
+ departedTtlMs?: number;
32
+ /** Wall-clock milliseconds; injectable for deterministic hosts and tests. */
33
+ nowMs?: () => number;
34
+ };
26
35
  }
27
36
  /**
28
37
  * The GameContext-loop equivalent of the pure-reducer `createGameHost`: one authoritative `createGameContext`
@@ -36,8 +45,18 @@ export interface HostedGameRunner {
36
45
  /** Re-attach an already-joined member on a reconstructed runner — restores membership and `ctx.game.players` without re-firing `onNewPlayer`. Stateless hosts call it once per persisted member after `restore`. */
37
46
  resume(userId: string): void;
38
47
  leave(userId: string): void;
39
- /** Record a client's latest input frame — stashed for {@link heldInput} and mirrored onto `ctx.game.players` so the movement seam reads each player's intent in `onTick`. */
48
+ /** Record a joined/resumed client's latest input frame; nonmember input is ignored. Stashed for {@link heldInput} and sampled onto `ctx.game.players` in `onTick`. */
40
49
  input(userId: string, frame: InputFrame): void;
50
+ /** Counts actual retained input storage for host health checks; elapsed recorder history is excluded and staged future replay ticks remain. */
51
+ inputStats(): {
52
+ members: number;
53
+ recorders: number;
54
+ recordedFrames: number;
55
+ latestInputs: number;
56
+ wireSequences: number;
57
+ pressSequences: number;
58
+ departedUsers: number;
59
+ };
41
60
  heldInput(userId: string): InputFrame | null;
42
61
  command(userId: string, name: string, input: unknown): CommandResult<GameContext>;
43
62
  tick(dt: number): number;
@@ -45,6 +64,10 @@ export interface HostedGameRunner {
45
64
  revision(): number;
46
65
  /** The full world baseline — projected to only what `viewer` may see when the context carries a replication policy, else the whole world. */
47
66
  snapshot(viewer?: SnapshotViewer): WorldSnapshot;
67
+ /** Complete detached authoritative state for persistence, including save-only modules. */
68
+ state(): WorldSnapshot;
69
+ /** Commit replication without advancing simulation or running game ticks. */
70
+ commit(): number;
48
71
  /** True when {@link snapshot} is viewer-dependent (a replication policy projects private/AOI state); a host must then serve each viewer its own frame. */
49
72
  projectsViewers(): boolean;
50
73
  members(): readonly string[];