@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,9 +1,145 @@
1
+ const RIG_KINDS = ["orbit", "first", "topDown", "rts", "shoulder", "lockOn", "chase", "observer", "turntable", "sideScroll", "inspection", "none"];
2
+ const number = (value) => typeof value === "number" && Number.isFinite(value);
3
+ const response = (value) => number(value) || value === Number.POSITIVE_INFINITY;
4
+ const boolean = (value) => typeof value === "boolean";
5
+ const string = (value) => typeof value === "string";
6
+ const oneOf = (...values) => (value) => typeof value === "string" && values.includes(value);
7
+ const shape = (fields, required = []) => (value) => {
8
+ if (value === null || typeof value !== "object" || Array.isArray(value))
9
+ return false;
10
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
11
+ return false;
12
+ const row = value;
13
+ return required.every((key) => row[key] !== undefined) && Object.entries(row).every(([key, field]) => Object.hasOwn(fields, key) && (field === undefined || fields[key](field)));
14
+ };
15
+ function numbers(...keys) {
16
+ return Object.fromEntries(keys.map((key) => [key, number]));
17
+ }
18
+ const point = shape(numbers("x", "y", "z"));
19
+ const target3d = shape(numbers("x", "y", "z"), ["x", "y", "z"]);
20
+ const topDownFields = {
21
+ ...numbers("height", "pitch", "yaw", "followSmoothing"), targetOffset: point,
22
+ zoom: shape(numbers("min", "max", "speed")),
23
+ };
24
+ const chaseFields = (allowInfinity) => ({
25
+ ...numbers("distance", "height", "lookHeight", "springDamping", "shakePerSpeed"),
26
+ headingSource: oneOf("body", "input"), view: oneOf("chase", "cockpit", "hood", "rear"),
27
+ yawResponse: allowInfinity ? response : number,
28
+ fov: shape({ ...numbers("base", "max", "speedForMax"), response: allowInfinity ? response : number }),
29
+ distanceBySpeed: shape(numbers("extra", "speedForMax"), ["extra"]),
30
+ pitchFollow: shape(numbers("blend", "max", "response")), fovKick: shape(numbers("decay", "max")),
31
+ lookBackAction: string,
32
+ collision: (value) => boolean(value) || shape(numbers("radius", "minDistance"))(value),
33
+ lead: shape(numbers("time", "max"), ["time"]), bank: shape(numbers("perYawRate", "max", "damping"), ["perYawRate"]),
34
+ velocityYaw: shape(numbers("blend", "minSpeed", "response")),
35
+ });
36
+ const cinematic = shape({
37
+ loop: boolean,
38
+ keyframes: (value) => Array.isArray(value) && value.every(shape({
39
+ position: target3d, lookAt: target3d, ...numbers("fov", "duration"), ease: oneOf("linear", "smooth"),
40
+ }, ["position", "lookAt"])),
41
+ }, ["keyframes"]);
42
+ const rigConfig = shape({
43
+ ...numbers("transitionSeconds", "minDistance", "maxDistance", "targetHeight", "initialDistance", "initialHeight", "initialYaw", "initialPitch", "rotateSpeed", "zoomSpeed", "dampingFactor", "targetSmoothing", "dragTargetSmoothing", "distanceSmoothing", "minPolarAngle", "maxPolarAngle"),
44
+ perspective: oneOf("third", "first"), targetOffset: point, followLock: boolean, followEnabled: boolean,
45
+ followEntityId: (value) => value === null || string(value),
46
+ pitchClamp: (value) => Array.isArray(value) && value.length === 2 && value.every(number),
47
+ collision: shape({ enabled: boolean, ...numbers("padding", "minTargetDistance") }),
48
+ firstPerson: shape({ ...numbers("eyeHeight", "sensitivity", "maxPitch"), reticle: boolean, viewmodel: boolean }),
49
+ topDown: shape(topDownFields),
50
+ rts: shape({ ...topDownFields, ...numbers("panSpeed", "rotateSpeed"), pan: boolean,
51
+ edgeScroll: (value) => boolean(value) || shape(numbers("margin", "speed"))(value),
52
+ bounds: shape(numbers("minX", "maxX", "minZ", "maxZ")), start: shape(numbers("x", "z")),
53
+ }),
54
+ shoulder: shape({ ...numbers("shoulderOffset", "heightOffset", "distance", "adsTransitionSpeed", "sensitivity", "fov"),
55
+ side: oneOf("left", "right"), ads: shape(numbers("distance", "shoulderOffset", "fov", "heightOffset")), reticleOffset: shape(numbers("x", "y")),
56
+ }),
57
+ lockOn: shape({ ...numbers("distance", "height", "lookHeight", "framingBias", "yawSmoothing"), targetEntityId: string }),
58
+ chase: shape({ ...chaseFields(true), seatOffsets: shape({ cockpit: point, hood: point, rear: point }) }),
59
+ observer: shape({ ...numbers("distance", "height", "lookHeight", "orbitSpeed", "startAngle", "fov"),
60
+ bind: (value) => shape({ kind: oneOf("entity"), entityId: string }, ["kind", "entityId"])(value) ||
61
+ shape({ kind: oneOf("point"), position: target3d }, ["kind", "position"])(value),
62
+ }),
63
+ turntable: shape({ ...numbers("distance", "height", "lookHeight", "orbitSpeed", "startAngle", "fov"), target: target3d }),
64
+ sideScroll: shape({ ...numbers("distance", "height", "lookHeight", "followSmoothing", "fov"), axis: oneOf("x", "z") }),
65
+ inspection: shape({ ...numbers("initialDistance", "minDistance", "maxDistance", "minPolarAngle", "maxPolarAngle", "rotateSpeed", "zoomSpeed", "dampingFactor"),
66
+ anchor: oneOf("target", "cursor", "center"), target: point, initialPosition: point, pan: boolean,
67
+ }),
68
+ shake: shape(numbers("maxOffset", "maxRoll", "decayPerSecond", "exponent", "frequency")), cinematic,
69
+ });
70
+ /** @internal */
71
+ export function validateCameraRig(kind, config = {}) {
72
+ if (!RIG_KINDS.includes(kind) || !rigConfig(config))
73
+ throw new Error("invalid camera rig override");
74
+ }
75
+ /** @internal */
76
+ export function validateChaseCameraTuning(tuning) {
77
+ if (tuning !== null && !shape(chaseFields(true))(tuning))
78
+ throw new Error("invalid chase camera tuning");
79
+ }
80
+ function reviveNumbers(raw) {
81
+ if (Array.isArray(raw))
82
+ return raw.map(reviveNumbers);
83
+ if (raw === null || typeof raw !== "object")
84
+ return raw;
85
+ if (Object.getPrototypeOf(raw) !== Object.prototype && Object.getPrototypeOf(raw) !== null)
86
+ return raw;
87
+ if (Object.keys(raw).length === 1 && "$cameraNumber" in raw && raw.$cameraNumber === "Infinity")
88
+ return Infinity;
89
+ return Object.fromEntries(Object.entries(raw).map(([key, value]) => [key, reviveNumbers(value)]));
90
+ }
91
+ function decodeCameraState(raw) {
92
+ try {
93
+ raw = reviveNumbers(raw);
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ if (raw === null || typeof raw !== "object")
99
+ return null;
100
+ const state = raw;
101
+ if (state.followEntityId !== undefined && state.followEntityId !== null && typeof state.followEntityId !== "string")
102
+ return null;
103
+ if (!("rig" in state) || !("cinematic" in state) || !("chase" in state))
104
+ return null;
105
+ try {
106
+ if (state.rig !== null) {
107
+ if (!shape({ kind: oneOf(...RIG_KINDS), config: rigConfig }, ["kind", "config"])(state.rig))
108
+ return null;
109
+ }
110
+ if (state.cinematic !== null && !cinematic(state.cinematic))
111
+ return null;
112
+ if (state.chase !== null && !shape(chaseFields(true))(state.chase))
113
+ return null;
114
+ return structuredClone(state);
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ }
120
+ /** Default order {@link nextChaseView} cycles through. */
121
+ export const CHASE_VIEWS = ["chase", "hood", "cockpit"];
122
+ /**
123
+ * The view after `current` in `views`, wrapping at the end; a view missing from the list starts the cycle over.
124
+ * Pair with `setChaseTuning` to bind a "change camera" key:
125
+ * `ctx.camera.setChaseTuning({ ...ctx.camera.chaseTuning(), view: nextChaseView(view) })`.
126
+ *
127
+ * @capability chase-view-cycle cycle a vehicle camera between chase, hood and cockpit views at runtime
128
+ */
129
+ export function nextChaseView(current, views = CHASE_VIEWS) {
130
+ if (views.length === 0)
131
+ return current;
132
+ const index = views.indexOf(current);
133
+ return views[(index + 1) % views.length];
134
+ }
1
135
  /** @internal */
2
136
  export function createCameraDirector() {
3
137
  const listeners = new Set();
4
138
  let followEntity = undefined;
5
139
  let cinematicConfig = null;
6
140
  let chaseTuningPatch = null;
141
+ let rigOverride = null;
142
+ let pendingFovKick = 0;
7
143
  function notify() {
8
144
  for (const listener of listeners)
9
145
  listener();
@@ -14,6 +150,13 @@ export function createCameraDirector() {
14
150
  notify();
15
151
  },
16
152
  followedEntityId: () => followEntity,
153
+ setRig(kind, config = {}) {
154
+ if (kind !== null)
155
+ validateCameraRig(kind, config);
156
+ rigOverride = kind === null ? null : { kind, config: structuredClone(config) };
157
+ notify();
158
+ },
159
+ rig: () => rigOverride,
17
160
  setCinematic(config) {
18
161
  cinematicConfig = config;
19
162
  notify();
@@ -24,9 +167,53 @@ export function createCameraDirector() {
24
167
  notify();
25
168
  },
26
169
  chaseTuning: () => chaseTuningPatch,
170
+ kickFov(degrees) {
171
+ if (Number.isFinite(degrees))
172
+ pendingFovKick += degrees;
173
+ },
174
+ takeFovKick() {
175
+ const kick = pendingFovKick;
176
+ pendingFovKick = 0;
177
+ return kick;
178
+ },
179
+ snapshot: () => JSON.parse(JSON.stringify({ ...(followEntity === undefined ? {} : { followEntityId: followEntity }), rig: rigOverride, cinematic: cinematicConfig, chase: chaseTuningPatch }, (_key, value) => {
180
+ if (value === Infinity)
181
+ return { $cameraNumber: "Infinity" };
182
+ if (typeof value === "number" && !Number.isFinite(value))
183
+ throw new Error("invalid camera number");
184
+ return value;
185
+ })),
186
+ restore(state) {
187
+ const decoded = decodeCameraState(state);
188
+ if (decoded === null)
189
+ throw new Error("invalid camera director state");
190
+ followEntity = decoded.followEntityId;
191
+ rigOverride = decoded.rig;
192
+ cinematicConfig = decoded.cinematic;
193
+ chaseTuningPatch = decoded.chase;
194
+ pendingFovKick = 0;
195
+ notify();
196
+ },
197
+ reset() {
198
+ followEntity = undefined;
199
+ rigOverride = null;
200
+ cinematicConfig = null;
201
+ chaseTuningPatch = null;
202
+ pendingFovKick = 0;
203
+ notify();
204
+ },
27
205
  subscribe(listener) {
28
206
  listeners.add(listener);
29
207
  return () => listeners.delete(listener);
30
208
  },
31
209
  };
32
210
  }
211
+ /** Local camera policy is saved, never broadcast to another player's presentation. @internal */
212
+ export function installCameraPersistence(ctx) {
213
+ ctx.game.registerSave?.({
214
+ key: "camera",
215
+ snapshot: ctx.camera.snapshot,
216
+ hydrate: ctx.camera.restore,
217
+ decode: decodeCameraState,
218
+ });
219
+ }
@@ -0,0 +1,14 @@
1
+ import type { CommandResult } from "../commands/commandRegistry.js";
2
+ import type { GameContext } from "./gameContext.js";
3
+ import type { LiveGameBackend } from "./transport.js";
4
+ /** A UI command completes locally or after its authoritative host acknowledges it. */
5
+ export type DispatchedCommandResult = CommandResult<GameContext> | Promise<CommandResult<GameContext>>;
6
+ type Route = {
7
+ transport: LiveGameBackend["transport"];
8
+ serverId: () => string | null;
9
+ };
10
+ /** Bind UI command dispatch to the host for this context. A missing join rejects; it never mutates the local replica. */
11
+ export declare function bindCommandTransport(ctx: GameContext, route: Route): () => void;
12
+ /** Dispatch a player's command through the bound host, or locally for an unbound offline context. Await the result before showing success. */
13
+ export declare function dispatchCommand(ctx: GameContext, name: string, input: unknown): DispatchedCommandResult;
14
+ export {};
@@ -0,0 +1,19 @@
1
+ const routes = new WeakMap();
2
+ /** Bind UI command dispatch to the host for this context. A missing join rejects; it never mutates the local replica. */
3
+ export function bindCommandTransport(ctx, route) {
4
+ routes.set(ctx, route);
5
+ return () => { if (routes.get(ctx) === route)
6
+ routes.delete(ctx); };
7
+ }
8
+ /** Dispatch a player's command through the bound host, or locally for an unbound offline context. Await the result before showing success. */
9
+ export function dispatchCommand(ctx, name, input) {
10
+ const route = routes.get(ctx);
11
+ if (route === undefined)
12
+ return ctx.game.commands.run(name, input);
13
+ const serverId = route.serverId();
14
+ if (serverId === null)
15
+ return { status: "rejected", reason: "not-joined" };
16
+ return route.transport.runCommand({ serverId, command: name, input }).then((result) => result.ok
17
+ ? { status: "applied", state: ctx }
18
+ : { status: "rejected", reason: result.reason }, (error) => ({ status: "rejected", reason: error instanceof Error ? error.message : "Command transport failed" }));
19
+ }
@@ -13,3 +13,40 @@ export declare function readQuantity(value: unknown, options?: {
13
13
  min?: number;
14
14
  max?: number;
15
15
  }): QuantityResult;
16
+ /** Narrow an untrusted value to a plain object, rejecting arrays and null. */
17
+ export declare function isInputRecord(value: unknown): value is Record<string, unknown>;
18
+ /** Read a non-empty string of at most `maxLength` characters, or `null`. */
19
+ export declare function readInputString(value: unknown, options?: {
20
+ maxLength?: number;
21
+ }): string | null;
22
+ /**
23
+ * Read a finite number inside `[min, max]`, or `null`. `NaN` and `±Infinity` are always rejected, so a
24
+ * coordinate can never drive an unbounded loop; `integer` also rejects fractions instead of flooring them.
25
+ * @capability command-number validate finite, bounded numbers and integers at command boundaries
26
+ */
27
+ export declare function readInputNumber(value: unknown, options?: {
28
+ min?: number;
29
+ max?: number;
30
+ integer?: boolean;
31
+ }): number | null;
32
+ /** Read one of a fixed set of values, or `null`. */
33
+ export declare function readInputOneOf<const T extends string | number>(value: unknown, allowed: readonly T[]): T | null;
34
+ /** Read an `{ x, y, z }` point whose axes are finite and within `maxAbs`, or `null`. */
35
+ export declare function readInputPoint3(value: unknown, options?: {
36
+ maxAbs?: number;
37
+ }): {
38
+ x: number;
39
+ y: number;
40
+ z: number;
41
+ } | null;
42
+ /** Read an `{ x, z }` ground point whose axes are finite and within `maxAbs`, or `null`. */
43
+ export declare function readInputPoint2(value: unknown, options?: {
44
+ maxAbs?: number;
45
+ }): {
46
+ x: number;
47
+ z: number;
48
+ } | null;
49
+ /** Read an array of at most `maxItems` entries, each accepted by `readItem`; any rejected entry rejects the whole array. */
50
+ export declare function readInputArray<T>(value: unknown, readItem: (item: unknown) => T | null, options?: {
51
+ maxItems?: number;
52
+ }): T[] | null;
@@ -11,3 +11,61 @@ export function readQuantity(value, options = {}) {
11
11
  ? { ok: true, quantity: value }
12
12
  : { ok: false, reason: "invalid-quantity" };
13
13
  }
14
+ /** Narrow an untrusted value to a plain object, rejecting arrays and null. */
15
+ export function isInputRecord(value) {
16
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17
+ }
18
+ /** Read a non-empty string of at most `maxLength` characters, or `null`. */
19
+ export function readInputString(value, options = {}) {
20
+ return typeof value === "string" && value.length > 0 && value.length <= (options.maxLength ?? 256) ? value : null;
21
+ }
22
+ /**
23
+ * Read a finite number inside `[min, max]`, or `null`. `NaN` and `±Infinity` are always rejected, so a
24
+ * coordinate can never drive an unbounded loop; `integer` also rejects fractions instead of flooring them.
25
+ * @capability command-number validate finite, bounded numbers and integers at command boundaries
26
+ */
27
+ export function readInputNumber(value, options = {}) {
28
+ if (typeof value !== "number" || !Number.isFinite(value))
29
+ return null;
30
+ if (options.integer === true && !Number.isSafeInteger(value))
31
+ return null;
32
+ if (value < (options.min ?? -Number.MAX_VALUE) || value > (options.max ?? Number.MAX_VALUE))
33
+ return null;
34
+ return value;
35
+ }
36
+ /** Read one of a fixed set of values, or `null`. */
37
+ export function readInputOneOf(value, allowed) {
38
+ return allowed.includes(value) ? value : null;
39
+ }
40
+ /** Read an `{ x, y, z }` point whose axes are finite and within `maxAbs`, or `null`. */
41
+ export function readInputPoint3(value, options = {}) {
42
+ if (!isInputRecord(value))
43
+ return null;
44
+ const bound = { min: -(options.maxAbs ?? Number.MAX_VALUE), max: options.maxAbs ?? Number.MAX_VALUE };
45
+ const x = readInputNumber(value.x, bound);
46
+ const y = readInputNumber(value.y, bound);
47
+ const z = readInputNumber(value.z, bound);
48
+ return x === null || y === null || z === null ? null : { x, y, z };
49
+ }
50
+ /** Read an `{ x, z }` ground point whose axes are finite and within `maxAbs`, or `null`. */
51
+ export function readInputPoint2(value, options = {}) {
52
+ if (!isInputRecord(value))
53
+ return null;
54
+ const bound = { min: -(options.maxAbs ?? Number.MAX_VALUE), max: options.maxAbs ?? Number.MAX_VALUE };
55
+ const x = readInputNumber(value.x, bound);
56
+ const z = readInputNumber(value.z, bound);
57
+ return x === null || z === null ? null : { x, z };
58
+ }
59
+ /** Read an array of at most `maxItems` entries, each accepted by `readItem`; any rejected entry rejects the whole array. */
60
+ export function readInputArray(value, readItem, options = {}) {
61
+ if (!Array.isArray(value) || value.length > (options.maxItems ?? 256))
62
+ return null;
63
+ const items = [];
64
+ for (const entry of value) {
65
+ const item = readItem(entry);
66
+ if (item === null)
67
+ return null;
68
+ items.push(item);
69
+ }
70
+ return items;
71
+ }
@@ -16,7 +16,26 @@ export type CommandScope = {
16
16
  export type CommandScopeDefinition = Omit<CommandScope, "players"> & {
17
17
  players?: readonly string[] | "actor";
18
18
  };
19
+ /** Who may send a command: any client through the public transport, or only trusted host code. */
20
+ export type CommandAccess = "client" | "server";
21
+ /** Reason every host returns when a command's `parse` rejects its input. */
22
+ export declare const MALFORMED_COMMAND_INPUT_REASON = "Malformed command input";
23
+ /** Reason a public transport returns for a {@link CommandDef} marked `access: "server"`. */
24
+ export declare const SERVER_ONLY_COMMAND_REASON = "Command is server-only";
19
25
  export type CommandDef<TInput = unknown> = {
26
+ /**
27
+ * `"server"` when `input` carries facts only the host may decide (unlock state, prices, multipliers):
28
+ * public transports then refuse it and only trusted host calls such as `helpers.runCommand` run it.
29
+ * Defaults to `"client"`, so every field of `input` must be validated as untrusted.
30
+ */
31
+ access?: CommandAccess;
32
+ /**
33
+ * Turns the untrusted wire value into the command's input, or `null` to refuse it with
34
+ * {@link MALFORMED_COMMAND_INPUT_REASON} before `scope`, `validate` or `apply` run. Those then only ever see
35
+ * parsed input, so a field checked here can't be skipped in one of them. Build it from the readers in
36
+ * `runtime/commandInput`; declare typed commands with {@link defineCommand}.
37
+ */
38
+ parse?: (input: unknown) => TInput | null;
20
39
  /**
21
40
  * What this command reads and writes, derived from its own input. A host that hydrates through a
22
41
  * scope loads only this slice instead of the whole world, and refuses the command if `apply` then
@@ -55,3 +74,11 @@ export type RunCommandResult = {
55
74
  };
56
75
  /** @internal */
57
76
  export declare function runCommand<TInput>(snapshot: import("./snapshot.js").GameRuntimeSnapshot, commands: Record<string, CommandDef<TInput>>, commandName: string, input: TInput, actorUserId: string, nowMs?: number): RunCommandResult;
77
+ /**
78
+ * Declare a command with a typed input: `parse` narrows the wire value once, and `scope`, `validate` and
79
+ * `apply` receive the parsed type. Returns the untyped {@link CommandDef} a runtime's command table holds.
80
+ * @capability command-parse parse untrusted command input once so every command stage sees typed, bounded values
81
+ */
82
+ export declare function defineCommand<TInput>(definition: CommandDef<TInput> & {
83
+ parse: (input: unknown) => TInput | null;
84
+ }): CommandDef;
@@ -1,10 +1,23 @@
1
+ /** Reason every host returns when a command's `parse` rejects its input. */
2
+ export const MALFORMED_COMMAND_INPUT_REASON = "Malformed command input";
3
+ /** Reason a public transport returns for a {@link CommandDef} marked `access: "server"`. */
4
+ export const SERVER_ONLY_COMMAND_REASON = "Command is server-only";
1
5
  /**
2
6
  * The resolved scope, defaulting to the actor and no chunks.
3
7
  * Evaluate it before hydration: the actor and the command's own input are both known by then.
4
8
  * @internal
5
9
  */
6
10
  export function resolveCommandScope(commands, commandName, input, actorUserId) {
7
- const scope = commands[commandName]?.scope?.(input, actorUserId) ?? { players: "actor", chunkKeys: [] };
11
+ const command = commands[commandName];
12
+ let declared;
13
+ if (command?.parse === undefined) {
14
+ declared = command?.scope?.(input, actorUserId);
15
+ }
16
+ else {
17
+ const parsed = command.parse(input);
18
+ declared = parsed === null ? undefined : command.scope?.(parsed, actorUserId);
19
+ }
20
+ const scope = declared ?? { players: "actor", chunkKeys: [] };
8
21
  const { players, ...rest } = scope;
9
22
  return players === undefined ? rest : { ...rest, players: players === "actor" ? [actorUserId] : players };
10
23
  }
@@ -42,11 +55,18 @@ export function runCommand(snapshot, commands, commandName, input, actorUserId,
42
55
  if (!command) {
43
56
  return { ok: false, reason: `Unknown command: ${commandName}` };
44
57
  }
45
- const validationError = command.validate(snapshot, input, actorUserId, nowMs);
58
+ let parsed = input;
59
+ if (command.parse !== undefined) {
60
+ const result = command.parse(input);
61
+ if (result === null)
62
+ return { ok: false, reason: MALFORMED_COMMAND_INPUT_REASON };
63
+ parsed = result;
64
+ }
65
+ const validationError = command.validate(snapshot, parsed, actorUserId, nowMs);
46
66
  if (validationError) {
47
67
  return { ok: false, reason: validationError.reason };
48
68
  }
49
- const next = command.apply(snapshot, input, actorUserId, nowMs);
69
+ const next = command.apply(snapshot, parsed, actorUserId, nowMs);
50
70
  return {
51
71
  ok: true,
52
72
  snapshot: {
@@ -62,3 +82,11 @@ export function runCommand(snapshot, commands, commandName, input, actorUserId,
62
82
  },
63
83
  };
64
84
  }
85
+ /**
86
+ * Declare a command with a typed input: `parse` narrows the wire value once, and `scope`, `validate` and
87
+ * `apply` receive the parsed type. Returns the untyped {@link CommandDef} a runtime's command table holds.
88
+ * @capability command-parse parse untrusted command input once so every command stage sees typed, bounded values
89
+ */
90
+ export function defineCommand(definition) {
91
+ return definition;
92
+ }
@@ -1,11 +1,11 @@
1
1
  import { type DeathSystem } from "../../combat/death.js";
2
2
  import { type CombatSpatialDeps, type EffectSystem } from "../../combat/effects.js";
3
- import { type ProjectileSystem } from "../../combat/projectiles.js";
3
+ import { type ProjectileSystem, type ProjectileTravelDeps } from "../../combat/projectiles.js";
4
4
  import type { PhysicsConfig } from "../../game/defineGame.js";
5
5
  import type { GameEvents } from "../../game/events.js";
6
6
  import { type VfxInstanceStore } from "../../game/vfxInstance.js";
7
7
  import type { LootRegistry } from "../../game/lootTable.js";
8
- import type { EntityColliderSet } from "../../scene/colliders.js";
8
+ import { type EntityColliderSet } from "../../scene/colliders.js";
9
9
  import type { EntityStore } from "../../scene/entityStore.js";
10
10
  import type { ObjectStore } from "../../scene/objectStore.js";
11
11
  import type { SceneRaycastApi } from "../../scene/sceneRaycast.js";
@@ -34,9 +34,16 @@ export interface CombatSubsystemDeps {
34
34
  lootRegistry: LootRegistry;
35
35
  spawnWorldItem: (input: WorldItemSpawnInput) => WorldItemRecord;
36
36
  despawnEntity: (instanceId: string) => boolean;
37
- runCommand: (name: string, args: unknown) => void;
38
- localUserId: string;
37
+ runCommand: (name: string, args: unknown, actorUserId?: string) => void;
38
+ userIdOf: (instanceId: string) => string | undefined;
39
+ rng: () => number;
39
40
  physics?: PhysicsConfig;
41
+ projectileTravel?: Pick<ProjectileTravelDeps, "acceleration" | "sweep" | "maxActive" | "maxRetained"> & {
42
+ /** Bounded collision source; omit for the context's entities. */
43
+ targets?(): readonly string[];
44
+ /** Reject an oversized collision source rather than silently missing targets. Default 2048. */
45
+ maxTargets?: number;
46
+ };
40
47
  }
41
48
  /** @internal Effects, projectiles, death, and combat presentation surface. */
42
49
  export interface CombatSubsystem {
@@ -46,6 +53,7 @@ export interface CombatSubsystem {
46
53
  projectiles: ProjectileSystem;
47
54
  combatFx: CombatFx;
48
55
  vfxInstances: VfxInstanceStore;
56
+ captureProjectileTargets(gameDt: number): void;
49
57
  }
50
58
  /** @internal */
51
59
  export declare function createCombatSubsystem(d: CombatSubsystemDeps): CombatSubsystem;