@jgengine/core 0.18.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. package/CHANGELOG.md +504 -7
  2. package/dist/ai/decisionGraph.d.ts +50 -4
  3. package/dist/ai/decisionGraph.js +159 -23
  4. package/dist/ai/flock.d.ts +1 -1
  5. package/dist/ai/flock.js +7 -4
  6. package/dist/ai/groupAssist.d.ts +10 -0
  7. package/dist/ai/groupAssist.js +11 -0
  8. package/dist/ai/pursuit.d.ts +1 -1
  9. package/dist/ai/pursuit.js +1 -1
  10. package/dist/ai/threat.d.ts +8 -0
  11. package/dist/ai/threat.js +10 -0
  12. package/dist/anim/animGraph.d.ts +43 -1
  13. package/dist/anim/animGraph.js +326 -10
  14. package/dist/anim/boneTexture.d.ts +55 -0
  15. package/dist/anim/boneTexture.js +43 -0
  16. package/dist/anim/footPlacement.d.ts +57 -0
  17. package/dist/anim/footPlacement.js +67 -0
  18. package/dist/anim/locomotionGraph.d.ts +18 -1
  19. package/dist/anim/locomotionGraph.js +42 -3
  20. package/dist/audio/audioFalloff.d.ts +6 -0
  21. package/dist/audio/doppler.d.ts +21 -0
  22. package/dist/audio/doppler.js +32 -0
  23. package/dist/audio/engineLayers.d.ts +83 -0
  24. package/dist/audio/engineLayers.js +115 -0
  25. package/dist/audio/voiceAllocator.d.ts +46 -0
  26. package/dist/audio/voiceAllocator.js +76 -0
  27. package/dist/combat/abilityKit.d.ts +20 -0
  28. package/dist/combat/abilityKit.js +41 -4
  29. package/dist/combat/death.d.ts +11 -3
  30. package/dist/combat/death.js +3 -3
  31. package/dist/combat/magazine.d.ts +9 -0
  32. package/dist/combat/magazine.js +24 -2
  33. package/dist/combat/projectiles.d.ts +94 -2
  34. package/dist/combat/projectiles.js +282 -61
  35. package/dist/combat/regenShield.d.ts +14 -0
  36. package/dist/combat/regenShield.js +23 -7
  37. package/dist/combat/shotOrigin.d.ts +6 -0
  38. package/dist/combat/shotOrigin.js +6 -0
  39. package/dist/combat/weaponFeedback.d.ts +36 -0
  40. package/dist/combat/weaponFeedback.js +42 -0
  41. package/dist/combat/weaponHandling.d.ts +161 -0
  42. package/dist/combat/weaponHandling.js +202 -0
  43. package/dist/combat/weaponPresentation.d.ts +115 -0
  44. package/dist/combat/weaponPresentation.js +96 -0
  45. package/dist/crafting/recipeCatalog.d.ts +16 -0
  46. package/dist/crafting/recipeCatalog.js +55 -0
  47. package/dist/devtools/warnOnce.d.ts +8 -0
  48. package/dist/devtools/warnOnce.js +18 -0
  49. package/dist/economy/auctionBook.d.ts +1 -1
  50. package/dist/economy/auctionBook.js +11 -1
  51. package/dist/economy/listingBook.d.ts +1 -1
  52. package/dist/economy/listingBook.js +11 -1
  53. package/dist/economy/resourceLedger.js +3 -3
  54. package/dist/economy/shopStock.js +7 -6
  55. package/dist/economy/techTree.js +7 -6
  56. package/dist/economy/unlockPoints.js +3 -6
  57. package/dist/editor/commandApply.js +89 -16
  58. package/dist/editor/commands.d.ts +38 -1
  59. package/dist/editor/commands.js +320 -3
  60. package/dist/editor/creatorStorage.d.ts +59 -0
  61. package/dist/editor/creatorStorage.js +164 -0
  62. package/dist/editor/document.d.ts +4 -0
  63. package/dist/editor/document.js +119 -18
  64. package/dist/editor/environment.js +8 -0
  65. package/dist/editor/index.d.ts +2 -0
  66. package/dist/editor/liveSync.d.ts +4 -1
  67. package/dist/editor/liveSync.js +26 -10
  68. package/dist/editor/materialAuthoring.d.ts +14 -0
  69. package/dist/editor/materialAuthoring.js +38 -0
  70. package/dist/editor/modelNodeVisibility.d.ts +11 -0
  71. package/dist/editor/modelNodeVisibility.js +29 -0
  72. package/dist/editor/movementCatalog.d.ts +90 -0
  73. package/dist/editor/movementCatalog.js +144 -0
  74. package/dist/editor/simulation.d.ts +60 -0
  75. package/dist/editor/simulation.js +202 -0
  76. package/dist/editor/staticPrefab.d.ts +17 -0
  77. package/dist/editor/staticPrefab.js +51 -0
  78. package/dist/editor/types.d.ts +15 -0
  79. package/dist/faction/reputation.d.ts +6 -0
  80. package/dist/faction/reputation.js +11 -0
  81. package/dist/game/cameraConfig.d.ts +54 -2
  82. package/dist/game/clipRoles.d.ts +2 -1
  83. package/dist/game/clipRoles.js +7 -3
  84. package/dist/game/contentValidation.d.ts +69 -0
  85. package/dist/game/contentValidation.js +118 -0
  86. package/dist/game/controlGate.d.ts +8 -0
  87. package/dist/game/controlGate.js +43 -6
  88. package/dist/game/defineGame.d.ts +32 -3
  89. package/dist/game/dialogueGraph.d.ts +20 -0
  90. package/dist/game/dialogueGraph.js +107 -0
  91. package/dist/game/events.d.ts +11 -0
  92. package/dist/game/keyValueStore.d.ts +17 -4
  93. package/dist/game/keyValueStore.js +35 -23
  94. package/dist/game/lootModifiers.d.ts +17 -0
  95. package/dist/game/lootModifiers.js +26 -0
  96. package/dist/game/lootTable.d.ts +4 -0
  97. package/dist/game/lootTable.js +16 -0
  98. package/dist/game/narrativeAuthoring.d.ts +67 -0
  99. package/dist/game/narrativeAuthoring.js +122 -0
  100. package/dist/game/playableGame.d.ts +134 -35
  101. package/dist/game/quest.d.ts +15 -0
  102. package/dist/game/quest.js +209 -63
  103. package/dist/game/questCatalog.d.ts +50 -0
  104. package/dist/game/questCatalog.js +205 -0
  105. package/dist/game/saveStore.d.ts +6 -5
  106. package/dist/game/saveStore.js +57 -57
  107. package/dist/game/systemRuntime.d.ts +2 -2
  108. package/dist/game/systemRuntime.js +84 -33
  109. package/dist/game/toasts.d.ts +8 -0
  110. package/dist/game/toasts.js +7 -0
  111. package/dist/game/vfxInstance.d.ts +9 -0
  112. package/dist/game/vfxInstance.js +12 -1
  113. package/dist/game/viewports.d.ts +43 -0
  114. package/dist/game/viewports.js +46 -0
  115. package/dist/game/worldItem.d.ts +5 -1
  116. package/dist/game/worldItem.js +0 -1
  117. package/dist/gameplay.d.ts +3 -0
  118. package/dist/gameplay.js +3 -0
  119. package/dist/input/actionBindings.d.ts +15 -1
  120. package/dist/input/actionBindings.js +14 -3
  121. package/dist/input/actionContexts.d.ts +25 -1
  122. package/dist/input/actionContexts.js +82 -25
  123. package/dist/input/axisInput.d.ts +4 -0
  124. package/dist/input/axisInput.js +4 -0
  125. package/dist/input/axisShaper.d.ts +73 -0
  126. package/dist/input/axisShaper.js +105 -0
  127. package/dist/input/gamepadModel.d.ts +36 -2
  128. package/dist/input/gamepadModel.js +69 -30
  129. package/dist/input/haptics.d.ts +50 -0
  130. package/dist/input/haptics.js +74 -0
  131. package/dist/input/inputBuffer.d.ts +40 -0
  132. package/dist/input/inputBuffer.js +59 -0
  133. package/dist/input/lookChannel.d.ts +10 -0
  134. package/dist/input/lookChannel.js +6 -0
  135. package/dist/input/pointer.d.ts +2 -0
  136. package/dist/input/pointer.js +4 -0
  137. package/dist/interaction/promptRegistry.d.ts +5 -4
  138. package/dist/interaction/promptRegistry.js +8 -2
  139. package/dist/inventory/inventoryModel.js +9 -6
  140. package/dist/inventory/shapedGrid.d.ts +48 -9
  141. package/dist/inventory/shapedGrid.js +69 -10
  142. package/dist/item/affix.d.ts +6 -0
  143. package/dist/item/affix.js +6 -0
  144. package/dist/item/durability.js +4 -0
  145. package/dist/item/itemInstanceRegistry.d.ts +32 -1
  146. package/dist/item/itemInstanceRegistry.js +111 -7
  147. package/dist/item/itemgen.d.ts +1 -1
  148. package/dist/item/itemgen.js +1 -1
  149. package/dist/item/modularItem.js +7 -1
  150. package/dist/material/appearanceSignals.d.ts +53 -0
  151. package/dist/material/appearanceSignals.js +47 -0
  152. package/dist/material/hairCards.d.ts +47 -0
  153. package/dist/material/hairCards.js +136 -0
  154. package/dist/material/materialAsset.d.ts +162 -0
  155. package/dist/material/materialAsset.js +382 -0
  156. package/dist/meta/changelog.d.ts +1 -1
  157. package/dist/meta/changelog.js +444 -1
  158. package/dist/movement/characterController.d.ts +5 -2
  159. package/dist/movement/characterController.js +50 -23
  160. package/dist/movement/freeFlight.d.ts +2 -2
  161. package/dist/movement/freeFlight.js +3 -3
  162. package/dist/movement/glideModel.d.ts +3 -0
  163. package/dist/movement/glideModel.js +6 -0
  164. package/dist/movement/grappleSwing.d.ts +2 -0
  165. package/dist/movement/grappleSwing.js +5 -1
  166. package/dist/movement/movementModel.d.ts +63 -1
  167. package/dist/movement/movementModel.js +136 -26
  168. package/dist/movement/movementProbe.d.ts +51 -0
  169. package/dist/movement/movementProbe.js +119 -0
  170. package/dist/movement/playerMovement.d.ts +48 -2
  171. package/dist/movement/playerMovement.js +493 -87
  172. package/dist/movement/poseState.d.ts +2 -0
  173. package/dist/movement/poseState.js +29 -1
  174. package/dist/movement/solidObstacles.d.ts +11 -1
  175. package/dist/movement/solidObstacles.js +48 -3
  176. package/dist/movement/terrainGrade.d.ts +13 -0
  177. package/dist/movement/terrainGrade.js +33 -0
  178. package/dist/movement/voxelController.d.ts +2 -2
  179. package/dist/movement/voxelController.js +10 -10
  180. package/dist/nav/navFromEnvironment.d.ts +9 -3
  181. package/dist/nav/navFromEnvironment.js +18 -12
  182. package/dist/nav/navGrid.d.ts +19 -1
  183. package/dist/nav/navGrid.js +26 -10
  184. package/dist/nav/navMesh.d.ts +43 -2
  185. package/dist/nav/navMesh.js +534 -128
  186. package/dist/nav/solidRoute.d.ts +50 -0
  187. package/dist/nav/solidRoute.js +144 -0
  188. package/dist/physics/aircraftDynamics.d.ts +390 -0
  189. package/dist/physics/aircraftDynamics.js +704 -0
  190. package/dist/physics/ballisticSweep.d.ts +10 -3
  191. package/dist/physics/ballisticSweep.js +53 -29
  192. package/dist/physics/boatDynamics.d.ts +116 -0
  193. package/dist/physics/boatDynamics.js +160 -0
  194. package/dist/physics/drivableVehicle.d.ts +22 -8
  195. package/dist/physics/drivableVehicle.js +4 -2
  196. package/dist/physics/flightDynamics.d.ts +21 -4
  197. package/dist/physics/flightDynamics.js +28 -5
  198. package/dist/physics/forceVolume.d.ts +41 -0
  199. package/dist/physics/forceVolume.js +88 -0
  200. package/dist/physics/handlingProbe.d.ts +249 -0
  201. package/dist/physics/handlingProbe.js +606 -0
  202. package/dist/physics/kinematicVehicle.d.ts +40 -5
  203. package/dist/physics/kinematicVehicle.js +17 -2
  204. package/dist/physics/physicsBackend.d.ts +12 -0
  205. package/dist/physics/physicsBackendConformance.js +34 -0
  206. package/dist/physics/physicsWorldBackend.d.ts +1 -0
  207. package/dist/physics/physicsWorldBackend.js +52 -3
  208. package/dist/physics/spatialGrid.d.ts +1 -1
  209. package/dist/physics/spatialGrid.js +8 -9
  210. package/dist/physics/vehicleBackendLink.d.ts +49 -0
  211. package/dist/physics/vehicleBackendLink.js +102 -0
  212. package/dist/physics/vehicleDynamics.d.ts +375 -0
  213. package/dist/physics/vehicleDynamics.js +737 -0
  214. package/dist/physics/vehicleObstacles.d.ts +3 -2
  215. package/dist/physics/vehicleObstacles.js +29 -5
  216. package/dist/physics/worldColliders.d.ts +1 -1
  217. package/dist/physics/worldColliders.js +35 -3
  218. package/dist/puzzle/cellGrid.d.ts +15 -9
  219. package/dist/puzzle/cellGrid.js +13 -9
  220. package/dist/render/lookPreset.d.ts +9 -2
  221. package/dist/render/lookPreset.js +29 -2
  222. package/dist/render/postProcessing.d.ts +30 -0
  223. package/dist/render/postProcessing.js +1 -1
  224. package/dist/runtime/cameraDirector.d.ts +54 -3
  225. package/dist/runtime/cameraDirector.js +187 -0
  226. package/dist/runtime/commandDispatch.d.ts +14 -0
  227. package/dist/runtime/commandDispatch.js +19 -0
  228. package/dist/runtime/commandInput.d.ts +37 -0
  229. package/dist/runtime/commandInput.js +58 -0
  230. package/dist/runtime/commandRunner.d.ts +27 -0
  231. package/dist/runtime/commandRunner.js +31 -3
  232. package/dist/runtime/context/combat.d.ts +12 -4
  233. package/dist/runtime/context/combat.js +179 -11
  234. package/dist/runtime/context/deathLoot.d.ts +11 -8
  235. package/dist/runtime/context/deathLoot.js +17 -16
  236. package/dist/runtime/context/scene.d.ts +2 -0
  237. package/dist/runtime/context/scene.js +13 -21
  238. package/dist/runtime/descriptors/baseline.js +34 -3
  239. package/dist/runtime/descriptors/features.d.ts +2 -1
  240. package/dist/runtime/descriptors/features.js +18 -0
  241. package/dist/runtime/gameContext.js +86 -13
  242. package/dist/runtime/gameContextTypes.d.ts +33 -3
  243. package/dist/runtime/gameRuntime.d.ts +3 -1
  244. package/dist/runtime/gameRuntime.js +4 -0
  245. package/dist/runtime/headlessRunner.d.ts +3 -1
  246. package/dist/runtime/headlessRunner.js +11 -11
  247. package/dist/runtime/hostedGameRunner.d.ts +24 -1
  248. package/dist/runtime/hostedGameRunner.js +156 -10
  249. package/dist/runtime/hostedWorldSession.d.ts +17 -5
  250. package/dist/runtime/hostedWorldSession.js +154 -16
  251. package/dist/runtime/hostedWorldStore.d.ts +2 -2
  252. package/dist/runtime/inputRecorder.d.ts +1 -1
  253. package/dist/runtime/inputRecorder.js +21 -1
  254. package/dist/runtime/inputSnapshot.d.ts +22 -4
  255. package/dist/runtime/inputSnapshot.js +41 -3
  256. package/dist/runtime/localPlayers.d.ts +73 -0
  257. package/dist/runtime/localPlayers.js +119 -0
  258. package/dist/runtime/runtimeSave.d.ts +10 -6
  259. package/dist/runtime/runtimeSave.js +11 -5
  260. package/dist/runtime/simContext.d.ts +8 -3
  261. package/dist/runtime/simContext.js +7 -3
  262. package/dist/runtime/simSnapshot.js +2 -0
  263. package/dist/runtime/transport.d.ts +9 -0
  264. package/dist/runtime/transport.js +7 -1
  265. package/dist/runtime/worldReplication.d.ts +2 -0
  266. package/dist/runtime/worldReplication.js +1 -1
  267. package/dist/scene/assetCatalog.d.ts +2 -0
  268. package/dist/scene/behaviorRuntime.d.ts +38 -7
  269. package/dist/scene/behaviorRuntime.js +293 -30
  270. package/dist/scene/behaviors.d.ts +35 -2
  271. package/dist/scene/behaviors.js +29 -0
  272. package/dist/scene/entityStore.d.ts +14 -3
  273. package/dist/scene/entityStore.js +45 -9
  274. package/dist/scene/mount.d.ts +9 -0
  275. package/dist/scene/mount.js +35 -0
  276. package/dist/scene/possession.d.ts +2 -0
  277. package/dist/scene/possession.js +21 -1
  278. package/dist/scene/sceneKinds.d.ts +9 -0
  279. package/dist/scene/sceneRaycast.d.ts +7 -0
  280. package/dist/scene/sceneRaycast.js +55 -0
  281. package/dist/scene/spatial.d.ts +20 -1
  282. package/dist/scene/spatial.js +94 -2
  283. package/dist/scene/vehicleSeat.d.ts +41 -3
  284. package/dist/scene/vehicleSeat.js +94 -3
  285. package/dist/sensor/concealment.d.ts +6 -0
  286. package/dist/sensor/concealment.js +8 -0
  287. package/dist/sensor/freezeMonitor.d.ts +6 -0
  288. package/dist/sensor/freezeMonitor.js +8 -0
  289. package/dist/sensor/perception.d.ts +11 -1
  290. package/dist/sensor/perception.js +57 -10
  291. package/dist/sensor/recordingBuffer.d.ts +3 -0
  292. package/dist/sensor/recordingBuffer.js +8 -0
  293. package/dist/session/roundState.d.ts +4 -0
  294. package/dist/session/roundState.js +11 -0
  295. package/dist/settings/frameRateLimit.d.ts +16 -0
  296. package/dist/settings/frameRateLimit.js +37 -0
  297. package/dist/settings/graphicsProfile.d.ts +2 -0
  298. package/dist/settings/graphicsProfile.js +12 -0
  299. package/dist/settings/graphicsSettings.d.ts +40 -0
  300. package/dist/settings/graphicsSettings.js +81 -0
  301. package/dist/settings/settingsModel.d.ts +9 -1
  302. package/dist/settings/settingsModel.js +10 -0
  303. package/dist/stats/accumulatorMeter.d.ts +10 -0
  304. package/dist/stats/accumulatorMeter.js +6 -0
  305. package/dist/stats/eventMeter.d.ts +10 -3
  306. package/dist/stats/eventMeter.js +6 -3
  307. package/dist/store/defineStore.d.ts +3 -1
  308. package/dist/store/defineStore.js +3 -1
  309. package/dist/store/observableKeyedStore.d.ts +1 -1
  310. package/dist/store/observableKeyedStore.js +8 -6
  311. package/dist/time/simClock.d.ts +6 -2
  312. package/dist/time/simClock.js +5 -0
  313. package/dist/vfx/feedbackMixer.d.ts +61 -0
  314. package/dist/vfx/feedbackMixer.js +106 -0
  315. package/dist/vfx/particleDirector.d.ts +58 -6
  316. package/dist/vfx/particleDirector.js +113 -22
  317. package/dist/vfx/particles.d.ts +68 -5
  318. package/dist/vfx/particles.js +284 -32
  319. package/dist/visibility/assetStreaming.d.ts +21 -3
  320. package/dist/visibility/assetStreaming.js +108 -24
  321. package/dist/visibility/settings.d.ts +8 -2
  322. package/dist/visibility/settings.js +3 -0
  323. package/dist/visibility/simulationCulling.d.ts +8 -1
  324. package/dist/visibility/simulationCulling.js +7 -0
  325. package/dist/visibility/spatialIndex.d.ts +21 -0
  326. package/dist/visibility/spatialIndex.js +16 -0
  327. package/dist/visibility/visibilitySystem.js +1 -0
  328. package/dist/work/expedition.d.ts +110 -0
  329. package/dist/work/expedition.js +195 -0
  330. package/dist/work/staffedStation.d.ts +29 -0
  331. package/dist/work/staffedStation.js +27 -0
  332. package/dist/world/authoredAnimation.d.ts +38 -0
  333. package/dist/world/authoredAnimation.js +99 -0
  334. package/dist/world/authoredObjects.d.ts +24 -4
  335. package/dist/world/authoredObjects.js +43 -16
  336. package/dist/world/authoredSimulation.d.ts +74 -0
  337. package/dist/world/authoredSimulation.js +302 -0
  338. package/dist/world/authoredSolids.d.ts +46 -0
  339. package/dist/world/authoredSolids.js +75 -0
  340. package/dist/world/authoredWeather.d.ts +120 -0
  341. package/dist/world/authoredWeather.js +314 -0
  342. package/dist/world/buildingGenerator.js +2 -2
  343. package/dist/world/buildingKit.d.ts +11 -4
  344. package/dist/world/buildingKit.js +21 -8
  345. package/dist/world/buildings.d.ts +34 -2
  346. package/dist/world/buildings.js +11 -1
  347. package/dist/world/cityKind.d.ts +7 -0
  348. package/dist/world/cityKind.js +52 -1
  349. package/dist/world/environmentForces.d.ts +21 -0
  350. package/dist/world/environmentForces.js +42 -0
  351. package/dist/world/features.d.ts +32 -5
  352. package/dist/world/features.js +6 -0
  353. package/dist/world/footprintGrid.d.ts +62 -0
  354. package/dist/world/footprintGrid.js +217 -0
  355. package/dist/world/interiors.d.ts +1 -0
  356. package/dist/world/interiors.js +5 -3
  357. package/dist/world/lod.d.ts +6 -0
  358. package/dist/world/lod.js +8 -0
  359. package/dist/world/pathTerrain.d.ts +1 -1
  360. package/dist/world/terraform.d.ts +9 -1
  361. package/dist/world/terraform.js +53 -18
  362. package/dist/world/walls.d.ts +7 -0
  363. package/dist/world/walls.js +7 -0
  364. package/dist/world/weather.d.ts +17 -0
  365. package/dist/world/weather.js +116 -20
  366. package/dist/world/worldSolids.d.ts +95 -0
  367. package/dist/world/worldSolids.js +351 -0
  368. package/dist/world.d.ts +6 -6
  369. package/dist/world.js +5 -5
  370. package/package.json +1 -1
  371. package/skills/ce-handoff/SKILL.md +2 -2
  372. package/skills/game-design/SKILL.md +2 -2
  373. package/skills/jgengine/SKILL.md +28 -6
  374. package/skills/jgengine/api.md +139 -99
  375. package/skills/jgengine/capabilities.md +28 -0
  376. package/skills/jgengine/recipes/minimal-game.md +10 -2
  377. package/skills/jgengine-combat/SKILL.md +13 -2
  378. package/skills/jgengine-combat/api.md +150 -103
  379. package/skills/jgengine-combat/capabilities.md +29 -1
  380. package/skills/jgengine-combat/recipes/portable-runtime-state.md +60 -6
  381. package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +37 -3
  382. package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
  383. package/skills/jgengine-combat/reference.md +24 -2
  384. package/skills/jgengine-gameplay/SKILL.md +63 -0
  385. package/skills/jgengine-gameplay/api.md +583 -463
  386. package/skills/jgengine-gameplay/capabilities.md +92 -0
  387. package/skills/jgengine-gameplay/recipes/device-save-status.md +46 -0
  388. package/skills/jgengine-gameplay/recipes/expeditions.md +108 -0
  389. package/skills/jgengine-gameplay/recipes/narrative-authoring.md +76 -0
  390. package/skills/jgengine-gameplay/recipes/paged-item-storage.md +101 -0
  391. package/skills/jgengine-gameplay/recipes/quest-authoring.md +63 -0
  392. package/skills/jgengine-gameplay/recipes/shaped-inventory.md +50 -0
  393. package/skills/jgengine-gameplay/reference-content-validation.md +74 -0
  394. package/skills/jgengine-gameplay/reference-systems.md +123 -0
  395. package/skills/jgengine-world/SKILL.md +25 -4
  396. package/skills/jgengine-world/api.md +1045 -655
  397. package/skills/jgengine-world/capabilities.md +246 -9
  398. package/skills/jgengine-world/recipes/character-animation.md +62 -0
  399. package/skills/jgengine-world/recipes/controls.md +48 -0
  400. package/skills/jgengine-world/recipes/flight-feel.md +76 -0
  401. package/skills/jgengine-world/recipes/npc-senses.md +55 -0
  402. package/skills/jgengine-world/recipes/vehicle-feel.md +87 -0
  403. package/skills/jgengine-world/reference.md +180 -4
  404. package/skills/jgengine-world/visibility.md +139 -0
@@ -0,0 +1,195 @@
1
+ import { appendFeed } from "../game/feed.js";
2
+ import { hashString, randomSeedFrom, stepRandomSeed } from "../random/rng.js";
3
+ import { accrueSince } from "../time/accrueSince.js";
4
+ import { steppedCatchUp } from "../time/idleProgress.js";
5
+ function nonnegative(value, name) {
6
+ if (!Number.isFinite(value) || value < 0)
7
+ throw new RangeError(`${name} must be finite and nonnegative`);
8
+ return value;
9
+ }
10
+ function positiveInteger(value, name) {
11
+ if (!Number.isSafeInteger(value) || value <= 0)
12
+ throw new RangeError(`${name} must be a positive safe integer`);
13
+ return value;
14
+ }
15
+ function validate(config, state, nowMs) {
16
+ if (!Number.isFinite(nowMs) || !Number.isFinite(state.startedAtMs) || !Number.isFinite(state.lastSettledMs))
17
+ throw new RangeError("Expedition timestamps must be finite");
18
+ if (state.lastSettledMs < state.startedAtMs || (state.returnUntilMs !== null && !Number.isFinite(state.returnUntilMs)))
19
+ throw new RangeError("Invalid expedition time frontier");
20
+ if (!Number.isSafeInteger(state.cursor))
21
+ throw new RangeError("Expedition RNG cursor must be a safe integer");
22
+ positiveInteger(config.tickMs, "tickMs");
23
+ positiveInteger(config.carryCap, "carryCap");
24
+ positiveInteger(config.maxTicksPerSettle ?? 10_000, "maxTicksPerSettle");
25
+ positiveInteger(config.logLimit ?? 200, "logLimit");
26
+ nonnegative(config.returnFraction, "returnFraction");
27
+ nonnegative(state.hp, "hp");
28
+ nonnegative(state.exposure, "exposure");
29
+ nonnegative(state.elapsedMs, "elapsedMs");
30
+ for (const count of Object.values(state.supplies))
31
+ if (!Number.isSafeInteger(count) || count < 0)
32
+ throw new RangeError("Supply counts must be nonnegative safe integers");
33
+ }
34
+ function record(config, state, event, text) {
35
+ return { ...state, log: appendFeed(state.log, { ...event, text: text ?? config.describe?.(event, state) ?? "" }, { limit: config.logLimit ?? 200 }) };
36
+ }
37
+ function itemUnits(drops) {
38
+ let units = 0;
39
+ for (const drop of drops)
40
+ if (drop.item !== undefined)
41
+ units += nonnegative(drop.count, "Item count");
42
+ return nonnegative(units, "Carried item units");
43
+ }
44
+ function acceptLoot(cap, carried, drops) {
45
+ let remaining = Math.max(0, cap - itemUnits(carried));
46
+ const next = carried.map(drop => ({ ...drop }));
47
+ const accepted = [];
48
+ for (const drop of drops) {
49
+ const count = nonnegative(drop.count, "Drop count");
50
+ if ((drop.item === undefined) === (drop.currency === undefined))
51
+ throw new RangeError("Expedition drops need exactly one item or currency id");
52
+ if (drop.item !== undefined && !Number.isSafeInteger(count))
53
+ throw new RangeError("Item counts must be nonnegative safe integers");
54
+ const admitted = drop.item === undefined ? count : Math.min(count, remaining);
55
+ if (drop.item !== undefined)
56
+ remaining -= admitted;
57
+ if (admitted === 0)
58
+ continue;
59
+ const incoming = { ...drop, count: admitted };
60
+ accepted.push(incoming);
61
+ const existing = next.find(entry => drop.item !== undefined ? entry.item === drop.item : entry.currency === drop.currency && entry.item === undefined);
62
+ if (existing === undefined)
63
+ next.push({ ...incoming });
64
+ else
65
+ existing.count = nonnegative(existing.count + admitted, "Carried count");
66
+ }
67
+ return { carried: next, accepted };
68
+ }
69
+ function arrive(config, state, nowMs) {
70
+ if (state.status !== "returning" || state.returnUntilMs === null || nowMs < state.returnUntilMs)
71
+ return state;
72
+ const at = state.returnUntilMs;
73
+ return record(config, { ...state, status: "home", lastSettledMs: at }, { at, kind: "return" });
74
+ }
75
+ function beginReturn(config, state, at) {
76
+ const elapsedMs = Math.max(state.elapsedMs, at - state.startedAtMs);
77
+ const returnUntilMs = at + elapsedMs * config.returnFraction;
78
+ if (!Number.isFinite(returnUntilMs))
79
+ throw new RangeError("Expedition return deadline overflowed");
80
+ return arrive(config, record(config, { ...state, status: "returning", elapsedMs, returnUntilMs, lastSettledMs: at }, { at, kind: "recall" }), at);
81
+ }
82
+ /**
83
+ * Dispatch plain expedition state with a persisted seeded cursor; no clock, scheduler or storage is owned.
84
+ *
85
+ * @capability expedition-dispatch dispatch a saveable timed away mission with finite supplies and a seeded RNG cursor
86
+ */
87
+ export function dispatchExpedition(input) {
88
+ if (!Number.isFinite(input.startAtMs))
89
+ throw new RangeError("startAtMs must be finite");
90
+ nonnegative(input.hp, "hp");
91
+ nonnegative(input.exposure ?? 0, "exposure");
92
+ const supplies = { ...input.supplies };
93
+ for (const count of Object.values(supplies))
94
+ if (!Number.isSafeInteger(count) || count < 0)
95
+ throw new RangeError("Supply counts must be nonnegative safe integers");
96
+ return { id: input.id, status: input.hp === 0 ? "dead" : "exploring", startedAtMs: input.startAtMs, lastSettledMs: input.startAtMs,
97
+ elapsedMs: 0, returnUntilMs: null, hp: input.hp, exposure: input.exposure ?? 0, supplies, carried: [], log: [], seed: input.seed, cursor: randomSeedFrom(hashString(input.seed)) };
98
+ }
99
+ /**
100
+ * Replay due event ticks using the existing stepped catch-up primitive. Order is loot → hazard → ordered
101
+ * supplies → death → capacity recall. Unprocessed ticks retain their frontier; returning never rolls or
102
+ * takes damage. Auto-recall uses the triggering tick's time, including offline arrival in the same call.
103
+ *
104
+ * @capability expedition-settle settle bounded offline expedition ticks without losing supplies rewards or RNG continuity
105
+ */
106
+ export function settleExpedition(config, state, nowMs) {
107
+ validate(config, state, nowMs);
108
+ if (nowMs < state.lastSettledMs || (state.status === "exploring" && nowMs < state.startedAtMs + state.elapsedMs) || state.status === "dead" || state.status === "home")
109
+ return state;
110
+ if (state.status === "returning")
111
+ return arrive(config, state, nowMs);
112
+ let next = state;
113
+ const step = config.randomStep ?? stepRandomSeed;
114
+ const window = accrueSince(state.lastSettledMs, nowMs);
115
+ nonnegative(window.elapsedMs, "Catch-up duration");
116
+ // Normalize to whole one-unit ticks so fractional-second division cannot lose a due millisecond.
117
+ const ticks = Math.floor(window.elapsedMs / config.tickMs);
118
+ steppedCatchUp(ticks, 1, () => {
119
+ if (next.status !== "exploring")
120
+ return;
121
+ const at = next.lastSettledMs + config.tickMs;
122
+ next = { ...next, lastSettledMs: at, elapsedMs: at - next.startedAtMs };
123
+ let cursor = next.cursor;
124
+ const rng = () => {
125
+ const [value, following] = step(cursor);
126
+ if (!Number.isFinite(value) || value < 0 || value >= 1 || !Number.isSafeInteger(following))
127
+ throw new RangeError("Expedition RNG must return a [0,1) value and safe integer cursor");
128
+ cursor = following;
129
+ return value;
130
+ };
131
+ const resolution = config.loot.resolve({ ctx: config.lootCtx(next), rng, seed: next.seed });
132
+ const accepted = acceptLoot(config.carryCap, next.carried, resolution.drops);
133
+ next = { ...next, carried: accepted.carried };
134
+ if (resolution.drops.length > 0)
135
+ next = record(config, next, { at, kind: "loot", drops: accepted.accepted, loot: resolution });
136
+ const hazard = config.hazard?.(next, rng) ?? {};
137
+ const exposure = nonnegative(next.exposure + nonnegative(hazard.exposure ?? 0, "Hazard exposure"), "Exposure");
138
+ const hp = Math.min(nonnegative(config.maxHp({ ...next, exposure }), "maxHp"), Math.max(0, next.hp - nonnegative(hazard.damage ?? 0, "Hazard damage")));
139
+ const hpDelta = hp - next.hp, exposureDelta = exposure - next.exposure;
140
+ next = { ...next, hp, exposure };
141
+ if (hpDelta !== 0 || exposureDelta !== 0)
142
+ next = record(config, next, { at, kind: "hazard", hpDelta, exposureDelta });
143
+ for (const notice of hazard.notices ?? [])
144
+ next = record(config, next, { at, kind: notice.kind }, notice.text);
145
+ for (const rule of config.supplies ?? []) {
146
+ const count = Object.hasOwn(next.supplies, rule.key) ? next.supplies[rule.key] : 0;
147
+ const restoreHp = nonnegative(rule.restoreHp ?? 0, "Supply restoration"), clearExposure = nonnegative(rule.clearExposure ?? 0, "Supply cleansing");
148
+ if (count <= 0 || !rule.when(next))
149
+ continue;
150
+ const exposure = Math.max(0, next.exposure - clearExposure);
151
+ const hp = Math.min(nonnegative(config.maxHp({ ...next, exposure }), "maxHp"), nonnegative(next.hp + restoreHp, "Restored hp"));
152
+ if (hp === next.hp && exposure === next.exposure)
153
+ continue;
154
+ const hpDelta = hp - next.hp, exposureDelta = exposure - next.exposure;
155
+ next = { ...next, hp, exposure, supplies: { ...next.supplies, [rule.key]: count - 1 } };
156
+ next = record(config, next, { at, kind: "supply", supply: rule.key, hpDelta, exposureDelta });
157
+ }
158
+ next = { ...next, cursor };
159
+ if (next.hp <= 0)
160
+ next = record(config, { ...next, status: "dead" }, { at, kind: "death" });
161
+ else if (itemUnits(next.carried) >= config.carryCap)
162
+ next = beginReturn(config, next, at);
163
+ }, config.maxTicksPerSettle ?? 10_000);
164
+ if (next.status === "exploring" && nowMs - next.lastSettledMs < config.tickMs) {
165
+ const elapsedMs = Math.max(next.elapsedMs, nowMs - next.startedAtMs);
166
+ nonnegative(elapsedMs, "Exploring duration");
167
+ if (elapsedMs !== next.elapsedMs)
168
+ next = { ...next, elapsedMs };
169
+ if (itemUnits(next.carried) >= config.carryCap)
170
+ next = beginReturn(config, next, nowMs);
171
+ }
172
+ return arrive(config, next, nowMs);
173
+ }
174
+ /**
175
+ * Settle first, then recall safely. If the catch-up budget leaves due exploring ticks, return that state
176
+ * without recalling; repeat at the same target until the frontier catches up. Dead state never revives.
177
+ *
178
+ * @capability expedition-recall begin a safe fractional-time return after settling the pending expedition frontier
179
+ */
180
+ export function recallExpedition(config, state, nowMs) {
181
+ const next = settleExpedition(config, state, nowMs);
182
+ return next.status === "exploring" && nowMs >= next.startedAtMs + next.elapsedMs && nowMs >= next.lastSettledMs && nowMs - next.lastSettledMs < config.tickMs ? beginReturn(config, next, nowMs) : next;
183
+ }
184
+ /**
185
+ * Settle first, then complete a live return instantly. Caller owns payment/authorization; pending ticks
186
+ * remain unsettled when the budget is exhausted, and dead/home state is never changed.
187
+ *
188
+ * @capability expedition-return complete an authorized instant return without skipping unsettled ticks or reviving death
189
+ */
190
+ export function returnExpeditionNow(config, state, nowMs) {
191
+ const next = settleExpedition(config, state, nowMs);
192
+ if (next.status === "dead" || next.status === "home" || nowMs < next.lastSettledMs || (next.status === "exploring" && nowMs < next.startedAtMs + next.elapsedMs) || (next.status === "exploring" && nowMs - next.lastSettledMs >= config.tickMs))
193
+ return next;
194
+ return record(config, { ...next, status: "home", returnUntilMs: nowMs, lastSettledMs: nowMs }, { at: nowMs, kind: "return" });
195
+ }
@@ -0,0 +1,29 @@
1
+ /** One assigned worker's caller-resolved contribution; stat names and modifiers stay caller-owned. */
2
+ export interface StationWorker {
3
+ readonly id: string;
4
+ readonly stat: number;
5
+ }
6
+ /** Serializable staffing snapshot. The caller owns assignment, unique worker ids, and stable order. */
7
+ export interface StaffedStation {
8
+ readonly stationId: string;
9
+ /** Nonnegative whole slot count; fractional values round down and nonfinite values admit no workers. */
10
+ readonly slots: number;
11
+ /** Only the first `slots` entries contribute; overflow assignments are not reordered or mutated. */
12
+ readonly workers: readonly StationWorker[];
13
+ }
14
+ /** Caller-owned output tuning in production cycles per game-second. */
15
+ export interface StationRateTuning {
16
+ readonly base: number;
17
+ readonly perStat: number;
18
+ /** Applied to the whole rate, including base; defaults to 1 and clamps to 0…1. */
19
+ readonly efficiency?: number;
20
+ }
21
+ /**
22
+ * Resolve `(base + perStat * sum(admitted worker stats)) * efficiency` without allocation.
23
+ * Empty staffing retains base production. Negative/nonfinite contributions are zero; a nonfinite
24
+ * efficiency or overflowing result stops production with rate zero. Invalid workers still occupy
25
+ * their ordered slots. Assign the result to a `ProductionBuildingDef.rate` for `tickProduction`.
26
+ *
27
+ * @capability staffed-station-rate turn bounded caller-owned worker stats and efficiency into a production rate
28
+ */
29
+ export declare function stationOutputRate(station: StaffedStation, tuning: StationRateTuning): number;
@@ -0,0 +1,27 @@
1
+ function contribution(value) {
2
+ return Number.isFinite(value) && value > 0 ? value : 0;
3
+ }
4
+ /**
5
+ * Resolve `(base + perStat * sum(admitted worker stats)) * efficiency` without allocation.
6
+ * Empty staffing retains base production. Negative/nonfinite contributions are zero; a nonfinite
7
+ * efficiency or overflowing result stops production with rate zero. Invalid workers still occupy
8
+ * their ordered slots. Assign the result to a `ProductionBuildingDef.rate` for `tickProduction`.
9
+ *
10
+ * @capability staffed-station-rate turn bounded caller-owned worker stats and efficiency into a production rate
11
+ */
12
+ export function stationOutputRate(station, tuning) {
13
+ const efficiency = Math.min(1, contribution(tuning.efficiency ?? 1));
14
+ if (efficiency === 0)
15
+ return 0;
16
+ const base = contribution(tuning.base);
17
+ const perStat = contribution(tuning.perStat);
18
+ if (perStat === 0)
19
+ return base * efficiency;
20
+ const slots = Number.isFinite(station.slots) ? Math.max(0, Math.floor(station.slots)) : 0;
21
+ const count = Math.min(slots, station.workers.length);
22
+ let total = 0;
23
+ for (let index = 0; index < count; index += 1)
24
+ total += contribution(station.workers[index].stat);
25
+ const rate = (base + perStat * total) * efficiency;
26
+ return Number.isFinite(rate) ? rate : 0;
27
+ }
@@ -0,0 +1,38 @@
1
+ import type { AuthoredAnimation } from "./authoredObjects.js";
2
+ /** Structural marker input; immutable editor scene documents satisfy it. */
3
+ export interface AuthoredAnimationDocumentLike {
4
+ readonly markers: readonly {
5
+ readonly id: string;
6
+ readonly meta?: Readonly<Record<string, unknown>>;
7
+ }[];
8
+ }
9
+ /** Rejected saved animation data with a document location and a repair. */
10
+ export interface AuthoredAnimationDiagnostic {
11
+ readonly path: string;
12
+ readonly message: string;
13
+ readonly repair: string;
14
+ }
15
+ /** Resolved marker animation or the caller's unchanged default, with rejected-value diagnostics. */
16
+ export interface AuthoredAnimationResult {
17
+ readonly animation: AuthoredAnimation | undefined;
18
+ readonly diagnostics: readonly AuthoredAnimationDiagnostic[];
19
+ }
20
+ /**
21
+ * Resolves a caller-selected marker's animation. Authored configuration replaces game defaults;
22
+ * omitted or malformed data retains the exact default. Partial roles remain available for editing.
23
+ * Asset clip and rig compatibility is diagnosed by the animation renderer.
24
+ * @capability authored-animation bind saved character animation without copying game-side validation
25
+ */
26
+ export declare function readAuthoredAnimation(document: AuthoredAnimationDocumentLike, markerId: string, defaults?: AuthoredAnimation): AuthoredAnimationResult;
27
+ /**
28
+ * Validates one saved animation value at the caller's document path without scanning markers.
29
+ * Omitted or malformed values retain the exact default; valid authored configuration replaces it whole.
30
+ * @capability authored-animation validate a located animation override in a single-pass document resolver
31
+ */
32
+ export declare function readAuthoredAnimationValue(value: unknown, path: string, defaults?: AuthoredAnimation): AuthoredAnimationResult;
33
+ /**
34
+ * Reads the latest immutable document each call, validating only when document identity changes.
35
+ * The caller owns the marker-to-character association and its asset-specific animation defaults.
36
+ * @capability authored-animation consume live authored character animation with bounded per-frame work
37
+ */
38
+ export declare function createAuthoredAnimationReader(getDocument: () => AuthoredAnimationDocumentLike, markerId: string, defaults?: AuthoredAnimation): () => AuthoredAnimationResult;
@@ -0,0 +1,99 @@
1
+ import { validateAnimGraph } from "../anim/animGraph.js";
2
+ function record(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ /**
6
+ * Resolves a caller-selected marker's animation. Authored configuration replaces game defaults;
7
+ * omitted or malformed data retains the exact default. Partial roles remain available for editing.
8
+ * Asset clip and rig compatibility is diagnosed by the animation renderer.
9
+ * @capability authored-animation bind saved character animation without copying game-side validation
10
+ */
11
+ export function readAuthoredAnimation(document, markerId, defaults) {
12
+ const index = document.markers.findIndex((marker) => marker.id === markerId);
13
+ return readAuthoredAnimationValue(document.markers[index]?.meta?.animation, `markers[${index}].meta.animation`, defaults);
14
+ }
15
+ /**
16
+ * Validates one saved animation value at the caller's document path without scanning markers.
17
+ * Omitted or malformed values retain the exact default; valid authored configuration replaces it whole.
18
+ * @capability authored-animation validate a located animation override in a single-pass document resolver
19
+ */
20
+ export function readAuthoredAnimationValue(value, path, defaults) {
21
+ const diagnostics = [];
22
+ if (value === undefined)
23
+ return { animation: defaults, diagnostics };
24
+ if (value === "auto" || value === "none")
25
+ return { animation: value, diagnostics };
26
+ const reject = (location, message, requirement) => {
27
+ diagnostics.push({ path: location, message, repair: `Use ${requirement}, or remove the animation override to inherit the game setting.` });
28
+ };
29
+ if (!record(value)) {
30
+ reject(path, "Animation must be an object, auto, or none", "an animation object, auto, or none");
31
+ return { animation: defaults, diagnostics };
32
+ }
33
+ if (value.clock !== undefined && value.clock !== "real" && value.clock !== "game")
34
+ reject(`${path}.clock`, "clock must be real or game", "real or game");
35
+ if (value.auto !== undefined && value.auto !== true)
36
+ reject(`${path}.auto`, "auto must be true when supplied", "true, or omit auto to use explicit configuration");
37
+ for (const key of ["clip"]) {
38
+ if (value[key] !== undefined && typeof value[key] !== "string")
39
+ reject(`${path}.${key}`, `${key} must be a string`, "a clip-name string");
40
+ }
41
+ for (const key of ["loop", "paused"]) {
42
+ if (value[key] !== undefined && typeof value[key] !== "boolean")
43
+ reject(`${path}.${key}`, `${key} must be a boolean`, "true or false");
44
+ }
45
+ for (const key of ["timeScale", "time"]) {
46
+ if (value[key] !== undefined && (typeof value[key] !== "number" || !Number.isFinite(value[key])))
47
+ reject(`${path}.${key}`, `${key} must be a finite number`, "a finite number");
48
+ }
49
+ if (value.states !== undefined) {
50
+ if (!record(value.states))
51
+ reject(`${path}.states`, "states must be an object", "a role-mapping object");
52
+ else {
53
+ for (const key of ["idle", "walk", "run"]) {
54
+ if (value.states[key] !== undefined && typeof value.states[key] !== "string")
55
+ reject(`${path}.states.${key}`, `${key} must be a string`, "a clip-name string");
56
+ }
57
+ for (const key of ["walkSpeed", "runSpeed", "fadeSec"]) {
58
+ const number = value.states[key];
59
+ if (number !== undefined && (typeof number !== "number" || !Number.isFinite(number)))
60
+ reject(`${path}.states.${key}`, `${key} must be a finite number`, "a finite number");
61
+ }
62
+ }
63
+ }
64
+ if (value.oneShots !== undefined) {
65
+ if (!record(value.oneShots))
66
+ reject(`${path}.oneShots`, "oneShots must be an object", "an event-to-clip mapping object");
67
+ else
68
+ for (const [event, clips] of Object.entries(value.oneShots)) {
69
+ if (typeof clips !== "string" && !(Array.isArray(clips) && clips.every((clip) => typeof clip === "string")))
70
+ reject(`${path}.oneShots[${JSON.stringify(event)}]`, "One-shot clips must be a string or an array of strings", "a clip-name string or an array of clip-name strings");
71
+ }
72
+ }
73
+ let animation = value;
74
+ if (value.graph !== undefined) {
75
+ const graph = validateAnimGraph(value.graph);
76
+ for (const diagnostic of graph.diagnostics)
77
+ diagnostics.push({ ...diagnostic, path: `${path}.graph${diagnostic.path === "" ? "" : `.${diagnostic.path}`}` });
78
+ if (graph.graph !== undefined)
79
+ animation = { ...value, graph: graph.graph };
80
+ }
81
+ return { animation: diagnostics.length === 0 ? animation : defaults, diagnostics };
82
+ }
83
+ /**
84
+ * Reads the latest immutable document each call, validating only when document identity changes.
85
+ * The caller owns the marker-to-character association and its asset-specific animation defaults.
86
+ * @capability authored-animation consume live authored character animation with bounded per-frame work
87
+ */
88
+ export function createAuthoredAnimationReader(getDocument, markerId, defaults) {
89
+ let previous;
90
+ let result;
91
+ return () => {
92
+ const document = getDocument();
93
+ if (document !== previous) {
94
+ result = readAuthoredAnimation(document, markerId, defaults);
95
+ previous = document;
96
+ }
97
+ return result;
98
+ };
99
+ }
@@ -1,3 +1,4 @@
1
+ import { type AuthoredAnimationDiagnostic } from "./authoredAnimation.js";
1
2
  import type { ModelAnimationConfig } from "../game/playableGame.js";
2
3
  import type { SceneMarkerLike } from "./sceneShapes.js";
3
4
  /** A placement's authored rig animation override — the stored `marker.meta.animation` contract (#1274). */
@@ -53,10 +54,8 @@ export interface PlaceAuthoredObjectsOptions {
53
54
  */
54
55
  export declare function markerCatalogId(marker: AuthoredObjectMarkerLike): string | null;
55
56
  /**
56
- * The authored per-placement rig animation override stored at `marker.meta.animation` (#1274), or
57
- * `undefined` when absent/malformed. `"auto"` / `"none"` pass through as the string modes; any other
58
- * object is a `ModelAnimationConfig` the shell applies verbatim (validated at author time in the
59
- * editor). Runtime trusts the persisted document here exactly like it trusts position and rotation.
57
+ * Validated per-placement animation from `marker.meta.animation`. Missing or malformed overrides
58
+ * return undefined so the placed object retains its catalog animation; valid data replaces it whole.
60
59
  * @capability authored-objects per-placement animation override from marker.meta.animation
61
60
  */
62
61
  export declare function markerAnimation(marker: AuthoredObjectMarkerLike): AuthoredAnimation | undefined;
@@ -80,6 +79,18 @@ export interface ResolveAuthoredObjectsOptions {
80
79
  * @capability authored-objects place catalog mesh props from an editor document
81
80
  */
82
81
  export declare function resolveAuthoredObjects(document: AuthoredObjectsDocumentLike, options?: ResolveAuthoredObjectsOptions): AuthoredObject[];
82
+ /** Resolved catalog props and located repairs for rejected per-placement animation overrides. */
83
+ export interface AuthoredObjectsResult {
84
+ objects: AuthoredObject[];
85
+ diagnostics: readonly AuthoredAnimationDiagnostic[];
86
+ }
87
+ /**
88
+ * Resolves catalog props and validates their animation in one document pass. Rejected overrides
89
+ * are omitted so each model inherits its original catalog setting. Excluded and unplaced markers
90
+ * remain the responsibility of their own consumers.
91
+ * @capability authored-objects resolve static placements with located animation repairs
92
+ */
93
+ export declare function resolveAuthoredObjectsWithDiagnostics(document: AuthoredObjectsDocumentLike, options?: ResolveAuthoredObjectsOptions): AuthoredObjectsResult;
83
94
  /**
84
95
  * Places resolved authored objects into an object store, grounding each on `sampleHeight(x,z)` plus
85
96
  * per-object and options vertical offsets. Returns the instance ids that were placed (or kept).
@@ -88,3 +99,12 @@ export declare function resolveAuthoredObjects(document: AuthoredObjectsDocument
88
99
  export declare function placeAuthoredObjects(store: AuthoredObjectPlaceTarget, objects: readonly AuthoredObject[], sampleHeight: (x: number, z: number) => number, options?: PlaceAuthoredObjectsOptions): string[];
89
100
  /** Convenience: resolve a document then place every authored catalog prop. */
90
101
  export declare function placeAuthoredObjectsFromDocument(store: AuthoredObjectPlaceTarget, document: AuthoredObjectsDocumentLike, sampleHeight: (x: number, z: number) => number, options?: PlaceAuthoredObjectsOptions): string[];
102
+ /**
103
+ * Update changed authored placements and remove previous authored ids absent from the next document.
104
+ * Initial placement keeps game onInit state; unrelated document edits preserve runtime prop state.
105
+ * Call on document changes, never on simulation ticks; runtime-created objects remain untouched.
106
+ * @capability sync-authored-objects synchronize edited catalog prop poses, elevation and removal through the existing object store
107
+ */
108
+ export declare function syncAuthoredObjects(store: AuthoredObjectPlaceTarget & {
109
+ remove(instanceId: string): boolean;
110
+ }, objects: readonly AuthoredObject[], previousObjects: readonly AuthoredObject[], sampleHeight: (x: number, z: number) => number, options?: Pick<PlaceAuthoredObjectsOptions, "verticalOffset">): AuthoredObject[];
@@ -1,3 +1,4 @@
1
+ import { readAuthoredAnimationValue } from "./authoredAnimation.js";
1
2
  function metaString(meta, key) {
2
3
  const value = meta?.[key];
3
4
  return typeof value === "string" && value.length > 0 ? value : null;
@@ -16,23 +17,13 @@ export function markerCatalogId(marker) {
16
17
  return marker.catalogId;
17
18
  return metaString(marker.meta, "catalogId");
18
19
  }
19
- function isRecord(value) {
20
- return typeof value === "object" && value !== null && !Array.isArray(value);
21
- }
22
20
  /**
23
- * The authored per-placement rig animation override stored at `marker.meta.animation` (#1274), or
24
- * `undefined` when absent/malformed. `"auto"` / `"none"` pass through as the string modes; any other
25
- * object is a `ModelAnimationConfig` the shell applies verbatim (validated at author time in the
26
- * editor). Runtime trusts the persisted document here exactly like it trusts position and rotation.
21
+ * Validated per-placement animation from `marker.meta.animation`. Missing or malformed overrides
22
+ * return undefined so the placed object retains its catalog animation; valid data replaces it whole.
27
23
  * @capability authored-objects per-placement animation override from marker.meta.animation
28
24
  */
29
25
  export function markerAnimation(marker) {
30
- const value = marker.meta?.["animation"];
31
- if (value === "auto" || value === "none")
32
- return value;
33
- if (isRecord(value))
34
- return value;
35
- return undefined;
26
+ return readAuthoredAnimationValue(marker.meta?.["animation"], "meta.animation").animation;
36
27
  }
37
28
  function markerVerticalOffset(marker) {
38
29
  const fromMeta = metaNumber(marker.meta, "verticalOffset") ??
@@ -55,15 +46,29 @@ export const ENTITY_MARKER_KINDS = ["mob", "boss"];
55
46
  * @capability authored-objects place catalog mesh props from an editor document
56
47
  */
57
48
  export function resolveAuthoredObjects(document, options = {}) {
49
+ return resolveAuthoredObjectsWithDiagnostics(document, options).objects;
50
+ }
51
+ /**
52
+ * Resolves catalog props and validates their animation in one document pass. Rejected overrides
53
+ * are omitted so each model inherits its original catalog setting. Excluded and unplaced markers
54
+ * remain the responsibility of their own consumers.
55
+ * @capability authored-objects resolve static placements with located animation repairs
56
+ */
57
+ export function resolveAuthoredObjectsWithDiagnostics(document, options = {}) {
58
58
  const excluded = new Set(options.excludeKinds ?? ENTITY_MARKER_KINDS);
59
59
  const out = [];
60
- for (const marker of document.markers) {
60
+ const diagnostics = [];
61
+ for (let index = 0; index < document.markers.length; index += 1) {
62
+ const marker = document.markers[index];
61
63
  if (excluded.has(marker.kind))
62
64
  continue;
63
65
  const catalogId = markerCatalogId(marker);
64
66
  if (catalogId === null)
65
67
  continue;
66
- const animation = markerAnimation(marker);
68
+ const result = readAuthoredAnimationValue(marker.meta?.["animation"], `markers[${index}].meta.animation`);
69
+ const animation = result.animation;
70
+ for (const diagnostic of result.diagnostics)
71
+ diagnostics.push(diagnostic);
67
72
  out.push({
68
73
  catalogId,
69
74
  x: marker.position.x,
@@ -74,7 +79,7 @@ export function resolveAuthoredObjects(document, options = {}) {
74
79
  ...(animation === undefined ? {} : { animation }),
75
80
  });
76
81
  }
77
- return out;
82
+ return { objects: out, diagnostics };
78
83
  }
79
84
  /**
80
85
  * Places resolved authored objects into an object store, grounding each on `sampleHeight(x,z)` plus
@@ -100,3 +105,25 @@ export function placeAuthoredObjects(store, objects, sampleHeight, options = {})
100
105
  export function placeAuthoredObjectsFromDocument(store, document, sampleHeight, options = {}) {
101
106
  return placeAuthoredObjects(store, resolveAuthoredObjects(document), sampleHeight, options);
102
107
  }
108
+ /**
109
+ * Update changed authored placements and remove previous authored ids absent from the next document.
110
+ * Initial placement keeps game onInit state; unrelated document edits preserve runtime prop state.
111
+ * Call on document changes, never on simulation ticks; runtime-created objects remain untouched.
112
+ * @capability sync-authored-objects synchronize edited catalog prop poses, elevation and removal through the existing object store
113
+ */
114
+ export function syncAuthoredObjects(store, objects, previousObjects, sampleHeight, options = {}) {
115
+ const nextIds = new Set(objects.map((object) => object.instanceId));
116
+ if (nextIds.size !== objects.length)
117
+ throw new Error("Authored object ids must be unique");
118
+ const previous = new Map(previousObjects.map((object) => [object.instanceId, object]));
119
+ for (const object of objects) {
120
+ const before = previous.get(object.instanceId);
121
+ if (before !== undefined && JSON.stringify(before) === JSON.stringify(object))
122
+ continue;
123
+ placeAuthoredObjects(store, [object], sampleHeight, { ...options, onExisting: before === undefined ? "keep" : "replace" });
124
+ }
125
+ for (const object of previousObjects)
126
+ if (!nextIds.has(object.instanceId))
127
+ store.remove(object.instanceId);
128
+ return objects.map((object) => ({ ...object, ...(typeof object.animation === "object" ? { animation: structuredClone(object.animation) } : {}) }));
129
+ }
@@ -0,0 +1,74 @@
1
+ import { type FlockStepAgent } from "../ai/flock.js";
2
+ import type { EditorDocument } from "../editor/types.js";
3
+ import { type AuthoredWeatherSample, type WeatherExposure, type WeatherExposureSnapshot, type WeatherExposureSurface } from "./authoredWeather.js";
4
+ import { type FireCell, type FireGrid } from "./weather.js";
5
+ import type { ParticleDirector } from "../vfx/particleDirector.js";
6
+ import type { Vec3 } from "../vfx/particles.js";
7
+ /** A watched surface is sampled in metres. Its accumulation survives runtime save/restore. */
8
+ export interface AuthoredSurface extends WeatherExposureSurface {
9
+ y?: number;
10
+ }
11
+ /** Appearance adapters consume these units without owning weather state or its clock. */
12
+ export interface EnvironmentAppearanceSignals {
13
+ timeSeconds: number;
14
+ wind: Vec3;
15
+ wetness: number;
16
+ snow: number;
17
+ exposure: number;
18
+ /** Normalized local fire heat, independent of the temperature offset in degrees Celsius. */
19
+ heat: number;
20
+ temperatureOffsetC: number;
21
+ }
22
+ /** Runtime state excludes authored configuration and cosmetic particle pools. */
23
+ export interface AuthoredSimulationSnapshot {
24
+ surfaces: WeatherExposureSnapshot;
25
+ watched: AuthoredSurface[];
26
+ fires: {
27
+ id: string;
28
+ cells: FireCell[];
29
+ }[];
30
+ flocks: {
31
+ id: string;
32
+ agents: FlockStepAgent[];
33
+ routeIndex: number;
34
+ }[];
35
+ }
36
+ /** A habitat's live poses; game species determine appearance and gameplay actor policy. */
37
+ export interface AuthoredFlockState {
38
+ readonly id: string;
39
+ readonly species: string;
40
+ readonly role: "cosmetic" | "gameplay";
41
+ readonly agents: readonly Readonly<FlockStepAgent>[];
42
+ readonly routeIndex: number;
43
+ }
44
+ /** Engine-owned authority uses the caller's simulation clock; render quality cannot change it. */
45
+ export interface AuthoredSimulationRuntime {
46
+ sample(x: number, z: number): AuthoredWeatherSample;
47
+ appearanceAt(surfaceId: string, position: Vec3): EnvironmentAppearanceSignals;
48
+ /** Fraction of open sky from bounded authored shelter volumes. World Y points up. */
49
+ exposureAt(x: number, y: number, z: number): number;
50
+ readonly surfaces: WeatherExposure;
51
+ watch(surface: AuthoredSurface): void;
52
+ forget(id: string): void;
53
+ /** XZ wind adapted to [x,0,z]; response is 1/second, cap is metres/second² (both 0..1e12). */
54
+ windAcceleration(position: Vec3, response: number, maxAcceleration: number): Vec3;
55
+ /** Sum authored fields and wind (response 1/second), capped in metres/second² by game policy. */
56
+ accelerationAt(position: Vec3, timeSeconds: number, windResponse: number, maxAcceleration: number, mask?: number, out?: [number, number, number]): Vec3;
57
+ fire(id: string): FireGrid | undefined;
58
+ /** Cosmetic poses render directly; gameplay habitats are consumed by game actor adapters. */
59
+ flocks(): readonly AuthoredFlockState[];
60
+ step(gameDt: number): void;
61
+ retune(document: EditorDocument): void;
62
+ snapshot(): AuthoredSimulationSnapshot;
63
+ restore(state: AuthoredSimulationSnapshot): void;
64
+ dispose(): void;
65
+ }
66
+ /** Compose authored weather, shelter, accumulation, named effect intents and fire with one clock.
67
+ * @capability authored-environment compose authored weather, shelter, accumulation, fire, flocks and effects using one simulation clock
68
+ */
69
+ export declare function createAuthoredSimulation(options: {
70
+ document?: EditorDocument;
71
+ timeSeconds: () => number;
72
+ particles?: ParticleDirector;
73
+ maxSurfaces?: number;
74
+ }): AuthoredSimulationRuntime;