@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,3 +1,7 @@
1
+ import { decodeEditorSimulation } from "./simulation.js";
2
+ import { parseMaterialAssignments, validateMaterialAsset, validateMaterialAssignments } from "../material/materialAsset.js";
3
+ import { authoredHiddenNodes } from "./modelNodeVisibility.js";
4
+ import { parseStaticPrefabBake } from "./staticPrefab.js";
1
5
  import { cloneEditorUiDocument, decodeEditorUiDocument } from "../ui/hudDocument.js";
2
6
  import { migrateTerrainSnapshot } from "../world/terraform.js";
3
7
  import { cloneGridLayer, migrateGridLayer, } from "./grid.js";
@@ -19,6 +23,8 @@ function cloneEnvironment(environment) {
19
23
  ...(environment.zenithColor === undefined ? {} : { zenithColor: environment.zenithColor }),
20
24
  ...(environment.sunIntensity === undefined ? {} : { sunIntensity: environment.sunIntensity }),
21
25
  ...(environment.ambientIntensity === undefined ? {} : { ambientIntensity: environment.ambientIntensity }),
26
+ ...(environment.sunAzimuth === undefined ? {} : { sunAzimuth: environment.sunAzimuth }),
27
+ ...(environment.sunElevation === undefined ? {} : { sunElevation: environment.sunElevation }),
22
28
  ...(environment.fog === undefined ? {} : { fog: { ...environment.fog } }),
23
29
  };
24
30
  }
@@ -34,7 +40,7 @@ function cloneDirective(directive) {
34
40
  ...(directive.region === undefined ? {} : { region: directive.region }),
35
41
  ...(area === undefined ? {} : { area }),
36
42
  ...(directive.seed === undefined ? {} : { seed: directive.seed }),
37
- ...(directive.meta === undefined ? {} : { meta: { ...directive.meta } }),
43
+ ...(directive.meta === undefined ? {} : { meta: structuredClone(directive.meta) }),
38
44
  };
39
45
  if (directive.kind === "scatter") {
40
46
  return { ...base, kind: "scatter", asset: directive.asset, density: directive.density,
@@ -66,6 +72,8 @@ function upsertGrids(base, overlay) {
66
72
  /** Spread-ready non-placeable fields preserved across document rebuilds. @internal */
67
73
  export function editorDocumentExtras(doc) {
68
74
  return {
75
+ ...(doc.simulation === undefined ? {} : { simulation: structuredClone(doc.simulation) }),
76
+ ...(doc.materialAssets === undefined ? {} : { materialAssets: doc.materialAssets }),
69
77
  prefabs: doc.prefabs,
70
78
  collections: doc.collections,
71
79
  catalogs: doc.catalogs,
@@ -97,14 +105,14 @@ function cloneCatalogEntry(entry) {
97
105
  return {
98
106
  id: entry.id,
99
107
  ...(entry.label === undefined ? {} : { label: entry.label }),
100
- ...(entry.meta === undefined ? {} : { meta: { ...entry.meta } }),
108
+ ...(entry.meta === undefined ? {} : { meta: structuredClone(entry.meta) }),
101
109
  };
102
110
  }
103
111
  function cloneCatalogs(catalogs) {
104
112
  return (catalogs ?? []).map((catalog) => ({
105
113
  id: catalog.id,
106
114
  ...(catalog.label === undefined ? {} : { label: catalog.label }),
107
- ...(catalog.schema === undefined ? {} : { schema: catalog.schema }),
115
+ ...(catalog.schema === undefined ? {} : { schema: structuredClone(catalog.schema) }),
108
116
  entries: catalog.entries.map(cloneCatalogEntry),
109
117
  }));
110
118
  }
@@ -119,38 +127,36 @@ export function cloneEditorDocument(doc) {
119
127
  const directives = cloneDirectives(doc.directives);
120
128
  return {
121
129
  version: 1,
130
+ ...(doc.simulation === undefined ? {} : { simulation: structuredClone(doc.simulation) }),
122
131
  markers: doc.markers.map((marker) => ({
123
132
  ...marker,
124
133
  position: { ...marker.position },
125
- ...(marker.meta === undefined ? {} : { meta: { ...marker.meta } }),
134
+ ...(marker.meta === undefined ? {} : { meta: structuredClone(marker.meta) }),
126
135
  })),
127
136
  volumes: doc.volumes.map((volume) => ({
128
137
  ...volume,
129
138
  center: { ...volume.center },
130
139
  ...(volume.halfExtents === undefined ? {} : { halfExtents: { ...volume.halfExtents } }),
131
- ...(volume.meta === undefined ? {} : { meta: { ...volume.meta } }),
140
+ ...(volume.meta === undefined ? {} : { meta: structuredClone(volume.meta) }),
132
141
  })),
133
142
  paths: doc.paths.map((path) => ({
134
143
  ...path,
135
144
  points: path.points.map((point) => ({ ...point })),
136
- ...(path.meta === undefined ? {} : { meta: { ...path.meta } }),
145
+ ...(path.meta === undefined ? {} : { meta: structuredClone(path.meta) }),
137
146
  })),
138
147
  annotations: doc.annotations.map((note) => ({
139
148
  ...note,
140
149
  position: { ...note.position },
141
- ...(note.meta === undefined ? {} : { meta: { ...note.meta } }),
150
+ ...(note.meta === undefined ? {} : { meta: structuredClone(note.meta) }),
142
151
  })),
143
152
  prefabs: doc.prefabs.map((prefab) => ({
144
153
  ...prefab,
145
- fragment: {
146
- markers: prefab.fragment.markers.map((marker) => ({ ...marker, position: { ...marker.position } })),
147
- volumes: prefab.fragment.volumes.map((volume) => ({ ...volume, center: { ...volume.center } })),
148
- paths: prefab.fragment.paths.map((path) => ({ ...path, points: path.points.map((point) => ({ ...point })) })),
149
- annotations: prefab.fragment.annotations.map((note) => ({ ...note, position: { ...note.position } })),
150
- },
154
+ ...(prefab.staticBake === undefined ? {} : { staticBake: parseStaticPrefabBake(prefab.staticBake) }),
155
+ fragment: structuredClone(prefab.fragment),
151
156
  })),
152
157
  collections: doc.collections.map((collection) => ({ ...collection, memberIds: [...collection.memberIds] })),
153
158
  catalogs: cloneCatalogs(doc.catalogs),
159
+ ...(doc.materialAssets === undefined ? {} : { materialAssets: structuredClone(doc.materialAssets) }),
154
160
  ...(doc.grids === undefined ? {} : { grids: doc.grids.map(cloneGridLayer) }),
155
161
  ...(doc.terrain === undefined ? {} : { terrain: doc.terrain }),
156
162
  ...(ui === undefined ? {} : { ui }),
@@ -172,7 +178,7 @@ function upsertCatalogs(base, overlay) {
172
178
  {
173
179
  id: catalog.id,
174
180
  ...(catalog.label === undefined ? {} : { label: catalog.label }),
175
- ...(catalog.schema === undefined ? {} : { schema: catalog.schema }),
181
+ ...(catalog.schema === undefined ? {} : { schema: structuredClone(catalog.schema) }),
176
182
  entries: catalog.entries.map(cloneCatalogEntry),
177
183
  },
178
184
  ]));
@@ -202,6 +208,7 @@ export function normalizeEditorLayers(input) {
202
208
  const directives = cloneDirectives(resolved.directives);
203
209
  return {
204
210
  version: 1,
211
+ ...(resolved.simulation === undefined ? {} : { simulation: decodeEditorSimulation(resolved.simulation, { paths: asArray(resolved.paths) }) }),
205
212
  markers: asArray(resolved.markers),
206
213
  volumes: asArray(resolved.volumes),
207
214
  paths: asArray(resolved.paths),
@@ -209,6 +216,7 @@ export function normalizeEditorLayers(input) {
209
216
  prefabs: asArray(resolved.prefabs),
210
217
  collections: asArray(resolved.collections),
211
218
  catalogs: cloneCatalogs(resolved.catalogs),
219
+ ...(resolved.materialAssets === undefined ? {} : { materialAssets: structuredClone(resolved.materialAssets) }),
212
220
  ...(grids === undefined ? {} : { grids }),
213
221
  ...(resolved.terrain === undefined ? {} : { terrain: migrateTerrainSnapshot(resolved.terrain) }),
214
222
  ...(ui === undefined ? {} : { ui }),
@@ -260,6 +268,8 @@ export function mergeEditorDocuments(...docs) {
260
268
  out.prefabs.push(...doc.prefabs);
261
269
  out.collections.push(...doc.collections);
262
270
  out.catalogs = upsertCatalogs(out.catalogs, doc.catalogs);
271
+ if (doc.materialAssets !== undefined)
272
+ out.materialAssets = structuredClone(upsertById(out.materialAssets ?? [], doc.materialAssets));
263
273
  const mergedGrids = upsertGrids(out.grids, doc.grids);
264
274
  if (mergedGrids !== undefined)
265
275
  out.grids = mergedGrids;
@@ -269,6 +279,8 @@ export function mergeEditorDocuments(...docs) {
269
279
  out.minimap = cloneMinimapBake(doc.minimap);
270
280
  if (doc.environment !== undefined)
271
281
  out.environment = cloneEnvironment(doc.environment);
282
+ if (doc.simulation !== undefined)
283
+ out.simulation = structuredClone(doc.simulation);
272
284
  if (doc.directives !== undefined) {
273
285
  out.directives = upsertById(out.directives ?? [], doc.directives);
274
286
  }
@@ -290,6 +302,7 @@ export function extractEditorFragment(doc, ids) {
290
302
  const wanted = new Set(ids);
291
303
  return cloneEditorDocument({
292
304
  version: 1,
305
+ ...(doc.materialAssets === undefined ? {} : { materialAssets: doc.materialAssets.filter((asset) => doc.markers.some((marker) => wanted.has(marker.id) && parseMaterialAssignments(marker.meta?.materialAssignments ?? []).some((item) => item.materialId === asset.id))) }),
293
306
  markers: doc.markers.filter((marker) => wanted.has(marker.id)),
294
307
  volumes: doc.volumes.filter((volume) => wanted.has(volume.id)),
295
308
  paths: doc.paths.filter((path) => wanted.has(path.id)),
@@ -433,6 +446,7 @@ export function createPrefabFragment(doc, ids) {
433
446
  z: point.z - origin.z,
434
447
  });
435
448
  return {
449
+ ...(extracted.materialAssets === undefined ? {} : { materialAssets: extracted.materialAssets }),
436
450
  markers: extracted.markers.map((marker) => ({ ...marker, position: shift(marker.position) })),
437
451
  volumes: extracted.volumes.map((volume) => ({ ...volume, center: shift(volume.center) })),
438
452
  paths: extracted.paths.map((path) => ({ ...path, points: path.points.map(shift) })),
@@ -606,7 +620,21 @@ function decodeMeta(value, path, errors) {
606
620
  errors.push({ path, message: "expected an object" });
607
621
  return undefined;
608
622
  }
609
- return value;
623
+ if (value.materialAssignments !== undefined) {
624
+ try {
625
+ parseMaterialAssignments(value.materialAssignments);
626
+ }
627
+ catch (error) {
628
+ errors.push({ path: `${path}.materialAssignments`, message: String(error) });
629
+ }
630
+ }
631
+ try {
632
+ authoredHiddenNodes(value);
633
+ }
634
+ catch (error) {
635
+ errors.push({ path: `${path}.hiddenNodes`, message: String(error) });
636
+ }
637
+ return structuredClone(value);
610
638
  }
611
639
  function decodeMarker(item, path, errors) {
612
640
  if (!isPlainObject(item)) {
@@ -742,18 +770,55 @@ function decodeNote(item, path, errors) {
742
770
  note.meta = meta;
743
771
  return note;
744
772
  }
773
+ function decodeMaterialAssets(value, path, errors) {
774
+ if (!Array.isArray(value)) {
775
+ errors.push({ path, message: "expected material assets" });
776
+ return [];
777
+ }
778
+ const ids = new Set();
779
+ const assets = [];
780
+ value.forEach((asset, index) => {
781
+ const diagnostics = validateMaterialAsset(asset).filter((diagnostic) => diagnostic.severity === "error");
782
+ for (const diagnostic of diagnostics)
783
+ errors.push({ path: `${path}[${index}].${diagnostic.path}`, message: diagnostic.message });
784
+ if (diagnostics.length === 0) {
785
+ if (ids.has(asset.id))
786
+ errors.push({ path: `${path}[${index}].id`, message: "duplicate material ID" });
787
+ ids.add(asset.id);
788
+ assets.push(structuredClone(asset));
789
+ }
790
+ });
791
+ return assets;
792
+ }
745
793
  function decodeFragmentContent(value, path, errors) {
746
794
  if (!isPlainObject(value)) {
747
795
  errors.push({ path, message: "expected an object" });
748
796
  return null;
749
797
  }
798
+ const materialAssets = value.materialAssets === undefined ? undefined : decodeMaterialAssets(value.materialAssets, `${path}.materialAssets`, errors);
799
+ const markers = decodeArray(value.markers, `${path}.markers`, decodeMarker, errors);
800
+ validateMarkerMaterialReferences(markers, materialAssets ?? [], path, errors);
750
801
  return {
751
- markers: decodeArray(value.markers, `${path}.markers`, decodeMarker, errors),
802
+ ...(materialAssets === undefined ? {} : { materialAssets }),
803
+ markers,
752
804
  volumes: decodeArray(value.volumes, `${path}.volumes`, decodeVolume, errors),
753
805
  paths: decodeArray(value.paths, `${path}.paths`, decodePath, errors),
754
806
  annotations: decodeArray(value.annotations, `${path}.annotations`, decodeNote, errors),
755
807
  };
756
808
  }
809
+ function validateMarkerMaterialReferences(markers, assets, path, errors) {
810
+ for (const [index, marker] of markers.entries()) {
811
+ if (marker.meta?.materialAssignments === undefined)
812
+ continue;
813
+ try {
814
+ for (const diagnostic of validateMaterialAssignments(parseMaterialAssignments(marker.meta.materialAssignments), assets)) {
815
+ if (diagnostic.severity === "error")
816
+ errors.push({ path: `${path}.markers[${index}].meta.${diagnostic.path}`, message: diagnostic.message });
817
+ }
818
+ }
819
+ catch { }
820
+ }
821
+ }
757
822
  function decodePrefab(item, path, errors) {
758
823
  if (!isPlainObject(item)) {
759
824
  errors.push({ path, message: "expected an object" });
@@ -766,7 +831,16 @@ function decodePrefab(item, path, errors) {
766
831
  const fragment = decodeFragmentContent(item.fragment, `${path}.fragment`, errors);
767
832
  if (typeof item.id !== "string" || typeof item.name !== "string" || fragment === null)
768
833
  return null;
769
- return { id: item.id, name: item.name, fragment };
834
+ const prefab = { id: item.id, name: item.name, fragment };
835
+ if (item.staticBake !== undefined) {
836
+ try {
837
+ prefab.staticBake = parseStaticPrefabBake(item.staticBake);
838
+ }
839
+ catch (error) {
840
+ errors.push({ path: `${path}.staticBake`, message: error instanceof Error ? error.message : String(error) });
841
+ }
842
+ }
843
+ return prefab;
770
844
  }
771
845
  function decodeCollection(item, path, errors) {
772
846
  if (!isPlainObject(item)) {
@@ -1082,7 +1156,7 @@ function decodeEnvironment(value, path, errors) {
1082
1156
  failed = true;
1083
1157
  }
1084
1158
  }
1085
- for (const key of ["sunIntensity", "ambientIntensity"]) {
1159
+ for (const key of ["sunIntensity", "ambientIntensity", "sunAzimuth", "sunElevation"]) {
1086
1160
  if (value[key] === undefined)
1087
1161
  continue;
1088
1162
  if (typeof value[key] === "number" && Number.isFinite(value[key]))
@@ -1226,6 +1300,7 @@ export function decodeEditorDocument(raw) {
1226
1300
  return { ok: false, errors: [{ path: "$", message: "editor document must be an object" }] };
1227
1301
  }
1228
1302
  const errors = [];
1303
+ const materialAssets = raw.materialAssets === undefined ? undefined : decodeMaterialAssets(raw.materialAssets, "$.materialAssets", errors);
1229
1304
  const markers = decodeArray(raw.markers, "$.markers", decodeMarker, errors);
1230
1305
  const volumes = decodeArray(raw.volumes, "$.volumes", decodeVolume, errors);
1231
1306
  const paths = decodeArray(raw.paths, "$.paths", decodePath, errors);
@@ -1241,6 +1316,15 @@ export function decodeEditorDocument(raw) {
1241
1316
  const minimap = raw.minimap === undefined ? undefined : decodeMinimapBake(raw.minimap, "$.minimap", errors);
1242
1317
  const bakes = raw.bakes === undefined ? undefined : decodeBakes(raw.bakes, "$.bakes", errors);
1243
1318
  const environment = raw.environment === undefined ? undefined : decodeEnvironment(raw.environment, "$.environment", errors);
1319
+ let simulation;
1320
+ if (raw.simulation !== undefined) {
1321
+ try {
1322
+ simulation = decodeEditorSimulation(raw.simulation, { paths });
1323
+ }
1324
+ catch (error) {
1325
+ errors.push({ path: "$.simulation", message: error instanceof Error ? error.message : String(error) });
1326
+ }
1327
+ }
1244
1328
  // Placeable object ids form one document-global namespace (selection, parenting, and removal all
1245
1329
  // treat them that way), so a document that reuses an id — even across two different collections —
1246
1330
  // is malformed and rejected here with the offending path, rather than silently loading a scene
@@ -1262,6 +1346,7 @@ export function decodeEditorDocument(raw) {
1262
1346
  }
1263
1347
  });
1264
1348
  }
1349
+ validateMarkerMaterialReferences(markers, materialAssets ?? [], "$", errors);
1265
1350
  if (errors.length > 0)
1266
1351
  return { ok: false, errors };
1267
1352
  const terrain = raw.terrain === undefined ? undefined : migrateTerrainSnapshot(raw.terrain);
@@ -1270,6 +1355,8 @@ export function decodeEditorDocument(raw) {
1270
1355
  ok: true,
1271
1356
  document: {
1272
1357
  version: 1,
1358
+ ...(simulation === undefined ? {} : { simulation }),
1359
+ ...(materialAssets === undefined ? {} : { materialAssets }),
1273
1360
  markers,
1274
1361
  volumes,
1275
1362
  paths,
@@ -1313,6 +1400,7 @@ export function applyEditorDocumentOverlay(base, overlay) {
1313
1400
  const terrain = overlay.terrain ?? base.terrain;
1314
1401
  const minimap = overlay.minimap ?? base.minimap;
1315
1402
  const environment = overlay.environment ?? base.environment;
1403
+ const simulation = overlay.simulation ?? base.simulation;
1316
1404
  const ui = overlay.ui === undefined
1317
1405
  ? cloneEditorUiDocument(base.ui)
1318
1406
  : {
@@ -1324,6 +1412,7 @@ export function applyEditorDocumentOverlay(base, overlay) {
1324
1412
  const grids = upsertGrids(base.grids, overlay.grids);
1325
1413
  return {
1326
1414
  version: 1,
1415
+ ...(simulation === undefined ? {} : { simulation: structuredClone(simulation) }),
1327
1416
  markers: upsertById(base.markers, overlay.markers),
1328
1417
  volumes: upsertById(base.volumes, overlay.volumes),
1329
1418
  paths: upsertById(base.paths, overlay.paths),
@@ -1331,6 +1420,7 @@ export function applyEditorDocumentOverlay(base, overlay) {
1331
1420
  prefabs: upsertById(base.prefabs, overlay.prefabs),
1332
1421
  collections: upsertById(base.collections, overlay.collections),
1333
1422
  catalogs: upsertCatalogs(base.catalogs, overlay.catalogs),
1423
+ ...(base.materialAssets === undefined && overlay.materialAssets === undefined ? {} : { materialAssets: structuredClone(upsertById(base.materialAssets ?? [], overlay.materialAssets ?? [])) }),
1334
1424
  ...(grids === undefined ? {} : { grids }),
1335
1425
  ...(terrain === undefined ? {} : { terrain }),
1336
1426
  ...(ui === undefined ? {} : { ui }),
@@ -1380,6 +1470,17 @@ export function editorDocumentBounds(doc) {
1380
1470
  for (const note of doc.annotations) {
1381
1471
  include(note.position.x, note.position.y, note.position.z);
1382
1472
  }
1473
+ if (doc.terrain !== undefined) {
1474
+ const terrain = doc.terrain;
1475
+ let low = 0;
1476
+ let high = 0;
1477
+ for (const offset of terrain.offsets) {
1478
+ low = Math.min(low, offset);
1479
+ high = Math.max(high, offset);
1480
+ }
1481
+ include(terrain.bounds.minX, low, terrain.bounds.minZ);
1482
+ include(terrain.bounds.maxX, high, terrain.bounds.maxZ);
1483
+ }
1383
1484
  if (!any)
1384
1485
  return null;
1385
1486
  return {
@@ -25,6 +25,14 @@ export function skyConfigFromEnvironment(env) {
25
25
  ...(env.zenithColor === undefined ? {} : { zenithColor: env.zenithColor }),
26
26
  ...(env.sunIntensity === undefined ? {} : { sunIntensity: env.sunIntensity }),
27
27
  ...(env.ambientIntensity === undefined ? {} : { ambientIntensity: env.ambientIntensity }),
28
+ ...(env.sunAzimuth === undefined && env.sunElevation === undefined
29
+ ? {}
30
+ : {
31
+ sun: {
32
+ ...(env.sunAzimuth === undefined ? {} : { azimuth: env.sunAzimuth }),
33
+ ...(env.sunElevation === undefined ? {} : { elevation: env.sunElevation }),
34
+ },
35
+ }),
28
36
  ...(env.fog === undefined ? {} : { fog: { ...env.fog } }),
29
37
  };
30
38
  }
@@ -90,6 +90,8 @@ export type { EditorDispatchOptions } from "./commands.js";
90
90
  export type { EditorSession } from "./commands.js";
91
91
  /** The document plus current selection at a point in editor history. */
92
92
  export type { EditorSessionState } from "./commands.js";
93
+ /** Atomic command-batch outcome; a failure identifies the zero-based command index. */
94
+ export type { EditorTransactionResult } from "./commands.js";
93
95
  export { applyDocumentPatch, applyRuntimeStateDelta, createDocumentLiveSync, getDocumentLiveSync, installDocumentLiveSync, runtimeEntityWriteBackCommand, subscribeDocumentLiveSyncInstall, } from "./liveSync.js";
94
96
  /** Versioned document mutation on the editorΓåöruntime live-sync stream. */
95
97
  export type { DocumentPatch, ApplyDocumentPatchResult } from "./liveSync.js";
@@ -4,7 +4,8 @@ import type { EditorDocument, EditorVec3 } from "./types.js";
4
4
  * One versioned document mutation on the live-sync stream. `snapshot` replaces the whole
5
5
  * document; `commands` replays structural editor commands onto the current document.
6
6
  * `baseRevision` must match the receiver's current revision unless `force` is set (document
7
- * authority from the editor).
7
+ * authority from the editor). `revision` preserves the authority cursor when replaying: snapshots
8
+ * may jump forward for reconnect recovery; commands must be the next revision.
8
9
  */
9
10
  export type DocumentPatch = {
10
11
  type: "snapshot";
@@ -26,6 +27,7 @@ export type ApplyDocumentPatchResult = {
26
27
  } | {
27
28
  ok: false;
28
29
  error: string;
30
+ commandIndex?: number;
29
31
  };
30
32
  /** One live entity row the runtime may stream to the editor (play-mode inspector feed). */
31
33
  export interface RuntimeEntityState {
@@ -92,6 +94,7 @@ export interface DocumentLiveSync {
92
94
  }): ApplyDocumentPatchResult;
93
95
  /** Document-authoritative full replace (editor session mirror). */
94
96
  replaceDocument(document: EditorDocument): DocumentLiveEvent;
97
+ /** Returns a current snapshot when the requested revision predates retained history. */
95
98
  pullPatches(sinceRevision: number): DocumentPatch[];
96
99
  subscribeDocument(listener: (event: DocumentLiveEvent) => void): () => void;
97
100
  getRuntimeState(): RuntimeStateSnapshot;
@@ -8,6 +8,15 @@ const MAX_RUNTIME_LOG = 128;
8
8
  * @capability editor-live-sync apply a versioned document patch onto a live scene document
9
9
  */
10
10
  export function applyDocumentPatch(document, revision, patch, options) {
11
+ if (!Number.isSafeInteger(patch.baseRevision) || patch.baseRevision < 0) {
12
+ return { ok: false, error: "baseRevision must be a nonnegative safe integer" };
13
+ }
14
+ if (patch.revision !== undefined && (!Number.isSafeInteger(patch.revision) || patch.revision <= revision)) {
15
+ return { ok: false, error: "revision must be a safe integer newer than the current revision" };
16
+ }
17
+ if (patch.type === "commands" && patch.revision !== undefined && patch.revision !== revision + 1) {
18
+ return { ok: false, error: "commands revision must immediately follow the current revision" };
19
+ }
11
20
  if (!options?.force && patch.baseRevision !== revision) {
12
21
  return {
13
22
  ok: false,
@@ -16,7 +25,7 @@ export function applyDocumentPatch(document, revision, patch, options) {
16
25
  }
17
26
  if (patch.type === "snapshot") {
18
27
  const nextDoc = cloneEditorDocument(patch.document);
19
- const nextRevision = revision + 1;
28
+ const nextRevision = patch.revision ?? revision + 1;
20
29
  return {
21
30
  ok: true,
22
31
  document: nextDoc,
@@ -24,15 +33,17 @@ export function applyDocumentPatch(document, revision, patch, options) {
24
33
  patch: { ...patch, revision: nextRevision, document: nextDoc },
25
34
  };
26
35
  }
27
- if (patch.commands.length === 0) {
36
+ if (!Array.isArray(patch.commands) || patch.commands.length === 0) {
28
37
  return { ok: false, error: "commands patch is empty" };
29
38
  }
30
- const session = createEditorSession(cloneEditorDocument(document));
31
- for (const command of patch.commands) {
32
- session.dispatch(command);
33
- }
34
- const nextDoc = session.getState().document;
35
- const nextRevision = revision + 1;
39
+ const session = createEditorSession(document);
40
+ const before = session.getState().document;
41
+ const result = session.transaction(patch.commands);
42
+ if (!result.ok)
43
+ return result;
44
+ const changed = result.state.document !== before;
45
+ const nextDoc = changed ? result.state.document : document;
46
+ const nextRevision = changed ? revision + 1 : revision;
36
47
  return {
37
48
  ok: true,
38
49
  document: nextDoc,
@@ -146,7 +157,8 @@ export function createDocumentLiveSync(initial) {
146
157
  const result = applyDocumentPatch(document, revision, patch, options);
147
158
  if (!result.ok)
148
159
  return result;
149
- commit(result);
160
+ if (result.revision !== revision)
161
+ commit(result);
150
162
  return result;
151
163
  },
152
164
  replaceDocument(next) {
@@ -157,7 +169,11 @@ export function createDocumentLiveSync(initial) {
157
169
  return commit(result);
158
170
  },
159
171
  pullPatches(sinceRevision) {
160
- return patchLog.filter((patch) => (patch.revision ?? 0) > sinceRevision);
172
+ const patches = patchLog.filter((patch) => (patch.revision ?? 0) > sinceRevision);
173
+ if (patches.length > 0 && patches[0].baseRevision !== sinceRevision) {
174
+ return [{ type: "snapshot", baseRevision: sinceRevision, revision, document: cloneEditorDocument(document) }];
175
+ }
176
+ return patches;
161
177
  },
162
178
  subscribeDocument(listener) {
163
179
  documentListeners.add(listener);
@@ -0,0 +1,14 @@
1
+ import type { ModelConfig } from "../game/playableGame.js";
2
+ import { type MaterialAsset, type MaterialAssignment, type MaterialSelector } from "../material/materialAsset.js";
3
+ import type { EditorDocument } from "./types.js";
4
+ /** Validate and copy a material asset at the editor boundary. */
5
+ export declare function parseEditorMaterialAsset(value: unknown): MaterialAsset;
6
+ /**
7
+ * Read named assignments from authored marker metadata without inferring whole-model overrides.
8
+ * @capability authored-material-slots read validated material assignments from editor marker metadata
9
+ */
10
+ export declare function authoredMaterialAssignments(meta: Record<string, unknown> | undefined): MaterialAssignment[];
11
+ /** Compare selector meaning independently of serialized property order. @internal */
12
+ export declare function sameMaterialSelector(a: MaterialSelector, b: MaterialSelector): boolean;
13
+ /** Apply authored references; document assets and selectors override model defaults of the same identity. */
14
+ export declare function modelWithAuthoredMaterials(model: ModelConfig | undefined, document: Pick<EditorDocument, "markers" | "materialAssets">, instanceId: string): ModelConfig | undefined;
@@ -0,0 +1,38 @@
1
+ import { parseMaterialAssignments, validateMaterialAsset, validateMaterialAssignments } from "../material/materialAsset.js";
2
+ /** Validate and copy a material asset at the editor boundary. */
3
+ export function parseEditorMaterialAsset(value) {
4
+ const errors = validateMaterialAsset(value).filter((item) => item.severity === "error");
5
+ if (errors.length > 0)
6
+ throw new TypeError(errors.map((item) => `${item.path}: ${item.message}`).join("; "));
7
+ return structuredClone(value);
8
+ }
9
+ /**
10
+ * Read named assignments from authored marker metadata without inferring whole-model overrides.
11
+ * @capability authored-material-slots read validated material assignments from editor marker metadata
12
+ */
13
+ export function authoredMaterialAssignments(meta) {
14
+ return meta?.materialAssignments === undefined ? [] : parseMaterialAssignments(meta.materialAssignments);
15
+ }
16
+ /** Compare selector meaning independently of serialized property order. @internal */
17
+ export function sameMaterialSelector(a, b) {
18
+ return a.mesh === b.mesh && a.slot === b.slot && a.slotIndex === b.slotIndex;
19
+ }
20
+ /** Apply authored references; document assets and selectors override model defaults of the same identity. */
21
+ export function modelWithAuthoredMaterials(model, document, instanceId) {
22
+ if (model === undefined)
23
+ return model;
24
+ const marker = document.markers.find((item) => item.id === instanceId);
25
+ const assignments = authoredMaterialAssignments(marker?.meta);
26
+ const modelAssignments = model.materialAssignments ?? [];
27
+ if (assignments.length === 0 && modelAssignments.length === 0)
28
+ return model;
29
+ const assetsById = new Map((model.materialAssets ?? []).map((asset) => [asset.id, asset]));
30
+ for (const asset of document.materialAssets ?? [])
31
+ assetsById.set(asset.id, asset);
32
+ const assets = [...assetsById.values()];
33
+ const resolvedAssignments = [...modelAssignments.filter((item) => !assignments.some((authored) => sameMaterialSelector(item.selector, authored.selector))), ...assignments];
34
+ const errors = validateMaterialAssignments(resolvedAssignments, assets).filter((item) => item.severity === "error");
35
+ if (errors.length > 0)
36
+ throw new TypeError(errors.map((item) => `${item.path}: ${item.message}`).join("; "));
37
+ return { ...model, materialAssets: assets, materialAssignments: resolvedAssignments };
38
+ }
@@ -0,0 +1,11 @@
1
+ import type { ModelConfig } from "../game/playableGame.js";
2
+ import type { EditorMarker } from "./types.js";
3
+ /** Validate and copy exact node names; an empty list selects no hidden subtrees. */
4
+ export declare function parseModelHiddenNodes(value: unknown): readonly string[] | undefined;
5
+ /**
6
+ * Read marker visibility metadata; an empty list clears model defaults.
7
+ * @capability model-node-visibility read authored per-placement model subtree selections
8
+ */
9
+ export declare function authoredHiddenNodes(meta: Record<string, unknown> | undefined): readonly string[] | undefined;
10
+ /** Apply a placement's authored visibility without erasing omitted game model defaults. */
11
+ export declare function modelWithAuthoredNodeVisibility(model: ModelConfig | undefined, marker: Pick<EditorMarker, "meta"> | undefined): ModelConfig | undefined;
@@ -0,0 +1,29 @@
1
+ /** Validate and copy exact node names; an empty list selects no hidden subtrees. */
2
+ export function parseModelHiddenNodes(value) {
3
+ if (value === undefined)
4
+ return undefined;
5
+ if (!Array.isArray(value)) {
6
+ throw new TypeError("hiddenNodes must be an array of nonblank node names");
7
+ }
8
+ for (const name of value) {
9
+ if (typeof name !== "string" || name.trim().length === 0)
10
+ throw new TypeError("hiddenNodes must be an array of nonblank node names");
11
+ }
12
+ return [...value];
13
+ }
14
+ /**
15
+ * Read marker visibility metadata; an empty list clears model defaults.
16
+ * @capability model-node-visibility read authored per-placement model subtree selections
17
+ */
18
+ export function authoredHiddenNodes(meta) {
19
+ return parseModelHiddenNodes(meta?.hiddenNodes);
20
+ }
21
+ /** Apply a placement's authored visibility without erasing omitted game model defaults. */
22
+ export function modelWithAuthoredNodeVisibility(model, marker) {
23
+ if (model === undefined)
24
+ return undefined;
25
+ const hiddenNodes = authoredHiddenNodes(marker?.meta);
26
+ if (hiddenNodes === undefined || (model.hiddenNodes?.length === hiddenNodes.length && hiddenNodes.every((name, index) => model.hiddenNodes[index] === name)))
27
+ return model;
28
+ return { ...model, hiddenNodes };
29
+ }
@@ -0,0 +1,90 @@
1
+ import type { PlayerMovementConfig } from "../game/playableGame.js";
2
+ import type { PhysicsConfig } from "../game/defineGame.js";
3
+ import type { ParamSchema } from "../scene/sceneKinds.js";
4
+ import type { EditorCatalogData } from "./types.js";
5
+ /** Numeric character controls a game elects to author through its own named catalog rows. */
6
+ export interface AuthoredMovementValues {
7
+ walkSpeed?: number;
8
+ gravity?: number;
9
+ jumpVelocity?: number;
10
+ stepHeight?: number;
11
+ /** Optional positive heightfield collision span from feet; independent of model and camera scale. */
12
+ collisionHeight?: number;
13
+ /** Optional heightfield rise/run limit; omitted controls retain the game's movement policy. */
14
+ maxClimbGrade?: number;
15
+ groundAcceleration?: number;
16
+ airAcceleration?: number;
17
+ groundFriction?: number;
18
+ runMultiplier?: number;
19
+ crouchMultiplier?: number;
20
+ jumpBufferMs?: number;
21
+ coyoteMs?: number;
22
+ /** Rising speed retained on early jump release, from 0 to 1. */
23
+ jumpCutFactor?: number;
24
+ apexGravityScale?: number;
25
+ apexSpeed?: number;
26
+ fallGravityScale?: number;
27
+ landingRecoveryMs?: number;
28
+ /** Initial landing speed multiplier; values above 1 retain the existing runtime contract. */
29
+ landingSpeedScale?: number;
30
+ }
31
+ /** Only authored values, ready to overlay on a game's existing movement and physics policy. */
32
+ export interface AuthoredMovementConfig {
33
+ readonly walkSpeed?: number;
34
+ readonly physics?: {
35
+ readonly gravity?: number;
36
+ readonly jumpVelocity?: number;
37
+ };
38
+ readonly movement?: Pick<PlayerMovementConfig, "stepHeight" | "collisionHeight" | "maxClimbGrade" | "feel">;
39
+ }
40
+ /** A persisted movement value rejected before it can replace the game's current setting. */
41
+ export interface AuthoredMovementDiagnostic {
42
+ readonly path: string;
43
+ readonly message: string;
44
+ readonly repair: string;
45
+ }
46
+ /** Valid partial tuning plus located rejected values; absent rows leave game defaults intact. */
47
+ export interface AuthoredMovementResult {
48
+ readonly config: AuthoredMovementConfig;
49
+ readonly diagnostics: readonly AuthoredMovementDiagnostic[];
50
+ }
51
+ /** Structural authored catalog input; an immutable EditorDocument satisfies it. */
52
+ export interface MovementCatalogDocumentLike {
53
+ readonly catalogs: readonly EditorCatalogData[];
54
+ }
55
+ /** Game-owned callbacks, collision and physics settings retained beneath authored numeric controls. */
56
+ export interface AuthoredMovementDefaults {
57
+ readonly movement?: PlayerMovementConfig;
58
+ readonly physics?: PhysicsConfig;
59
+ }
60
+ /** Stable runtime objects whose numeric getters resolve the current authored document. */
61
+ export interface AuthoredMovementBinding {
62
+ readonly movement: PlayerMovementConfig;
63
+ readonly physics: PhysicsConfig;
64
+ readonly diagnostics: readonly AuthoredMovementDiagnostic[];
65
+ }
66
+ /**
67
+ * Creates editor/RPC controls only for the values a game supplies, with those game's defaults.
68
+ * Gravity retains the game's sign convention; collision height is positive, other controls nonnegative.
69
+ * Jump release is bounded to 0..1; supplied deeper jump controls share a collapsed response group.
70
+ * @capability editor-movement author game-chosen character movement controls through ordinary catalog rows
71
+ */
72
+ export declare function createMovementSchema(defaults: AuthoredMovementValues): ParamSchema;
73
+ /**
74
+ * Reads one game-named row as partial runtime tuning, retaining game defaults for omitted or invalid
75
+ * values. Rejected values include their document path and repair; no policy callbacks are replaced.
76
+ * @capability editor-movement resolve saved movement controls without replacing a game's character rules
77
+ */
78
+ export declare function readAuthoredMovement(document: MovementCatalogDocumentLike, catalogId: string, entryId: string): AuthoredMovementResult;
79
+ /**
80
+ * Memoizes movement resolution by immutable document identity; reads the latest document on every
81
+ * call while catalog lookup and diagnostics run only after authoring changes.
82
+ * @capability editor-movement consume live saved movement tuning with bounded per-frame work
83
+ */
84
+ export declare function createAuthoredMovementReader(getDocument: () => MovementCatalogDocumentLike, catalogId: string, entryId: string): () => AuthoredMovementResult;
85
+ /**
86
+ * Binds live authored numeric controls onto stable runtime objects, preserving the game's callbacks,
87
+ * collision, backend and other policy. The movement controller reads these getters each step.
88
+ * @capability editor-movement bind live character tuning while preserving distinctive game policy
89
+ */
90
+ export declare function bindAuthoredMovement(read: () => AuthoredMovementResult, defaults: AuthoredMovementDefaults): AuthoredMovementBinding;