@jgengine/core 0.10.0 → 0.12.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 (727) hide show
  1. package/CHANGELOG.md +39 -2
  2. package/LICENSE +203 -661
  3. package/README.md +1 -1
  4. package/dist/ai/crowd.d.ts +55 -0
  5. package/dist/ai/crowd.js +114 -0
  6. package/dist/ai/flock.d.ts +5 -2
  7. package/dist/ai/flock.js +5 -2
  8. package/dist/ai/groupAssist.d.ts +1 -0
  9. package/dist/ai/groupAssist.js +1 -0
  10. package/dist/ai/heatSystem.d.ts +76 -0
  11. package/dist/ai/heatSystem.js +97 -0
  12. package/dist/ai/interestScheduler.d.ts +107 -0
  13. package/dist/ai/interestScheduler.js +118 -0
  14. package/dist/ai/jobBoard.d.ts +1 -0
  15. package/dist/ai/jobBoard.js +1 -0
  16. package/dist/ai/laneSelect.d.ts +5 -2
  17. package/dist/ai/laneSelect.js +5 -2
  18. package/dist/ai/mobBrain.d.ts +1 -0
  19. package/dist/ai/mobBrain.js +1 -0
  20. package/dist/ai/populationDirector.d.ts +103 -0
  21. package/dist/ai/populationDirector.js +190 -0
  22. package/dist/ai/pursuit.d.ts +56 -0
  23. package/dist/ai/pursuit.js +53 -0
  24. package/dist/ai/spawnDirector.d.ts +7 -2
  25. package/dist/ai/spawnDirector.js +18 -15
  26. package/dist/ai/spawnPoint.d.ts +3 -1
  27. package/dist/ai/spawnPoint.js +3 -1
  28. package/dist/ai/targetAcquisition.d.ts +84 -0
  29. package/dist/ai/targetAcquisition.js +92 -0
  30. package/dist/anim/easing.d.ts +19 -0
  31. package/dist/anim/easing.js +19 -0
  32. package/dist/anim/oscillator.d.ts +12 -0
  33. package/dist/anim/oscillator.js +25 -0
  34. package/dist/area/areaEffectField.d.ts +127 -0
  35. package/dist/area/areaEffectField.js +175 -0
  36. package/dist/area/stackPolicy.d.ts +47 -0
  37. package/dist/area/stackPolicy.js +99 -0
  38. package/dist/authoring.d.ts +2 -1
  39. package/dist/authoring.js +2 -1
  40. package/dist/board/laneBoard.d.ts +13 -0
  41. package/dist/board/laneBoard.js +13 -0
  42. package/dist/board/timelineBoard.d.ts +10 -0
  43. package/dist/board/timelineBoard.js +10 -0
  44. package/dist/combat/abilityKit.d.ts +18 -0
  45. package/dist/combat/abilityKit.js +24 -9
  46. package/dist/combat/animationState.d.ts +5 -0
  47. package/dist/combat/animationState.js +5 -0
  48. package/dist/combat/breakMeters.d.ts +10 -0
  49. package/dist/combat/breakMeters.js +10 -0
  50. package/dist/combat/castRunner.d.ts +5 -0
  51. package/dist/combat/castRunner.js +5 -0
  52. package/dist/combat/comboPoints.d.ts +5 -0
  53. package/dist/combat/comboPoints.js +5 -0
  54. package/dist/combat/comboString.d.ts +5 -0
  55. package/dist/combat/comboString.js +5 -0
  56. package/dist/combat/damageInterceptors.d.ts +192 -0
  57. package/dist/combat/damageInterceptors.js +251 -0
  58. package/dist/combat/damageMatchup.d.ts +68 -0
  59. package/dist/combat/damageMatchup.js +42 -0
  60. package/dist/combat/damageResolution.d.ts +63 -0
  61. package/dist/combat/damageResolution.js +50 -0
  62. package/dist/combat/death.d.ts +7 -1
  63. package/dist/combat/death.js +5 -0
  64. package/dist/combat/deathReason.d.ts +14 -0
  65. package/dist/combat/deathReason.js +1 -0
  66. package/dist/combat/defensiveWindow.d.ts +5 -0
  67. package/dist/combat/defensiveWindow.js +5 -0
  68. package/dist/combat/dotField.d.ts +5 -1
  69. package/dist/combat/dotField.js +5 -1
  70. package/dist/combat/downed.d.ts +5 -0
  71. package/dist/combat/downed.js +5 -0
  72. package/dist/combat/effects.d.ts +5 -0
  73. package/dist/combat/effects.js +5 -0
  74. package/dist/combat/encounterSequence.d.ts +258 -0
  75. package/dist/combat/encounterSequence.js +426 -0
  76. package/dist/combat/hitReaction.d.ts +57 -1
  77. package/dist/combat/hitReaction.js +45 -5
  78. package/dist/combat/magazine.d.ts +54 -0
  79. package/dist/combat/magazine.js +96 -0
  80. package/dist/combat/projectiles.d.ts +5 -0
  81. package/dist/combat/projectiles.js +32 -14
  82. package/dist/combat/receivedDamage.d.ts +117 -0
  83. package/dist/combat/receivedDamage.js +92 -0
  84. package/dist/combat/regenShield.d.ts +5 -1
  85. package/dist/combat/regenShield.js +5 -1
  86. package/dist/combat/renderCues.d.ts +51 -0
  87. package/dist/combat/renderCues.js +44 -0
  88. package/dist/combat/resourcePool.d.ts +5 -0
  89. package/dist/combat/resourcePool.js +5 -0
  90. package/dist/combat/rewardAllocation.d.ts +121 -0
  91. package/dist/combat/rewardAllocation.js +251 -0
  92. package/dist/combat/shotOrigin.d.ts +18 -1
  93. package/dist/combat/shotOrigin.js +39 -0
  94. package/dist/combat/statusApplication.d.ts +96 -0
  95. package/dist/combat/statusApplication.js +81 -0
  96. package/dist/combat/telegraph.d.ts +11 -2
  97. package/dist/combat/telegraph.js +11 -2
  98. package/dist/combat/thresholdCrossings.d.ts +85 -0
  99. package/dist/combat/thresholdCrossings.js +116 -0
  100. package/dist/combat.d.ts +35 -0
  101. package/dist/combat.js +35 -0
  102. package/dist/commands/commandRegistry.d.ts +11 -0
  103. package/dist/commands/commandRegistry.js +9 -2
  104. package/dist/crafting/crop.d.ts +18 -0
  105. package/dist/crafting/crop.js +18 -0
  106. package/dist/crafting/production.d.ts +5 -0
  107. package/dist/crafting/production.js +5 -0
  108. package/dist/devtools/controls.d.ts +38 -0
  109. package/dist/devtools/controls.js +181 -0
  110. package/dist/devtools/devtools.d.ts +5 -197
  111. package/dist/devtools/devtools.js +38 -774
  112. package/dist/devtools/discover.d.ts +32 -0
  113. package/dist/devtools/discover.js +240 -0
  114. package/dist/devtools/fallbackSeams.d.ts +50 -0
  115. package/dist/devtools/fallbackSeams.js +111 -0
  116. package/dist/devtools/frame.d.ts +15 -0
  117. package/dist/devtools/frame.js +247 -0
  118. package/dist/devtools/logs.d.ts +15 -0
  119. package/dist/devtools/logs.js +86 -0
  120. package/dist/devtools/overrides.d.ts +16 -0
  121. package/dist/devtools/overrides.js +66 -0
  122. package/dist/devtools/rewriteTunables.d.ts +17 -0
  123. package/dist/devtools/rewriteTunables.js +276 -0
  124. package/dist/devtools/saveEndpoint.d.ts +46 -0
  125. package/dist/devtools/saveEndpoint.js +48 -0
  126. package/dist/devtools/transformTunables.d.ts +3 -0
  127. package/dist/devtools/transformTunables.js +3 -0
  128. package/dist/devtools/tunableSchema.d.ts +31 -0
  129. package/dist/devtools/tunableSchema.js +31 -0
  130. package/dist/devtools/types.d.ts +212 -0
  131. package/dist/devtools/types.js +1 -0
  132. package/dist/economy/auctionBook.d.ts +122 -0
  133. package/dist/economy/auctionBook.js +213 -0
  134. package/dist/economy/currency.d.ts +12 -1
  135. package/dist/economy/currency.js +12 -1
  136. package/dist/economy/listingBook.d.ts +91 -0
  137. package/dist/economy/listingBook.js +144 -0
  138. package/dist/economy/priceHistory.d.ts +44 -0
  139. package/dist/economy/priceHistory.js +89 -0
  140. package/dist/economy/resourceLedger.d.ts +222 -0
  141. package/dist/economy/resourceLedger.js +353 -0
  142. package/dist/economy/sharedWallet.d.ts +14 -0
  143. package/dist/economy/sharedWallet.js +14 -0
  144. package/dist/economy/techTree.d.ts +12 -0
  145. package/dist/economy/techTree.js +12 -0
  146. package/dist/economy/unlockPoints.d.ts +92 -0
  147. package/dist/economy/unlockPoints.js +103 -0
  148. package/dist/economy/wallet.d.ts +26 -2
  149. package/dist/economy/wallet.js +35 -6
  150. package/dist/editor/commandApply.d.ts +5 -0
  151. package/dist/editor/commandApply.js +755 -0
  152. package/dist/editor/commands.d.ts +235 -7
  153. package/dist/editor/commands.js +82 -124
  154. package/dist/editor/directives.d.ts +94 -0
  155. package/dist/editor/directives.js +206 -0
  156. package/dist/editor/document.d.ts +194 -13
  157. package/dist/editor/document.js +1166 -19
  158. package/dist/editor/entityCatalog.d.ts +42 -0
  159. package/dist/editor/entityCatalog.js +101 -0
  160. package/dist/editor/environment.d.ts +74 -0
  161. package/dist/editor/environment.js +169 -0
  162. package/dist/editor/grid.d.ts +229 -0
  163. package/dist/editor/grid.js +366 -0
  164. package/dist/editor/gridAdapters.d.ts +80 -0
  165. package/dist/editor/gridAdapters.js +134 -0
  166. package/dist/editor/index.d.ts +98 -6
  167. package/dist/editor/index.js +13 -2
  168. package/dist/editor/liveSync.d.ts +127 -0
  169. package/dist/editor/liveSync.js +249 -0
  170. package/dist/editor/minimap.d.ts +31 -0
  171. package/dist/editor/minimap.js +73 -0
  172. package/dist/editor/runtimeInspector.d.ts +88 -0
  173. package/dist/editor/runtimeInspector.js +239 -0
  174. package/dist/editor/streamer.d.ts +66 -0
  175. package/dist/editor/streamer.js +68 -0
  176. package/dist/editor/types.d.ts +250 -1
  177. package/dist/editor/world.d.ts +136 -0
  178. package/dist/editor/world.js +303 -0
  179. package/dist/faction/reputation.d.ts +5 -0
  180. package/dist/faction/reputation.js +5 -0
  181. package/dist/format/distance.d.ts +17 -0
  182. package/dist/format/distance.js +18 -0
  183. package/dist/format/duration.d.ts +27 -0
  184. package/dist/format/duration.js +45 -0
  185. package/dist/format/speed.d.ts +17 -0
  186. package/dist/format/speed.js +25 -0
  187. package/dist/game/breeding.d.ts +161 -0
  188. package/dist/game/breeding.js +181 -0
  189. package/dist/game/cameraConfig.d.ts +451 -0
  190. package/dist/game/cameraConfig.js +1 -0
  191. package/dist/game/connectedPlayers.d.ts +6 -4
  192. package/dist/game/connectedPlayers.js +2 -2
  193. package/dist/game/cosmetics.d.ts +9 -0
  194. package/dist/game/cosmetics.js +16 -0
  195. package/dist/game/defineGame.d.ts +110 -5
  196. package/dist/game/defineGame.js +49 -4
  197. package/dist/game/defineSystem.d.ts +90 -0
  198. package/dist/game/defineSystem.js +47 -0
  199. package/dist/game/dialogue.d.ts +33 -0
  200. package/dist/game/dialogue.js +21 -0
  201. package/dist/game/events.d.ts +63 -13
  202. package/dist/game/events.js +5 -0
  203. package/dist/game/feed.d.ts +33 -0
  204. package/dist/game/feed.js +35 -2
  205. package/dist/game/gamePhase.d.ts +11 -0
  206. package/dist/game/gamePhase.js +16 -0
  207. package/dist/game/leaderboard.d.ts +5 -0
  208. package/dist/game/leaderboard.js +5 -0
  209. package/dist/game/loadout.d.ts +5 -0
  210. package/dist/game/loadout.js +5 -0
  211. package/dist/game/lootFilter.d.ts +2 -0
  212. package/dist/game/lootFilter.js +2 -0
  213. package/dist/game/lootPipeline.d.ts +164 -0
  214. package/dist/game/lootPipeline.js +216 -0
  215. package/dist/game/lootTable.d.ts +19 -2
  216. package/dist/game/lootTable.js +15 -5
  217. package/dist/game/objectives.d.ts +43 -0
  218. package/dist/game/objectives.js +36 -0
  219. package/dist/game/paramLayers.d.ts +148 -0
  220. package/dist/game/paramLayers.js +185 -0
  221. package/dist/game/ping.d.ts +5 -0
  222. package/dist/game/ping.js +5 -0
  223. package/dist/game/playableGame.d.ts +116 -432
  224. package/dist/game/playableGame.js +1 -1
  225. package/dist/game/progression.d.ts +35 -0
  226. package/dist/game/progression.js +19 -0
  227. package/dist/game/quest.d.ts +8 -0
  228. package/dist/game/quest.js +30 -3
  229. package/dist/game/race.d.ts +141 -0
  230. package/dist/game/race.js +169 -0
  231. package/dist/game/recordBook.d.ts +3 -1
  232. package/dist/game/recordBook.js +4 -1
  233. package/dist/game/ruleSelection.d.ts +75 -0
  234. package/dist/game/ruleSelection.js +127 -0
  235. package/dist/game/runDraft.d.ts +5 -0
  236. package/dist/game/runDraft.js +5 -0
  237. package/dist/game/saveStore.d.ts +111 -0
  238. package/dist/game/saveStore.js +312 -0
  239. package/dist/game/snapshotHistory.d.ts +67 -0
  240. package/dist/game/snapshotHistory.js +95 -0
  241. package/dist/game/social.d.ts +38 -1
  242. package/dist/game/social.js +71 -0
  243. package/dist/game/spawnPoints.d.ts +5 -0
  244. package/dist/game/spawnPoints.js +5 -0
  245. package/dist/game/systemRuntime.d.ts +50 -0
  246. package/dist/game/systemRuntime.js +157 -0
  247. package/dist/game/systemSchedule.d.ts +72 -0
  248. package/dist/game/systemSchedule.js +242 -0
  249. package/dist/game/toasts.d.ts +43 -0
  250. package/dist/game/toasts.js +44 -0
  251. package/dist/game/trade.d.ts +10 -0
  252. package/dist/game/trade.js +10 -0
  253. package/dist/game/tribe.d.ts +184 -0
  254. package/dist/game/tribe.js +432 -0
  255. package/dist/game/unlocks.d.ts +8 -0
  256. package/dist/game/unlocks.js +16 -0
  257. package/dist/game/vfxInstance.d.ts +138 -0
  258. package/dist/game/vfxInstance.js +96 -0
  259. package/dist/game/worldItem.d.ts +17 -4
  260. package/dist/game/worldItem.js +17 -4
  261. package/dist/gameplay.d.ts +93 -0
  262. package/dist/gameplay.js +93 -0
  263. package/dist/index.d.ts +5 -14
  264. package/dist/index.js +5 -14
  265. package/dist/input/actionBindings.d.ts +23 -6
  266. package/dist/input/actionBindings.js +23 -6
  267. package/dist/input/axisInput.d.ts +7 -2
  268. package/dist/input/axisInput.js +7 -2
  269. package/dist/input/controlGroups.d.ts +67 -0
  270. package/dist/input/controlGroups.js +41 -0
  271. package/dist/input/touchScheme.d.ts +82 -6
  272. package/dist/input/touchScheme.js +123 -10
  273. package/dist/interaction/lockpick.d.ts +61 -0
  274. package/dist/interaction/lockpick.js +168 -0
  275. package/dist/interaction/proximityPrompt.d.ts +2 -0
  276. package/dist/interaction/proximityPrompt.js +2 -0
  277. package/dist/interaction/qte.d.ts +5 -0
  278. package/dist/interaction/qte.js +5 -0
  279. package/dist/interaction/skillCheck.d.ts +5 -0
  280. package/dist/interaction/skillCheck.js +5 -0
  281. package/dist/inventory/encumbrance.d.ts +69 -0
  282. package/dist/inventory/encumbrance.js +68 -0
  283. package/dist/inventory/inventoryModel.d.ts +12 -0
  284. package/dist/inventory/inventoryModel.js +12 -0
  285. package/dist/inventory/shapedGrid.d.ts +16 -0
  286. package/dist/inventory/shapedGrid.js +16 -0
  287. package/dist/inventory/slotModel.d.ts +10 -0
  288. package/dist/inventory/slotModel.js +10 -0
  289. package/dist/item/durability.d.ts +5 -0
  290. package/dist/item/durability.js +6 -3
  291. package/dist/item/itemIdentity.d.ts +171 -0
  292. package/dist/item/itemIdentity.js +138 -0
  293. package/dist/item/itemInstanceRegistry.d.ts +32 -0
  294. package/dist/item/itemInstanceRegistry.js +34 -0
  295. package/dist/item/itemgen.d.ts +163 -0
  296. package/dist/item/itemgen.js +151 -0
  297. package/dist/item/modularItem.d.ts +5 -0
  298. package/dist/item/modularItem.js +5 -0
  299. package/dist/item/use.d.ts +5 -0
  300. package/dist/item/use.js +5 -0
  301. package/dist/item/useBehavior.d.ts +142 -0
  302. package/dist/item/useBehavior.js +105 -0
  303. package/dist/item/weapon.d.ts +5 -0
  304. package/dist/item/weapon.js +5 -0
  305. package/dist/math/scalar.d.ts +25 -0
  306. package/dist/math/scalar.js +47 -0
  307. package/dist/meta/changelog.d.ts +4 -1
  308. package/dist/meta/changelog.js +39 -2
  309. package/dist/movement/avoidance.d.ts +45 -0
  310. package/dist/movement/avoidance.js +123 -0
  311. package/dist/movement/cameraRig.d.ts +8 -0
  312. package/dist/movement/cameraRig.js +8 -0
  313. package/dist/movement/dash.d.ts +11 -0
  314. package/dist/movement/dash.js +11 -0
  315. package/dist/movement/formation.d.ts +135 -0
  316. package/dist/movement/formation.js +194 -0
  317. package/dist/movement/glideModel.d.ts +5 -0
  318. package/dist/movement/glideModel.js +5 -0
  319. package/dist/movement/grappleSwing.d.ts +5 -0
  320. package/dist/movement/grappleSwing.js +5 -0
  321. package/dist/movement/leaderTrail.d.ts +5 -0
  322. package/dist/movement/leaderTrail.js +5 -0
  323. package/dist/movement/movementModel.d.ts +50 -9
  324. package/dist/movement/movementModel.js +75 -29
  325. package/dist/movement/playerMovement.js +167 -12
  326. package/dist/movement/poseState.d.ts +13 -0
  327. package/dist/movement/poseState.js +16 -0
  328. package/dist/movement/steering.d.ts +8 -0
  329. package/dist/movement/steering.js +18 -0
  330. package/dist/multiplayer/combatSnapshot.d.ts +3 -0
  331. package/dist/multiplayer/combatSnapshot.js +16 -8
  332. package/dist/multiplayer/lagCompensation.d.ts +5 -0
  333. package/dist/multiplayer/lagCompensation.js +5 -0
  334. package/dist/multiplayer/presenceModel.d.ts +11 -1
  335. package/dist/multiplayer/presenceModel.js +17 -1
  336. package/dist/multiplayer/simultaneousCommit.d.ts +3 -0
  337. package/dist/multiplayer/simultaneousCommit.js +3 -0
  338. package/dist/multiplayer.d.ts +11 -0
  339. package/dist/multiplayer.js +11 -0
  340. package/dist/nav/pathFollow.d.ts +57 -0
  341. package/dist/nav/pathFollow.js +109 -0
  342. package/dist/orders/orderKinds.d.ts +156 -0
  343. package/dist/orders/orderKinds.js +197 -0
  344. package/dist/orders/orderQueue.d.ts +208 -0
  345. package/dist/orders/orderQueue.js +211 -0
  346. package/dist/physics/drivableVehicle.d.ts +31 -0
  347. package/dist/physics/drivableVehicle.js +16 -0
  348. package/dist/physics/flightDynamics.d.ts +82 -0
  349. package/dist/physics/flightDynamics.js +165 -0
  350. package/dist/physics/gravityField.d.ts +25 -0
  351. package/dist/physics/gravityField.js +41 -0
  352. package/dist/physics/kinematicVehicle.d.ts +108 -1
  353. package/dist/physics/kinematicVehicle.js +212 -15
  354. package/dist/physics/physicsWorld.d.ts +50 -12
  355. package/dist/physics/physicsWorld.js +120 -122
  356. package/dist/physics/radialImpulse.d.ts +25 -0
  357. package/dist/physics/radialImpulse.js +27 -0
  358. package/dist/physics/vehicleObstacles.d.ts +44 -0
  359. package/dist/physics/vehicleObstacles.js +115 -0
  360. package/dist/procedural.d.ts +3 -0
  361. package/dist/procedural.js +3 -0
  362. package/dist/progression/statGraph.d.ts +116 -0
  363. package/dist/progression/statGraph.js +318 -0
  364. package/dist/puzzle/cellGrid.d.ts +9 -0
  365. package/dist/puzzle/cellGrid.js +9 -0
  366. package/dist/puzzle/fallingPiece.d.ts +9 -0
  367. package/dist/puzzle/fallingPiece.js +9 -0
  368. package/dist/puzzle/nonogram.d.ts +39 -0
  369. package/dist/puzzle/nonogram.js +136 -0
  370. package/dist/random/nameGen.d.ts +5 -0
  371. package/dist/random/nameGen.js +5 -0
  372. package/dist/random/pick.d.ts +8 -0
  373. package/dist/random/pick.js +31 -0
  374. package/dist/random/rng.d.ts +26 -1
  375. package/dist/random/rng.js +32 -9
  376. package/dist/random/seedLink.d.ts +4 -0
  377. package/dist/random/seedLink.js +4 -0
  378. package/dist/relation/keyedValues.d.ts +54 -0
  379. package/dist/relation/keyedValues.js +115 -0
  380. package/dist/relation/thresholds.d.ts +62 -0
  381. package/dist/relation/thresholds.js +62 -0
  382. package/dist/render/color.d.ts +12 -0
  383. package/dist/render/color.js +26 -0
  384. package/dist/render/lookPreset.d.ts +41 -0
  385. package/dist/render/lookPreset.js +29 -0
  386. package/dist/render/postProcessing.d.ts +20 -0
  387. package/dist/render/postProcessing.js +21 -1
  388. package/dist/rules/predicate.d.ts +53 -0
  389. package/dist/rules/predicate.js +64 -0
  390. package/dist/rules/ruleEffects.d.ts +38 -0
  391. package/dist/rules/ruleEffects.js +37 -0
  392. package/dist/rules/triggeredRules.d.ts +160 -0
  393. package/dist/rules/triggeredRules.js +241 -0
  394. package/dist/runtime/adapter.d.ts +55 -3
  395. package/dist/runtime/adapter.js +57 -1
  396. package/dist/runtime/cameraDirector.d.ts +1 -0
  397. package/dist/runtime/cameraDirector.js +1 -0
  398. package/dist/runtime/commandRunner.d.ts +1 -0
  399. package/dist/runtime/commandRunner.js +1 -0
  400. package/dist/runtime/context/combat.d.ts +51 -0
  401. package/dist/runtime/context/combat.js +130 -0
  402. package/dist/runtime/context/combatFx.d.ts +23 -0
  403. package/dist/runtime/context/combatFx.js +161 -0
  404. package/dist/runtime/context/deathLoot.d.ts +30 -0
  405. package/dist/runtime/context/deathLoot.js +39 -0
  406. package/dist/runtime/context/player.d.ts +48 -0
  407. package/dist/runtime/context/player.js +125 -0
  408. package/dist/runtime/context/registries.d.ts +13 -0
  409. package/dist/runtime/context/registries.js +84 -0
  410. package/dist/runtime/context/scene.d.ts +59 -0
  411. package/dist/runtime/context/scene.js +291 -0
  412. package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
  413. package/dist/runtime/context/snapshotCodecs.js +26 -0
  414. package/dist/runtime/context/worldItems.d.ts +21 -0
  415. package/dist/runtime/context/worldItems.js +37 -0
  416. package/dist/runtime/descriptors/baseline.d.ts +48 -0
  417. package/dist/runtime/descriptors/baseline.js +157 -0
  418. package/dist/runtime/descriptors/features.d.ts +79 -0
  419. package/dist/runtime/descriptors/features.js +299 -0
  420. package/dist/runtime/gameContext.d.ts +2 -327
  421. package/dist/runtime/gameContext.js +273 -778
  422. package/dist/runtime/gameContextTypes.d.ts +467 -0
  423. package/dist/runtime/gameContextTypes.js +1 -0
  424. package/dist/runtime/gameRuntime.d.ts +1 -0
  425. package/dist/runtime/gameRuntime.js +1 -0
  426. package/dist/runtime/headlessRunner.d.ts +62 -0
  427. package/dist/runtime/headlessRunner.js +45 -0
  428. package/dist/runtime/hostPersistence.d.ts +12 -0
  429. package/dist/runtime/hostPersistence.js +12 -0
  430. package/dist/runtime/hostPolicy.d.ts +51 -0
  431. package/dist/runtime/hostPolicy.js +68 -0
  432. package/dist/runtime/hostedGameRunner.d.ts +11 -4
  433. package/dist/runtime/hostedGameRunner.js +27 -3
  434. package/dist/runtime/hostedWorldSession.d.ts +15 -5
  435. package/dist/runtime/hostedWorldSession.js +12 -4
  436. package/dist/runtime/inputSnapshot.d.ts +8 -3
  437. package/dist/runtime/inputSnapshot.js +6 -2
  438. package/dist/runtime/motionIntents.d.ts +11 -2
  439. package/dist/runtime/motionIntents.js +16 -2
  440. package/dist/runtime/perContext.d.ts +15 -0
  441. package/dist/runtime/perContext.js +21 -0
  442. package/dist/runtime/runtimeSave.d.ts +79 -0
  443. package/dist/runtime/runtimeSave.js +105 -0
  444. package/dist/runtime/save.d.ts +4 -0
  445. package/dist/runtime/save.js +4 -0
  446. package/dist/runtime/snapshot.d.ts +7 -0
  447. package/dist/runtime/snapshot.js +7 -0
  448. package/dist/runtime/visibility.d.ts +2 -1
  449. package/dist/runtime/visibility.js +2 -1
  450. package/dist/runtime/worldChannel.d.ts +7 -3
  451. package/dist/runtime/worldChannel.js +51 -6
  452. package/dist/runtime/worldMirror.d.ts +14 -2
  453. package/dist/runtime/worldMirror.js +15 -2
  454. package/dist/runtime/worldProjection.d.ts +47 -0
  455. package/dist/runtime/worldProjection.js +52 -0
  456. package/dist/runtime/worldReplication.d.ts +30 -7
  457. package/dist/runtime/worldReplication.js +41 -9
  458. package/dist/runtime/worldSnapshot.d.ts +42 -3
  459. package/dist/runtime/worldSnapshot.js +34 -7
  460. package/dist/scene/assetCatalog.d.ts +26 -0
  461. package/dist/scene/assetCatalog.js +4 -0
  462. package/dist/scene/assetDiagnostics.d.ts +69 -0
  463. package/dist/scene/assetDiagnostics.js +110 -0
  464. package/dist/scene/assetGenerator.d.ts +65 -0
  465. package/dist/scene/assetGenerator.js +64 -0
  466. package/dist/scene/assetSpace.d.ts +137 -0
  467. package/dist/scene/assetSpace.js +257 -0
  468. package/dist/scene/authoredTriggers.d.ts +219 -0
  469. package/dist/scene/authoredTriggers.js +454 -0
  470. package/dist/scene/behaviorRuntime.d.ts +75 -0
  471. package/dist/scene/behaviorRuntime.js +217 -0
  472. package/dist/scene/behaviors.d.ts +14 -2
  473. package/dist/scene/behaviors.js +7 -2
  474. package/dist/scene/bodyBind.d.ts +60 -0
  475. package/dist/scene/bodyBind.js +51 -0
  476. package/dist/scene/builtinSceneKinds.d.ts +5 -0
  477. package/dist/scene/builtinSceneKinds.js +67 -0
  478. package/dist/scene/colliders.d.ts +71 -1
  479. package/dist/scene/colliders.js +145 -1
  480. package/dist/scene/collisionMesh.d.ts +94 -0
  481. package/dist/scene/collisionMesh.js +699 -0
  482. package/dist/scene/companion.d.ts +147 -0
  483. package/dist/scene/companion.js +193 -0
  484. package/dist/scene/entityStats.d.ts +18 -11
  485. package/dist/scene/entityStats.js +21 -16
  486. package/dist/scene/entityStore.d.ts +12 -1
  487. package/dist/scene/entityStore.js +15 -0
  488. package/dist/scene/facing.d.ts +18 -0
  489. package/dist/scene/facing.js +26 -0
  490. package/dist/scene/modelSockets.d.ts +33 -0
  491. package/dist/scene/modelSockets.js +29 -0
  492. package/dist/scene/objectQuery.d.ts +2 -0
  493. package/dist/scene/objectQuery.js +2 -0
  494. package/dist/scene/objectStore.d.ts +4 -0
  495. package/dist/scene/objectStore.js +6 -0
  496. package/dist/scene/possession.d.ts +8 -0
  497. package/dist/scene/possession.js +14 -0
  498. package/dist/scene/roster.d.ts +3 -0
  499. package/dist/scene/roster.js +12 -0
  500. package/dist/scene/sceneKinds.d.ts +256 -0
  501. package/dist/scene/sceneKinds.js +218 -0
  502. package/dist/scene/sceneOwnership.d.ts +169 -0
  503. package/dist/scene/sceneOwnership.js +179 -0
  504. package/dist/scene/sceneRaycast.js +15 -0
  505. package/dist/scene/selectionBookmarks.d.ts +93 -0
  506. package/dist/scene/selectionBookmarks.js +122 -0
  507. package/dist/scene/vehicleSeat.d.ts +68 -0
  508. package/dist/scene/vehicleSeat.js +59 -0
  509. package/dist/sensor/concealment.d.ts +3 -0
  510. package/dist/sensor/concealment.js +3 -0
  511. package/dist/sensor/freezeMonitor.d.ts +1 -0
  512. package/dist/sensor/freezeMonitor.js +1 -0
  513. package/dist/sensor/frustumSensor.d.ts +7 -3
  514. package/dist/sensor/frustumSensor.js +7 -3
  515. package/dist/sensor/hiddenStateProbe.d.ts +5 -2
  516. package/dist/sensor/hiddenStateProbe.js +5 -2
  517. package/dist/sensor/recordingBuffer.d.ts +2 -1
  518. package/dist/sensor/recordingBuffer.js +2 -1
  519. package/dist/sensor/replayLoop.d.ts +6 -2
  520. package/dist/sensor/replayLoop.js +6 -2
  521. package/dist/sensor/revealQuery.d.ts +2 -1
  522. package/dist/sensor/revealQuery.js +2 -1
  523. package/dist/sensor/visionCone.d.ts +10 -3
  524. package/dist/sensor/visionCone.js +10 -3
  525. package/dist/session/contestedChannel.d.ts +1 -0
  526. package/dist/session/contestedChannel.js +1 -0
  527. package/dist/session/extraction.d.ts +1 -0
  528. package/dist/session/extraction.js +1 -0
  529. package/dist/session/roles.d.ts +6 -0
  530. package/dist/session/roles.js +6 -0
  531. package/dist/session/roundState.d.ts +8 -0
  532. package/dist/session/roundState.js +2 -0
  533. package/dist/settings/settingsModel.d.ts +1 -0
  534. package/dist/settings/settingsModel.js +1 -0
  535. package/dist/stats/accumulatorMeter.d.ts +6 -0
  536. package/dist/stats/accumulatorMeter.js +6 -0
  537. package/dist/stats/eventMeter.d.ts +5 -0
  538. package/dist/stats/eventMeter.js +5 -0
  539. package/dist/stats/rollCheck.d.ts +5 -0
  540. package/dist/stats/rollCheck.js +5 -0
  541. package/dist/stats/spawnLevelStats.d.ts +110 -0
  542. package/dist/stats/spawnLevelStats.js +140 -0
  543. package/dist/stats/statModifiers.d.ts +5 -0
  544. package/dist/stats/statModifiers.js +5 -0
  545. package/dist/stats/statPool.d.ts +70 -0
  546. package/dist/stats/statPool.js +59 -0
  547. package/dist/store/changeSignal.d.ts +3 -0
  548. package/dist/store/changeSignal.js +2 -0
  549. package/dist/store/defineKeyedStore.d.ts +38 -0
  550. package/dist/store/defineKeyedStore.js +42 -0
  551. package/dist/store/defineStore.d.ts +33 -0
  552. package/dist/store/defineStore.js +35 -0
  553. package/dist/store/observableKeyedStore.d.ts +7 -0
  554. package/dist/store/observableKeyedStore.js +35 -2
  555. package/dist/survival/decayMeter.d.ts +55 -0
  556. package/dist/survival/decayMeter.js +130 -57
  557. package/dist/survival/moodle.d.ts +2 -0
  558. package/dist/survival/regionHealth.d.ts +6 -0
  559. package/dist/survival/regionHealth.js +6 -3
  560. package/dist/tactics/fallingGrid.d.ts +4 -2
  561. package/dist/tactics/fallingGrid.js +4 -2
  562. package/dist/tactics/predictiveQuery.d.ts +3 -0
  563. package/dist/tactics/predictiveQuery.js +3 -0
  564. package/dist/tactics/snapshot.d.ts +2 -0
  565. package/dist/tactics/snapshot.js +2 -0
  566. package/dist/tactics/surface.d.ts +1 -0
  567. package/dist/tactics/surface.js +1 -0
  568. package/dist/tactics/tacticalGrid.d.ts +1 -0
  569. package/dist/tactics/tacticalGrid.js +1 -0
  570. package/dist/time/beatClock.d.ts +14 -4
  571. package/dist/time/beatClock.js +14 -4
  572. package/dist/time/calendarClock.d.ts +3 -0
  573. package/dist/time/calendarClock.js +3 -0
  574. package/dist/time/idleProgress.d.ts +4 -0
  575. package/dist/time/idleProgress.js +4 -0
  576. package/dist/time/simClock.d.ts +2 -0
  577. package/dist/time/simClock.js +20 -5
  578. package/dist/ui/actionModel.d.ts +110 -0
  579. package/dist/ui/actionModel.js +159 -0
  580. package/dist/ui/gameLayout.d.ts +30 -11
  581. package/dist/ui/gameLayout.js +30 -11
  582. package/dist/ui/hudDocument.d.ts +114 -0
  583. package/dist/ui/hudDocument.js +220 -0
  584. package/dist/ui/hudLayout.d.ts +38 -1
  585. package/dist/ui/hudLayout.js +220 -17
  586. package/dist/ui/selectionModel.d.ts +92 -0
  587. package/dist/ui/selectionModel.js +88 -0
  588. package/dist/ui/tooltipModel.d.ts +63 -0
  589. package/dist/ui/tooltipModel.js +82 -0
  590. package/dist/ui.d.ts +15 -0
  591. package/dist/ui.js +15 -0
  592. package/dist/visibility/bounds.d.ts +8 -1
  593. package/dist/visibility/bounds.js +8 -1
  594. package/dist/visibility/frustum.d.ts +12 -3
  595. package/dist/visibility/frustum.js +12 -3
  596. package/dist/work/jobQueue.d.ts +201 -0
  597. package/dist/work/jobQueue.js +253 -0
  598. package/dist/work/unitTraining.d.ts +58 -0
  599. package/dist/work/unitTraining.js +52 -0
  600. package/dist/world/authoredEntities.d.ts +29 -0
  601. package/dist/world/authoredEntities.js +29 -0
  602. package/dist/world/authoredObjects.d.ts +72 -0
  603. package/dist/world/authoredObjects.js +81 -0
  604. package/dist/world/authoredSpawn.d.ts +39 -0
  605. package/dist/world/authoredSpawn.js +37 -0
  606. package/dist/world/buildSockets.d.ts +152 -0
  607. package/dist/world/buildSockets.js +188 -0
  608. package/dist/world/buildingGenerator.d.ts +18 -0
  609. package/dist/world/buildingGenerator.js +62 -0
  610. package/dist/world/buildings.d.ts +5 -0
  611. package/dist/world/buildings.js +5 -0
  612. package/dist/world/catenary.d.ts +22 -0
  613. package/dist/world/catenary.js +69 -0
  614. package/dist/world/cityBlocks.d.ts +152 -0
  615. package/dist/world/cityBlocks.js +670 -0
  616. package/dist/world/cityContent.d.ts +77 -0
  617. package/dist/world/cityContent.js +261 -0
  618. package/dist/world/cityGeometry.d.ts +114 -0
  619. package/dist/world/cityGeometry.js +573 -0
  620. package/dist/world/cityKind.d.ts +314 -0
  621. package/dist/world/cityKind.js +1578 -0
  622. package/dist/world/features.d.ts +133 -10
  623. package/dist/world/features.js +66 -6
  624. package/dist/world/footprintGrid.d.ts +59 -0
  625. package/dist/world/footprintGrid.js +141 -0
  626. package/dist/world/geometry.d.ts +64 -0
  627. package/dist/world/geometry.js +94 -0
  628. package/dist/world/grassKind.d.ts +51 -0
  629. package/dist/world/grassKind.js +101 -0
  630. package/dist/world/markers.d.ts +50 -4
  631. package/dist/world/markers.js +49 -0
  632. package/dist/world/massing.js +1 -1
  633. package/dist/world/minimapBake.d.ts +78 -0
  634. package/dist/world/minimapBake.js +208 -0
  635. package/dist/world/minimapTrack.d.ts +17 -0
  636. package/dist/world/minimapTrack.js +22 -0
  637. package/dist/world/pathInstances.d.ts +27 -0
  638. package/dist/world/pathInstances.js +27 -0
  639. package/dist/world/pathNetwork.d.ts +159 -0
  640. package/dist/world/pathNetwork.js +684 -0
  641. package/dist/world/pathTerrain.d.ts +80 -0
  642. package/dist/world/pathTerrain.js +142 -0
  643. package/dist/world/place.d.ts +166 -0
  644. package/dist/world/place.js +96 -0
  645. package/dist/world/placeAsset.d.ts +81 -0
  646. package/dist/world/placeAsset.js +79 -0
  647. package/dist/world/placement.d.ts +4 -0
  648. package/dist/world/placement.js +4 -0
  649. package/dist/world/placementController.d.ts +6 -0
  650. package/dist/world/placementController.js +6 -0
  651. package/dist/world/poleLineKind.d.ts +40 -0
  652. package/dist/world/poleLineKind.js +97 -0
  653. package/dist/world/polyline.d.ts +34 -0
  654. package/dist/world/polyline.js +88 -0
  655. package/dist/world/resourceNode.d.ts +109 -0
  656. package/dist/world/resourceNode.js +140 -0
  657. package/dist/world/roads.d.ts +8 -0
  658. package/dist/world/roads.js +38 -0
  659. package/dist/world/scatter.js +2 -20
  660. package/dist/world/scatterRegion.d.ts +164 -0
  661. package/dist/world/scatterRegion.js +409 -0
  662. package/dist/world/sceneShapes.d.ts +43 -0
  663. package/dist/world/sceneShapes.js +1 -0
  664. package/dist/world/segment.d.ts +27 -0
  665. package/dist/world/segment.js +43 -0
  666. package/dist/world/soilKind.d.ts +48 -0
  667. package/dist/world/soilKind.js +92 -0
  668. package/dist/world/spatialSample.d.ts +241 -0
  669. package/dist/world/spatialSample.js +465 -0
  670. package/dist/world/streets.d.ts +29 -0
  671. package/dist/world/streets.js +55 -0
  672. package/dist/world/terraform.d.ts +213 -1
  673. package/dist/world/terraform.js +627 -22
  674. package/dist/world/terrain.d.ts +164 -20
  675. package/dist/world/terrain.js +325 -41
  676. package/dist/world/terrainGuides.d.ts +182 -0
  677. package/dist/world/terrainGuides.js +343 -0
  678. package/dist/world/vec2.d.ts +81 -0
  679. package/dist/world/vec2.js +128 -0
  680. package/dist/world/vec3.d.ts +49 -0
  681. package/dist/world/vec3.js +76 -0
  682. package/dist/world/vegetation.d.ts +71 -0
  683. package/dist/world/vegetation.js +151 -0
  684. package/dist/world/visibilityField.d.ts +154 -0
  685. package/dist/world/visibilityField.js +271 -0
  686. package/dist/world/volumetricClouds.d.ts +52 -0
  687. package/dist/world/volumetricClouds.js +43 -0
  688. package/dist/world/walls.d.ts +9 -0
  689. package/dist/world/walls.js +9 -0
  690. package/dist/world/waterKind.d.ts +55 -0
  691. package/dist/world/waterKind.js +99 -0
  692. package/dist/world.d.ts +141 -0
  693. package/dist/world.js +141 -0
  694. package/package.json +32 -8
  695. package/skills/game-design/SKILL.md +28 -0
  696. package/skills/game-design/agents/openai.yaml +4 -0
  697. package/skills/game-design/references/application-playbook.md +170 -0
  698. package/skills/game-design/references/field-guide.md +288 -0
  699. package/skills/jgengine/SKILL.md +54 -0
  700. package/skills/jgengine/api.md +527 -0
  701. package/skills/jgengine/capabilities.md +131 -0
  702. package/skills/jgengine/recipes/minimal-game.md +88 -0
  703. package/skills/jgengine-combat/SKILL.md +38 -0
  704. package/skills/jgengine-combat/api.md +585 -0
  705. package/skills/jgengine-combat/capabilities.md +213 -0
  706. package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
  707. package/skills/jgengine-combat/reference.md +108 -0
  708. package/skills/jgengine-gameplay/SKILL.md +39 -0
  709. package/skills/jgengine-gameplay/api.md +1920 -0
  710. package/skills/jgengine-gameplay/capabilities.md +454 -0
  711. package/skills/jgengine-gameplay/reference-systems.md +56 -0
  712. package/skills/jgengine-world/SKILL.md +51 -0
  713. package/skills/jgengine-world/api.md +3056 -0
  714. package/skills/jgengine-world/capabilities.md +476 -0
  715. package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
  716. package/skills/jgengine-world/reference.md +261 -0
  717. package/dist/cartridge/runtime.d.ts +0 -15
  718. package/dist/cartridge/runtime.js +0 -556
  719. package/dist/cartridge/spec.d.ts +0 -188
  720. package/dist/cartridge/spec.js +0 -19
  721. package/dist/cartridge/validate.d.ts +0 -2
  722. package/dist/cartridge/validate.js +0 -78
  723. package/dist/store/engineStore.d.ts +0 -22
  724. package/dist/store/engineStore.js +0 -50
  725. package/dist/store/features.d.ts +0 -10
  726. package/dist/store/features.js +0 -23
  727. package/llms.txt +0 -5389
@@ -1,4 +1,77 @@
1
- /** Builds a fresh, empty editor document to start authoring a scene from scratch. */
1
+ import { cloneEditorUiDocument, decodeEditorUiDocument } from "../ui/hudDocument.js";
2
+ import { migrateTerrainSnapshot } from "../world/terraform.js";
3
+ import { cloneGridLayer, migrateGridLayer, } from "./grid.js";
4
+ /** Copies a baked minimap so a document's PNG + bounds are never shared by reference across rebuilds. */
5
+ function cloneMinimapBake(minimap) {
6
+ return { background: minimap.background, bounds: { ...minimap.bounds } };
7
+ }
8
+ /** Deep-copies scene-document environment/lighting so edits never mutate history snapshots. */
9
+ function cloneEnvironment(environment) {
10
+ return {
11
+ ...(environment.preset === undefined ? {} : { preset: environment.preset }),
12
+ ...(environment.timeOfDay === undefined ? {} : { timeOfDay: environment.timeOfDay }),
13
+ ...(environment.horizonColor === undefined ? {} : { horizonColor: environment.horizonColor }),
14
+ ...(environment.zenithColor === undefined ? {} : { zenithColor: environment.zenithColor }),
15
+ ...(environment.sunIntensity === undefined ? {} : { sunIntensity: environment.sunIntensity }),
16
+ ...(environment.ambientIntensity === undefined ? {} : { ambientIntensity: environment.ambientIntensity }),
17
+ ...(environment.fog === undefined ? {} : { fog: { ...environment.fog } }),
18
+ };
19
+ }
20
+ function cloneDirective(directive) {
21
+ const area = directive.area === undefined
22
+ ? undefined
23
+ : { min: [...directive.area.min], max: [...directive.area.max] };
24
+ const base = {
25
+ id: directive.id,
26
+ ...(directive.region === undefined ? {} : { region: directive.region }),
27
+ ...(area === undefined ? {} : { area }),
28
+ ...(directive.seed === undefined ? {} : { seed: directive.seed }),
29
+ ...(directive.meta === undefined ? {} : { meta: { ...directive.meta } }),
30
+ };
31
+ if (directive.kind === "scatter") {
32
+ return { ...base, kind: "scatter", asset: directive.asset, density: directive.density,
33
+ ...(directive.minSpacing === undefined ? {} : { minSpacing: directive.minSpacing }),
34
+ ...(directive.jitter === undefined ? {} : { jitter: directive.jitter }),
35
+ ...(directive.minScale === undefined ? {} : { minScale: directive.minScale }),
36
+ ...(directive.maxScale === undefined ? {} : { maxScale: directive.maxScale }),
37
+ ...(directive.minYaw === undefined ? {} : { minYaw: directive.minYaw }),
38
+ ...(directive.maxYaw === undefined ? {} : { maxYaw: directive.maxYaw }) };
39
+ }
40
+ return { ...base, kind: "population", species: directive.species.map((s) => ({ ...s })) };
41
+ }
42
+ function cloneDirectives(directives) {
43
+ return directives === undefined ? undefined : directives.map(cloneDirective);
44
+ }
45
+ function migrateGrids(grids) {
46
+ return grids === undefined ? undefined : grids.map(migrateGridLayer);
47
+ }
48
+ function upsertGrids(base, overlay) {
49
+ if (base === undefined && overlay === undefined)
50
+ return undefined;
51
+ const byId = new Map();
52
+ for (const layer of base ?? [])
53
+ byId.set(layer.id, cloneGridLayer(layer));
54
+ for (const layer of overlay ?? [])
55
+ byId.set(layer.id, cloneGridLayer(layer));
56
+ return [...byId.values()];
57
+ }
58
+ /** Spread-ready non-placeable fields preserved across document rebuilds. @internal */
59
+ export function editorDocumentExtras(doc) {
60
+ return {
61
+ prefabs: doc.prefabs,
62
+ collections: doc.collections,
63
+ catalogs: doc.catalogs,
64
+ ...(doc.grids === undefined ? {} : { grids: doc.grids }),
65
+ ...(doc.terrain === undefined ? {} : { terrain: doc.terrain }),
66
+ ...(doc.ui === undefined ? {} : { ui: doc.ui }),
67
+ ...(doc.directives === undefined ? {} : { directives: doc.directives }),
68
+ ...(doc.minimap === undefined ? {} : { minimap: doc.minimap }),
69
+ ...(doc.environment === undefined ? {} : { environment: doc.environment }),
70
+ };
71
+ }
72
+ /** Builds a fresh, empty editor document to start authoring a scene from scratch.
73
+ * @internal
74
+ */
2
75
  export function createEmptyEditorDocument() {
3
76
  return {
4
77
  version: 1,
@@ -6,10 +79,35 @@ export function createEmptyEditorDocument() {
6
79
  volumes: [],
7
80
  paths: [],
8
81
  annotations: [],
82
+ prefabs: [],
83
+ collections: [],
84
+ catalogs: [],
85
+ };
86
+ }
87
+ function cloneCatalogEntry(entry) {
88
+ return {
89
+ id: entry.id,
90
+ ...(entry.label === undefined ? {} : { label: entry.label }),
91
+ ...(entry.meta === undefined ? {} : { meta: { ...entry.meta } }),
9
92
  };
10
93
  }
11
- /** Deep-copies an editor document so edits never mutate the source. */
94
+ function cloneCatalogs(catalogs) {
95
+ return (catalogs ?? []).map((catalog) => ({
96
+ id: catalog.id,
97
+ ...(catalog.label === undefined ? {} : { label: catalog.label }),
98
+ ...(catalog.schema === undefined ? {} : { schema: catalog.schema }),
99
+ entries: catalog.entries.map(cloneCatalogEntry),
100
+ }));
101
+ }
102
+ /**
103
+ * Deep-copies an editor document so edits never mutate the source. The terrain snapshot is
104
+ * shared by reference: sculpt commands replace it wholesale (copy-on-write), never mutate it in
105
+ * place, so history snapshots stay cheap even on large heightfields.
106
+ * @internal
107
+ */
12
108
  export function cloneEditorDocument(doc) {
109
+ const ui = cloneEditorUiDocument(doc.ui);
110
+ const directives = cloneDirectives(doc.directives);
13
111
  return {
14
112
  version: 1,
15
113
  markers: doc.markers.map((marker) => ({
@@ -33,52 +131,353 @@ export function cloneEditorDocument(doc) {
33
131
  position: { ...note.position },
34
132
  ...(note.meta === undefined ? {} : { meta: { ...note.meta } }),
35
133
  })),
134
+ prefabs: doc.prefabs.map((prefab) => ({
135
+ ...prefab,
136
+ fragment: {
137
+ markers: prefab.fragment.markers.map((marker) => ({ ...marker, position: { ...marker.position } })),
138
+ volumes: prefab.fragment.volumes.map((volume) => ({ ...volume, center: { ...volume.center } })),
139
+ paths: prefab.fragment.paths.map((path) => ({ ...path, points: path.points.map((point) => ({ ...point })) })),
140
+ annotations: prefab.fragment.annotations.map((note) => ({ ...note, position: { ...note.position } })),
141
+ },
142
+ })),
143
+ collections: doc.collections.map((collection) => ({ ...collection, memberIds: [...collection.memberIds] })),
144
+ catalogs: cloneCatalogs(doc.catalogs),
145
+ ...(doc.grids === undefined ? {} : { grids: doc.grids.map(cloneGridLayer) }),
146
+ ...(doc.terrain === undefined ? {} : { terrain: doc.terrain }),
147
+ ...(ui === undefined ? {} : { ui }),
148
+ ...(directives === undefined ? {} : { directives }),
149
+ ...(doc.minimap === undefined ? {} : { minimap: cloneMinimapBake(doc.minimap) }),
150
+ ...(doc.environment === undefined ? {} : { environment: cloneEnvironment(doc.environment) }),
36
151
  };
37
152
  }
38
153
  function asArray(value) {
39
154
  return value === undefined ? [] : [...value];
40
155
  }
41
- /** Resolves a game's `editorLayers` export — document, partial data, or factory — into a full document. */
156
+ function upsertCatalogEntries(base, overlay) {
157
+ return upsertById(base.map(cloneCatalogEntry), overlay.map(cloneCatalogEntry));
158
+ }
159
+ function upsertCatalogs(base, overlay) {
160
+ const byId = new Map((base ?? []).map((catalog) => [
161
+ catalog.id,
162
+ {
163
+ id: catalog.id,
164
+ ...(catalog.label === undefined ? {} : { label: catalog.label }),
165
+ ...(catalog.schema === undefined ? {} : { schema: catalog.schema }),
166
+ entries: catalog.entries.map(cloneCatalogEntry),
167
+ },
168
+ ]));
169
+ for (const catalog of overlay ?? []) {
170
+ const existing = byId.get(catalog.id);
171
+ // Overlay schema/label win when present; otherwise keep whatever the base carried.
172
+ const label = catalog.label ?? existing?.label;
173
+ const schema = catalog.schema ?? existing?.schema;
174
+ byId.set(catalog.id, {
175
+ id: catalog.id,
176
+ ...(label === undefined ? {} : { label }),
177
+ ...(schema === undefined ? {} : { schema }),
178
+ entries: existing === undefined ? catalog.entries.map(cloneCatalogEntry) : upsertCatalogEntries(existing.entries, catalog.entries),
179
+ });
180
+ }
181
+ return [...byId.values()];
182
+ }
183
+ /** Resolves a game's `editorLayers` export — document, partial data, or factory — into a full document.
184
+ * @internal
185
+ */
42
186
  export function normalizeEditorLayers(input) {
43
187
  if (input === undefined || input === null)
44
188
  return createEmptyEditorDocument();
45
189
  const resolved = typeof input === "function" ? input() : input;
190
+ const ui = cloneEditorUiDocument(resolved.ui);
191
+ const grids = migrateGrids(resolved.grids);
192
+ const directives = cloneDirectives(resolved.directives);
46
193
  return {
47
194
  version: 1,
48
195
  markers: asArray(resolved.markers),
49
196
  volumes: asArray(resolved.volumes),
50
197
  paths: asArray(resolved.paths),
51
198
  annotations: asArray(resolved.annotations),
199
+ prefabs: asArray(resolved.prefabs),
200
+ collections: asArray(resolved.collections),
201
+ catalogs: cloneCatalogs(resolved.catalogs),
202
+ ...(grids === undefined ? {} : { grids }),
203
+ ...(resolved.terrain === undefined ? {} : { terrain: migrateTerrainSnapshot(resolved.terrain) }),
204
+ ...(ui === undefined ? {} : { ui }),
205
+ ...(directives === undefined ? {} : { directives }),
206
+ ...(resolved.minimap === undefined ? {} : { minimap: cloneMinimapBake(resolved.minimap) }),
207
+ ...(resolved.environment === undefined ? {} : { environment: cloneEnvironment(resolved.environment) }),
52
208
  };
53
209
  }
54
- /** Combines multiple editor documents' markers, volumes, paths, and notes into one. */
210
+ /**
211
+ * Reserves `id` in `taken` and returns it unchanged when free; otherwise mints the next free
212
+ * `<id>_copy`/`<id>_copyN` suffix and reserves that instead. The one place object ids are made
213
+ * unique across a document's markers/volumes/paths/annotations as a single global namespace, so a
214
+ * merged-in or imported object can never silently shadow an existing object in another collection.
215
+ * @internal
216
+ */
217
+ export function ensureUniqueEditorId(id, taken) {
218
+ if (!taken.has(id)) {
219
+ taken.add(id);
220
+ return id;
221
+ }
222
+ let candidate = `${id}_copy`;
223
+ let n = 2;
224
+ while (taken.has(candidate)) {
225
+ candidate = `${id}_copy${n}`;
226
+ n += 1;
227
+ }
228
+ taken.add(candidate);
229
+ return candidate;
230
+ }
231
+ /** Combines multiple editor documents' markers, volumes, paths, notes, prefabs, and collections
232
+ * into one, re-idding any placeable object whose id collides with one already merged in — across
233
+ * all four collections, since selection/parenting/removal treat those ids as one document-global
234
+ * namespace.
235
+ * @internal
236
+ */
55
237
  export function mergeEditorDocuments(...docs) {
56
238
  const out = createEmptyEditorDocument();
239
+ const taken = new Set();
240
+ const remap = (item) => {
241
+ const id = ensureUniqueEditorId(item.id, taken);
242
+ return id === item.id ? item : { ...item, id };
243
+ };
57
244
  for (const doc of docs) {
58
- out.markers.push(...doc.markers);
59
- out.volumes.push(...doc.volumes);
60
- out.paths.push(...doc.paths);
61
- out.annotations.push(...doc.annotations);
245
+ out.markers.push(...doc.markers.map(remap));
246
+ out.volumes.push(...doc.volumes.map(remap));
247
+ out.paths.push(...doc.paths.map(remap));
248
+ out.annotations.push(...doc.annotations.map(remap));
249
+ out.prefabs.push(...doc.prefabs);
250
+ out.collections.push(...doc.collections);
251
+ out.catalogs = upsertCatalogs(out.catalogs, doc.catalogs);
252
+ const mergedGrids = upsertGrids(out.grids, doc.grids);
253
+ if (mergedGrids !== undefined)
254
+ out.grids = mergedGrids;
255
+ if (doc.terrain !== undefined)
256
+ out.terrain = doc.terrain;
257
+ if (doc.minimap !== undefined)
258
+ out.minimap = cloneMinimapBake(doc.minimap);
259
+ if (doc.environment !== undefined)
260
+ out.environment = cloneEnvironment(doc.environment);
261
+ if (doc.directives !== undefined) {
262
+ out.directives = upsertById(out.directives ?? [], doc.directives);
263
+ }
264
+ if (doc.ui !== undefined) {
265
+ out.ui = {
266
+ panels: {
267
+ ...(out.ui?.panels ?? {}),
268
+ ...Object.fromEntries(Object.entries(doc.ui.panels).map(([id, panel]) => [id, { ...panel }])),
269
+ },
270
+ };
271
+ }
62
272
  }
63
273
  return out;
64
274
  }
65
- /** Looks up a marker by id in an editor document. */
275
+ /** Extracts the subset of a document matching the given ids — the clipboard fragment for copy/paste.
276
+ * @internal
277
+ */
278
+ export function extractEditorFragment(doc, ids) {
279
+ const wanted = new Set(ids);
280
+ return cloneEditorDocument({
281
+ version: 1,
282
+ markers: doc.markers.filter((marker) => wanted.has(marker.id)),
283
+ volumes: doc.volumes.filter((volume) => wanted.has(volume.id)),
284
+ paths: doc.paths.filter((path) => wanted.has(path.id)),
285
+ annotations: doc.annotations.filter((note) => wanted.has(note.id)),
286
+ prefabs: [],
287
+ collections: [],
288
+ catalogs: [],
289
+ });
290
+ }
291
+ /** Counts every object in a document across markers, volumes, paths, and notes.
292
+ * @internal
293
+ */
294
+ export function editorDocumentSize(doc) {
295
+ return doc.markers.length + doc.volumes.length + doc.paths.length + doc.annotations.length;
296
+ }
297
+ /** Looks up a prefab by id in an editor document.
298
+ * @internal
299
+ */
300
+ export function findEditorPrefab(doc, id) {
301
+ return doc.prefabs.find((prefab) => prefab.id === id);
302
+ }
303
+ /** Looks up a named collection / selection set by id in an editor document.
304
+ * @internal
305
+ */
306
+ export function findEditorCollection(doc, id) {
307
+ return doc.collections.find((collection) => collection.id === id);
308
+ }
309
+ /**
310
+ * Looks up a gameplay data catalog by id on the scene document.
311
+ * @capability editor-catalogs Find a persisted catalog bag by id.
312
+ */
313
+ export function findEditorCatalog(doc, id) {
314
+ return doc.catalogs.find((catalog) => catalog.id === id);
315
+ }
316
+ /**
317
+ * Looks up one entry inside a gameplay data catalog.
318
+ * @capability editor-catalogs Find a catalog entry's meta bag by catalog + entry id.
319
+ */
320
+ export function findEditorCatalogEntry(doc, catalogId, entryId) {
321
+ return findEditorCatalog(doc, catalogId)?.entries.find((entry) => entry.id === entryId);
322
+ }
323
+ /**
324
+ * Seeds default catalog rows from game-exported definitions into a document: missing catalogs and
325
+ * missing entries are filled from the definition; existing document values win (overlay already applied).
326
+ * @capability editor-catalogs Seed scene-document catalogs from game-exported schemas.
327
+ */
328
+ export function seedEditorCatalogs(doc, definitions) {
329
+ if (definitions.length === 0)
330
+ return doc;
331
+ const next = cloneCatalogs(doc.catalogs);
332
+ for (const definition of definitions) {
333
+ const existingIndex = next.findIndex((catalog) => catalog.id === definition.id);
334
+ const existing = existingIndex >= 0 ? next[existingIndex] : undefined;
335
+ const byId = new Map((existing?.entries ?? []).map((entry) => [entry.id, cloneCatalogEntry(entry)]));
336
+ for (const entry of definition.entries) {
337
+ if (!byId.has(entry.id))
338
+ byId.set(entry.id, cloneCatalogEntry(entry));
339
+ }
340
+ const catalog = {
341
+ id: definition.id,
342
+ ...(existing?.label === undefined ? {} : { label: existing.label }),
343
+ ...(existing?.schema === undefined ? {} : { schema: existing.schema }),
344
+ entries: [...byId.values()],
345
+ };
346
+ if (existingIndex >= 0)
347
+ next[existingIndex] = catalog;
348
+ else
349
+ next.push(catalog);
350
+ }
351
+ return { ...doc, catalogs: next };
352
+ }
353
+ /**
354
+ * True when an object carries a per-object `locked` flag (hierarchy lock toggle).
355
+ * Collection membership is checked separately by {@link isEditorObjectLocked}.
356
+ * @internal
357
+ */
358
+ export function isEditorObjectSelfLocked(doc, id) {
359
+ const marker = doc.markers.find((entry) => entry.id === id);
360
+ if (marker !== undefined)
361
+ return marker.locked === true;
362
+ const volume = doc.volumes.find((entry) => entry.id === id);
363
+ if (volume !== undefined)
364
+ return volume.locked === true;
365
+ const path = doc.paths.find((entry) => entry.id === id);
366
+ if (path !== undefined)
367
+ return path.locked === true;
368
+ const note = doc.annotations.find((entry) => entry.id === id);
369
+ return note?.locked === true;
370
+ }
371
+ /**
372
+ * True when an object is a member of any locked collection (production group lock).
373
+ * @internal
374
+ */
375
+ export function isEditorObjectCollectionLocked(doc, id) {
376
+ return doc.collections.some((collection) => collection.locked === true && collection.memberIds.includes(id));
377
+ }
378
+ /**
379
+ * True when an object id is locked for edit — either its own `locked` flag or membership in a
380
+ * locked collection. Blocks move/delete via session mutations.
381
+ * @internal
382
+ */
383
+ export function isEditorObjectLocked(doc, id) {
384
+ return isEditorObjectSelfLocked(doc, id) || isEditorObjectCollectionLocked(doc, id);
385
+ }
386
+ /**
387
+ * True when an object carries `hidden: true` — omitted from editor viewport overlays and picking
388
+ * while remaining in the hierarchy and document.
389
+ * @internal
390
+ */
391
+ export function isEditorObjectHidden(doc, id) {
392
+ const marker = doc.markers.find((entry) => entry.id === id);
393
+ if (marker !== undefined)
394
+ return marker.hidden === true;
395
+ const volume = doc.volumes.find((entry) => entry.id === id);
396
+ if (volume !== undefined)
397
+ return volume.hidden === true;
398
+ const path = doc.paths.find((entry) => entry.id === id);
399
+ if (path !== undefined)
400
+ return path.hidden === true;
401
+ const note = doc.annotations.find((entry) => entry.id === id);
402
+ return note?.hidden === true;
403
+ }
404
+ /**
405
+ * Extracts the selected ids into a prefab fragment centered on their own bounds centroid, so the
406
+ * same prefab reinserts consistently regardless of where in the scene (or which game) it lands.
407
+ * @internal
408
+ */
409
+ export function createPrefabFragment(doc, ids) {
410
+ const extracted = extractEditorFragment(doc, ids);
411
+ const bounds = editorDocumentBounds(extracted);
412
+ const origin = bounds === null
413
+ ? { x: 0, y: 0, z: 0 }
414
+ : {
415
+ x: (bounds.min.x + bounds.max.x) / 2,
416
+ y: (bounds.min.y + bounds.max.y) / 2,
417
+ z: (bounds.min.z + bounds.max.z) / 2,
418
+ };
419
+ const shift = (point) => ({
420
+ x: point.x - origin.x,
421
+ y: point.y - origin.y,
422
+ z: point.z - origin.z,
423
+ });
424
+ return {
425
+ markers: extracted.markers.map((marker) => ({ ...marker, position: shift(marker.position) })),
426
+ volumes: extracted.volumes.map((volume) => ({ ...volume, center: shift(volume.center) })),
427
+ paths: extracted.paths.map((path) => ({ ...path, points: path.points.map(shift) })),
428
+ annotations: extracted.annotations.map((note) => ({ ...note, position: shift(note.position) })),
429
+ };
430
+ }
431
+ /** Looks up a marker by id in an editor document.
432
+ * @internal
433
+ */
66
434
  export function findEditorMarker(doc, id) {
67
435
  return doc.markers.find((marker) => marker.id === id);
68
436
  }
69
- /** Looks up a volume by id in an editor document. */
437
+ /** Looks up a marker by id and throws when it is absent — for scene contracts where a
438
+ * missing marker is an authoring error to surface loudly, not a runtime branch to handle.
439
+ * @internal
440
+ */
441
+ export function requireEditorMarker(doc, id) {
442
+ const marker = findEditorMarker(doc, id);
443
+ if (marker === undefined) {
444
+ throw new Error(`editor document has no marker with id "${id}"`);
445
+ }
446
+ return marker;
447
+ }
448
+ /** A marker's world position as an `[x, y, z]` tuple — the shared shape gameplay tables read.
449
+ * @internal
450
+ */
451
+ export function editorMarkerPosition(marker) {
452
+ return [marker.position.x, marker.position.y, marker.position.z];
453
+ }
454
+ /** A marker's ground position as an `[x, z]` tuple, dropping the vertical axis.
455
+ * @internal
456
+ */
457
+ export function editorMarkerXZ(marker) {
458
+ return [marker.position.x, marker.position.z];
459
+ }
460
+ /** Looks up a volume by id in an editor document.
461
+ * @internal
462
+ */
70
463
  export function findEditorVolume(doc, id) {
71
464
  return doc.volumes.find((volume) => volume.id === id);
72
465
  }
73
- /** Looks up a path by id in an editor document. */
466
+ /** Looks up a path by id in an editor document.
467
+ * @internal
468
+ */
74
469
  export function findEditorPath(doc, id) {
75
470
  return doc.paths.find((path) => path.id === id);
76
471
  }
77
- /** Looks up an annotation note by id in an editor document. */
472
+ /** Looks up an annotation note by id in an editor document.
473
+ * @internal
474
+ */
78
475
  export function findEditorNote(doc, id) {
79
476
  return doc.annotations.find((note) => note.id === id);
80
477
  }
81
- /** Lists the distinct marker, volume, and path kinds authored in a document. */
478
+ /** Lists the distinct marker, volume, and path kinds authored in a document.
479
+ * @internal
480
+ */
82
481
  export function listEditorKinds(doc) {
83
482
  const markers = new Set();
84
483
  const volumes = new Set();
@@ -95,19 +494,767 @@ export function listEditorKinds(doc) {
95
494
  paths: [...paths].sort(),
96
495
  };
97
496
  }
98
- /** Serializes an editor document to JSON text for saving or export. */
497
+ function editorNodes(doc) {
498
+ return [...doc.markers, ...doc.volumes, ...doc.paths, ...doc.annotations];
499
+ }
500
+ /** The id of an object's parent, or undefined when it is a root (or unknown).
501
+ * @internal
502
+ */
503
+ export function editorParentOf(doc, id) {
504
+ return editorNodes(doc).find((node) => node.id === id)?.parentId;
505
+ }
506
+ /** The direct child ids of an object (empty when it has none).
507
+ * @internal
508
+ */
509
+ export function editorChildren(doc, parentId) {
510
+ return editorNodes(doc)
511
+ .filter((node) => node.parentId === parentId)
512
+ .map((node) => node.id);
513
+ }
514
+ /** Object ids with no parent (or whose parent no longer exists) — the roots of the hierarchy.
515
+ * @internal
516
+ */
517
+ export function editorRoots(doc) {
518
+ const ids = new Set(editorNodes(doc).map((node) => node.id));
519
+ return editorNodes(doc)
520
+ .filter((node) => node.parentId === undefined || !ids.has(node.parentId))
521
+ .map((node) => node.id);
522
+ }
523
+ /** Every descendant id of the given ids (children, grandchildren, …), excluding the inputs.
524
+ * @internal
525
+ */
526
+ export function collectDescendants(doc, ids) {
527
+ const childrenByParent = new Map();
528
+ for (const node of editorNodes(doc)) {
529
+ if (node.parentId === undefined)
530
+ continue;
531
+ const bucket = childrenByParent.get(node.parentId);
532
+ if (bucket === undefined)
533
+ childrenByParent.set(node.parentId, [node.id]);
534
+ else
535
+ bucket.push(node.id);
536
+ }
537
+ const out = new Set();
538
+ const stack = [...ids];
539
+ while (stack.length > 0) {
540
+ const current = stack.pop();
541
+ for (const child of childrenByParent.get(current) ?? []) {
542
+ if (out.has(child))
543
+ continue;
544
+ out.add(child);
545
+ stack.push(child);
546
+ }
547
+ }
548
+ return out;
549
+ }
550
+ /** True when parenting `id` under `parentId` would form a cycle (or parent itself to itself).
551
+ * @internal
552
+ */
553
+ export function wouldCreateCycle(doc, id, parentId) {
554
+ if (parentId === null)
555
+ return false;
556
+ if (parentId === id)
557
+ return true;
558
+ return collectDescendants(doc, [id]).has(parentId);
559
+ }
560
+ /** Serializes an editor document to JSON text for saving or export.
561
+ * @internal
562
+ */
99
563
  export function exportEditorDocumentJson(doc, pretty = true) {
100
564
  return JSON.stringify(doc, null, pretty ? 2 : undefined);
101
565
  }
102
- /** Parses JSON text back into a normalized editor document. */
566
+ function isPlainObject(value) {
567
+ return typeof value === "object" && value !== null && !Array.isArray(value);
568
+ }
569
+ function decodeArray(value, path, decodeItem, errors) {
570
+ if (value === undefined)
571
+ return [];
572
+ if (!Array.isArray(value)) {
573
+ errors.push({ path, message: "expected an array" });
574
+ return [];
575
+ }
576
+ const out = [];
577
+ value.forEach((item, index) => {
578
+ const decoded = decodeItem(item, `${path}[${index}]`, errors);
579
+ if (decoded !== null)
580
+ out.push(decoded);
581
+ });
582
+ return out;
583
+ }
584
+ function decodeVec3(value, path, errors) {
585
+ if (!isPlainObject(value) || typeof value.x !== "number" || typeof value.y !== "number" || typeof value.z !== "number") {
586
+ errors.push({ path, message: "expected {x,y,z} numbers" });
587
+ return null;
588
+ }
589
+ return { x: value.x, y: value.y, z: value.z };
590
+ }
591
+ function decodeMeta(value, path, errors) {
592
+ if (value === undefined)
593
+ return undefined;
594
+ if (!isPlainObject(value)) {
595
+ errors.push({ path, message: "expected an object" });
596
+ return undefined;
597
+ }
598
+ return value;
599
+ }
600
+ function decodeMarker(item, path, errors) {
601
+ if (!isPlainObject(item)) {
602
+ errors.push({ path, message: "expected an object" });
603
+ return null;
604
+ }
605
+ if (typeof item.id !== "string")
606
+ errors.push({ path: `${path}.id`, message: "expected a string" });
607
+ if (typeof item.kind !== "string")
608
+ errors.push({ path: `${path}.kind`, message: "expected a string" });
609
+ const position = decodeVec3(item.position, `${path}.position`, errors);
610
+ if (typeof item.id !== "string" || typeof item.kind !== "string" || position === null)
611
+ return null;
612
+ const marker = { id: item.id, kind: item.kind, position };
613
+ if (typeof item.rotationY === "number")
614
+ marker.rotationY = item.rotationY;
615
+ if (typeof item.color === "string")
616
+ marker.color = item.color;
617
+ if (typeof item.label === "string")
618
+ marker.label = item.label;
619
+ if (typeof item.catalogId === "string")
620
+ marker.catalogId = item.catalogId;
621
+ if (typeof item.parentId === "string")
622
+ marker.parentId = item.parentId;
623
+ if (typeof item.locked === "boolean")
624
+ marker.locked = item.locked;
625
+ if (typeof item.hidden === "boolean")
626
+ marker.hidden = item.hidden;
627
+ const meta = decodeMeta(item.meta, `${path}.meta`, errors);
628
+ if (meta !== undefined)
629
+ marker.meta = meta;
630
+ return marker;
631
+ }
632
+ const VOLUME_SHAPES = new Set(["sphere", "cylinder", "box"]);
633
+ function decodeVolume(item, path, errors) {
634
+ if (!isPlainObject(item)) {
635
+ errors.push({ path, message: "expected an object" });
636
+ return null;
637
+ }
638
+ if (typeof item.id !== "string")
639
+ errors.push({ path: `${path}.id`, message: "expected a string" });
640
+ if (typeof item.kind !== "string")
641
+ errors.push({ path: `${path}.kind`, message: "expected a string" });
642
+ const validShape = typeof item.shape === "string" && VOLUME_SHAPES.has(item.shape);
643
+ if (!validShape)
644
+ errors.push({ path: `${path}.shape`, message: "expected sphere | cylinder | box" });
645
+ const center = decodeVec3(item.center, `${path}.center`, errors);
646
+ if (typeof item.id !== "string" || typeof item.kind !== "string" || !validShape || center === null)
647
+ return null;
648
+ const volume = { id: item.id, kind: item.kind, shape: item.shape, center };
649
+ if (typeof item.radius === "number")
650
+ volume.radius = item.radius;
651
+ if (typeof item.height === "number")
652
+ volume.height = item.height;
653
+ if (item.halfExtents !== undefined) {
654
+ const halfExtents = decodeVec3(item.halfExtents, `${path}.halfExtents`, errors);
655
+ if (halfExtents !== null)
656
+ volume.halfExtents = halfExtents;
657
+ }
658
+ if (typeof item.color === "string")
659
+ volume.color = item.color;
660
+ if (typeof item.label === "string")
661
+ volume.label = item.label;
662
+ if (typeof item.parentId === "string")
663
+ volume.parentId = item.parentId;
664
+ if (typeof item.locked === "boolean")
665
+ volume.locked = item.locked;
666
+ if (typeof item.hidden === "boolean")
667
+ volume.hidden = item.hidden;
668
+ const meta = decodeMeta(item.meta, `${path}.meta`, errors);
669
+ if (meta !== undefined)
670
+ volume.meta = meta;
671
+ return volume;
672
+ }
673
+ function decodePath(item, path, errors) {
674
+ if (!isPlainObject(item)) {
675
+ errors.push({ path, message: "expected an object" });
676
+ return null;
677
+ }
678
+ if (typeof item.id !== "string")
679
+ errors.push({ path: `${path}.id`, message: "expected a string" });
680
+ if (typeof item.kind !== "string")
681
+ errors.push({ path: `${path}.kind`, message: "expected a string" });
682
+ const validPoints = Array.isArray(item.points);
683
+ if (!validPoints)
684
+ errors.push({ path: `${path}.points`, message: "expected an array" });
685
+ const points = validPoints
686
+ ? decodeArray(item.points, `${path}.points`, decodeVec3, errors)
687
+ : [];
688
+ if (typeof item.id !== "string" || typeof item.kind !== "string" || !validPoints)
689
+ return null;
690
+ const decodedPath = { id: item.id, kind: item.kind, points };
691
+ if (typeof item.width === "number")
692
+ decodedPath.width = item.width;
693
+ if (typeof item.color === "string")
694
+ decodedPath.color = item.color;
695
+ if (typeof item.label === "string")
696
+ decodedPath.label = item.label;
697
+ if (typeof item.parentId === "string")
698
+ decodedPath.parentId = item.parentId;
699
+ if (typeof item.locked === "boolean")
700
+ decodedPath.locked = item.locked;
701
+ if (typeof item.hidden === "boolean")
702
+ decodedPath.hidden = item.hidden;
703
+ const meta = decodeMeta(item.meta, `${path}.meta`, errors);
704
+ if (meta !== undefined)
705
+ decodedPath.meta = meta;
706
+ return decodedPath;
707
+ }
708
+ function decodeNote(item, path, errors) {
709
+ if (!isPlainObject(item)) {
710
+ errors.push({ path, message: "expected an object" });
711
+ return null;
712
+ }
713
+ if (typeof item.id !== "string")
714
+ errors.push({ path: `${path}.id`, message: "expected a string" });
715
+ if (typeof item.text !== "string")
716
+ errors.push({ path: `${path}.text`, message: "expected a string" });
717
+ const position = decodeVec3(item.position, `${path}.position`, errors);
718
+ if (typeof item.id !== "string" || typeof item.text !== "string" || position === null)
719
+ return null;
720
+ const note = { id: item.id, text: item.text, position };
721
+ if (typeof item.color === "string")
722
+ note.color = item.color;
723
+ if (typeof item.parentId === "string")
724
+ note.parentId = item.parentId;
725
+ if (typeof item.locked === "boolean")
726
+ note.locked = item.locked;
727
+ if (typeof item.hidden === "boolean")
728
+ note.hidden = item.hidden;
729
+ const meta = decodeMeta(item.meta, `${path}.meta`, errors);
730
+ if (meta !== undefined)
731
+ note.meta = meta;
732
+ return note;
733
+ }
734
+ function decodeFragmentContent(value, path, errors) {
735
+ if (!isPlainObject(value)) {
736
+ errors.push({ path, message: "expected an object" });
737
+ return null;
738
+ }
739
+ return {
740
+ markers: decodeArray(value.markers, `${path}.markers`, decodeMarker, errors),
741
+ volumes: decodeArray(value.volumes, `${path}.volumes`, decodeVolume, errors),
742
+ paths: decodeArray(value.paths, `${path}.paths`, decodePath, errors),
743
+ annotations: decodeArray(value.annotations, `${path}.annotations`, decodeNote, errors),
744
+ };
745
+ }
746
+ function decodePrefab(item, path, errors) {
747
+ if (!isPlainObject(item)) {
748
+ errors.push({ path, message: "expected an object" });
749
+ return null;
750
+ }
751
+ if (typeof item.id !== "string")
752
+ errors.push({ path: `${path}.id`, message: "expected a string" });
753
+ if (typeof item.name !== "string")
754
+ errors.push({ path: `${path}.name`, message: "expected a string" });
755
+ const fragment = decodeFragmentContent(item.fragment, `${path}.fragment`, errors);
756
+ if (typeof item.id !== "string" || typeof item.name !== "string" || fragment === null)
757
+ return null;
758
+ return { id: item.id, name: item.name, fragment };
759
+ }
760
+ function decodeCollection(item, path, errors) {
761
+ if (!isPlainObject(item)) {
762
+ errors.push({ path, message: "expected an object" });
763
+ return null;
764
+ }
765
+ if (typeof item.id !== "string")
766
+ errors.push({ path: `${path}.id`, message: "expected a string" });
767
+ if (typeof item.name !== "string")
768
+ errors.push({ path: `${path}.name`, message: "expected a string" });
769
+ const memberIds = [];
770
+ if (!Array.isArray(item.memberIds)) {
771
+ errors.push({ path: `${path}.memberIds`, message: "expected an array of strings" });
772
+ }
773
+ else {
774
+ item.memberIds.forEach((member, index) => {
775
+ if (typeof member === "string")
776
+ memberIds.push(member);
777
+ else
778
+ errors.push({ path: `${path}.memberIds[${index}]`, message: "expected a string" });
779
+ });
780
+ }
781
+ if (typeof item.id !== "string" || typeof item.name !== "string" || !Array.isArray(item.memberIds))
782
+ return null;
783
+ const collection = { id: item.id, name: item.name, memberIds };
784
+ if (typeof item.color === "string")
785
+ collection.color = item.color;
786
+ if (typeof item.locked === "boolean")
787
+ collection.locked = item.locked;
788
+ if (typeof item.visible === "boolean")
789
+ collection.visible = item.visible;
790
+ return collection;
791
+ }
792
+ function decodeCatalogEntry(item, path, errors) {
793
+ if (!isPlainObject(item)) {
794
+ errors.push({ path, message: "expected an object" });
795
+ return null;
796
+ }
797
+ if (typeof item.id !== "string") {
798
+ errors.push({ path: `${path}.id`, message: "expected a string" });
799
+ return null;
800
+ }
801
+ const entry = { id: item.id };
802
+ if (typeof item.label === "string")
803
+ entry.label = item.label;
804
+ const meta = decodeMeta(item.meta, `${path}.meta`, errors);
805
+ if (meta !== undefined)
806
+ entry.meta = meta;
807
+ return entry;
808
+ }
809
+ /**
810
+ * Decodes a document-carried `ParamSchema` (`{ fields: ParamField[] }`) — modeled on
811
+ * `loadGameCatalogs`'s `decodeSchema`. Any field that is not a plain object with string `key` and
812
+ * `type` is dropped with a diagnostic instead of poisoning the whole schema; a non-array `fields`
813
+ * value drops the schema entirely (undefined). Structural but permissive: field-shape validation
814
+ * beyond key/type is left to `parseParams`, which defaults anything it cannot read.
815
+ */
816
+ function decodeParamSchema(raw, path, errors) {
817
+ if (raw === undefined)
818
+ return undefined;
819
+ if (!isPlainObject(raw) || !Array.isArray(raw.fields)) {
820
+ errors.push({ path, message: "expected { fields: ParamField[] }" });
821
+ return undefined;
822
+ }
823
+ const fields = [];
824
+ raw.fields.forEach((field, index) => {
825
+ if (!isPlainObject(field) || typeof field.key !== "string" || typeof field.type !== "string") {
826
+ errors.push({ path: `${path}.fields[${index}]`, message: "expected a ParamField with string key + type" });
827
+ return;
828
+ }
829
+ fields.push(field);
830
+ });
831
+ return { fields, ...(Array.isArray(raw.groups) ? { groups: raw.groups } : {}) };
832
+ }
833
+ function decodeCatalog(item, path, errors) {
834
+ if (!isPlainObject(item)) {
835
+ errors.push({ path, message: "expected an object" });
836
+ return null;
837
+ }
838
+ if (typeof item.id !== "string") {
839
+ errors.push({ path: `${path}.id`, message: "expected a string" });
840
+ return null;
841
+ }
842
+ const entries = decodeArray(item.entries, `${path}.entries`, decodeCatalogEntry, errors);
843
+ const schema = decodeParamSchema(item.schema, `${path}.schema`, errors);
844
+ const catalog = { id: item.id, entries };
845
+ if (typeof item.label === "string")
846
+ catalog.label = item.label;
847
+ if (schema !== undefined)
848
+ catalog.schema = schema;
849
+ return catalog;
850
+ }
851
+ function decodeDirectiveArea(value, path, errors) {
852
+ if (value === undefined)
853
+ return undefined;
854
+ if (!isPlainObject(value)) {
855
+ errors.push({ path, message: "expected an object" });
856
+ return undefined;
857
+ }
858
+ const pair = (v, p) => {
859
+ if (!Array.isArray(v) || v.length !== 2 || typeof v[0] !== "number" || typeof v[1] !== "number") {
860
+ errors.push({ path: p, message: "expected [number, number]" });
861
+ return null;
862
+ }
863
+ return [v[0], v[1]];
864
+ };
865
+ const min = pair(value.min, `${path}.min`);
866
+ const max = pair(value.max, `${path}.max`);
867
+ if (min === null || max === null)
868
+ return undefined;
869
+ return { min, max };
870
+ }
871
+ function decodeSpecies(item, path, errors) {
872
+ if (!isPlainObject(item)) {
873
+ errors.push({ path, message: "expected an object" });
874
+ return null;
875
+ }
876
+ if (typeof item.id !== "string")
877
+ errors.push({ path: `${path}.id`, message: "expected a string" });
878
+ if (typeof item.cap !== "number")
879
+ errors.push({ path: `${path}.cap`, message: "expected a number" });
880
+ if (typeof item.id !== "string" || typeof item.cap !== "number")
881
+ return null;
882
+ const species = { id: item.id, cap: item.cap };
883
+ if (typeof item.weight === "number")
884
+ species.weight = item.weight;
885
+ return species;
886
+ }
887
+ function decodeDirective(item, path, errors) {
888
+ if (!isPlainObject(item)) {
889
+ errors.push({ path, message: "expected an object" });
890
+ return null;
891
+ }
892
+ if (typeof item.id !== "string")
893
+ errors.push({ path: `${path}.id`, message: "expected a string" });
894
+ const kindOk = item.kind === "scatter" || item.kind === "population";
895
+ if (!kindOk)
896
+ errors.push({ path: `${path}.kind`, message: "expected \"scatter\" | \"population\"" });
897
+ const area = decodeDirectiveArea(item.area, `${path}.area`, errors);
898
+ if (typeof item.id !== "string" || !kindOk)
899
+ return null;
900
+ const base = {
901
+ id: item.id,
902
+ ...(typeof item.region === "string" ? { region: item.region } : {}),
903
+ ...(area === undefined ? {} : { area }),
904
+ ...(typeof item.seed === "string" || typeof item.seed === "number" ? { seed: item.seed } : {}),
905
+ ...(isPlainObject(item.meta) ? { meta: item.meta } : {}),
906
+ };
907
+ if (item.kind === "scatter") {
908
+ if (typeof item.asset !== "string")
909
+ errors.push({ path: `${path}.asset`, message: "expected a string" });
910
+ if (typeof item.density !== "number")
911
+ errors.push({ path: `${path}.density`, message: "expected a number" });
912
+ if (typeof item.asset !== "string" || typeof item.density !== "number")
913
+ return null;
914
+ const scatter = { ...base, kind: "scatter", asset: item.asset, density: item.density };
915
+ for (const key of ["minSpacing", "jitter", "minScale", "maxScale", "minYaw", "maxYaw"]) {
916
+ if (typeof item[key] === "number")
917
+ scatter[key] = item[key];
918
+ }
919
+ return scatter;
920
+ }
921
+ const species = decodeArray(item.species, `${path}.species`, decodeSpecies, errors);
922
+ return { ...base, kind: "population", species };
923
+ }
924
+ /**
925
+ * Validates a baked minimap arriving from outside this process: `background` must be a
926
+ * `data:image/…` URI and `bounds` must be four finite numbers, so a malformed bake is rejected with a
927
+ * path-specific diagnostic instead of feeding a broken `<image href>` to the runtime minimap.
928
+ */
929
+ function decodeMinimapBake(value, path, errors) {
930
+ if (!isPlainObject(value)) {
931
+ errors.push({ path, message: "expected an object" });
932
+ return undefined;
933
+ }
934
+ const backgroundOk = typeof value.background === "string" && value.background.startsWith("data:image/");
935
+ if (!backgroundOk) {
936
+ errors.push({ path: `${path}.background`, message: "expected a data:image/… string" });
937
+ }
938
+ const bounds = value.bounds;
939
+ const boundsOk = isPlainObject(bounds) &&
940
+ ["minX", "minZ", "maxX", "maxZ"].every((key) => typeof bounds[key] === "number" && Number.isFinite(bounds[key]));
941
+ if (!boundsOk) {
942
+ errors.push({ path: `${path}.bounds`, message: "expected {minX,minZ,maxX,maxZ} finite numbers" });
943
+ }
944
+ if (!backgroundOk || !boundsOk)
945
+ return undefined;
946
+ const b = bounds;
947
+ return {
948
+ background: value.background,
949
+ bounds: { minX: b.minX, minZ: b.minZ, maxX: b.maxX, maxZ: b.maxZ },
950
+ };
951
+ }
952
+ const SKY_PRESETS = new Set(["day", "dusk", "night"]);
953
+ /**
954
+ * Validates scene-document environment/lighting. Unknown presets, non-finite intensities, and
955
+ * malformed fog objects produce path-specific diagnostics; empty valid objects are accepted so a
956
+ * game can seed `environment: {}` as "use engine defaults, ready to author".
957
+ */
958
+ function decodeEnvironment(value, path, errors) {
959
+ if (!isPlainObject(value)) {
960
+ errors.push({ path, message: "expected an object" });
961
+ return undefined;
962
+ }
963
+ let failed = false;
964
+ const env = {};
965
+ if (value.preset !== undefined) {
966
+ if (typeof value.preset === "string" && SKY_PRESETS.has(value.preset)) {
967
+ env.preset = value.preset;
968
+ }
969
+ else {
970
+ errors.push({ path: `${path}.preset`, message: 'expected "day" | "dusk" | "night"' });
971
+ failed = true;
972
+ }
973
+ }
974
+ if (value.timeOfDay !== undefined) {
975
+ if (typeof value.timeOfDay === "boolean")
976
+ env.timeOfDay = value.timeOfDay;
977
+ else {
978
+ errors.push({ path: `${path}.timeOfDay`, message: "expected a boolean" });
979
+ failed = true;
980
+ }
981
+ }
982
+ if (value.horizonColor !== undefined) {
983
+ if (typeof value.horizonColor === "string")
984
+ env.horizonColor = value.horizonColor;
985
+ else {
986
+ errors.push({ path: `${path}.horizonColor`, message: "expected a string" });
987
+ failed = true;
988
+ }
989
+ }
990
+ if (value.zenithColor !== undefined) {
991
+ if (typeof value.zenithColor === "string")
992
+ env.zenithColor = value.zenithColor;
993
+ else {
994
+ errors.push({ path: `${path}.zenithColor`, message: "expected a string" });
995
+ failed = true;
996
+ }
997
+ }
998
+ for (const key of ["sunIntensity", "ambientIntensity"]) {
999
+ if (value[key] === undefined)
1000
+ continue;
1001
+ if (typeof value[key] === "number" && Number.isFinite(value[key]))
1002
+ env[key] = value[key];
1003
+ else {
1004
+ errors.push({ path: `${path}.${key}`, message: "expected a finite number" });
1005
+ failed = true;
1006
+ }
1007
+ }
1008
+ if (value.fog !== undefined) {
1009
+ if (!isPlainObject(value.fog)) {
1010
+ errors.push({ path: `${path}.fog`, message: "expected an object" });
1011
+ failed = true;
1012
+ }
1013
+ else {
1014
+ const fog = {};
1015
+ if (value.fog.color !== undefined) {
1016
+ if (typeof value.fog.color === "string")
1017
+ fog.color = value.fog.color;
1018
+ else {
1019
+ errors.push({ path: `${path}.fog.color`, message: "expected a string" });
1020
+ failed = true;
1021
+ }
1022
+ }
1023
+ for (const key of ["near", "far"]) {
1024
+ if (value.fog[key] === undefined)
1025
+ continue;
1026
+ if (typeof value.fog[key] === "number" && Number.isFinite(value.fog[key]))
1027
+ fog[key] = value.fog[key];
1028
+ else {
1029
+ errors.push({ path: `${path}.fog.${key}`, message: "expected a finite number" });
1030
+ failed = true;
1031
+ }
1032
+ }
1033
+ if (Object.keys(fog).length > 0)
1034
+ env.fog = fog;
1035
+ }
1036
+ }
1037
+ if (failed)
1038
+ return undefined;
1039
+ return env;
1040
+ }
1041
+ const GRID_AXES = new Set(["xz", "xy"]);
1042
+ function decodeGridPaletteEntry(item, path, errors) {
1043
+ if (!isPlainObject(item)) {
1044
+ errors.push({ path, message: "expected an object" });
1045
+ return null;
1046
+ }
1047
+ if (typeof item.id !== "string") {
1048
+ errors.push({ path: `${path}.id`, message: "expected a string" });
1049
+ return null;
1050
+ }
1051
+ const entry = { id: item.id };
1052
+ if (typeof item.label === "string")
1053
+ entry.label = item.label;
1054
+ if (typeof item.glyph === "string")
1055
+ entry.glyph = item.glyph;
1056
+ if (typeof item.color === "string")
1057
+ entry.color = item.color;
1058
+ const meta = decodeMeta(item.meta, `${path}.meta`, errors);
1059
+ if (meta !== undefined)
1060
+ entry.meta = meta;
1061
+ return entry;
1062
+ }
1063
+ function decodeGridLayer(item, path, errors) {
1064
+ if (!isPlainObject(item)) {
1065
+ errors.push({ path, message: "expected an object" });
1066
+ return null;
1067
+ }
1068
+ if (typeof item.id !== "string")
1069
+ errors.push({ path: `${path}.id`, message: "expected a string" });
1070
+ if (typeof item.kind !== "string")
1071
+ errors.push({ path: `${path}.kind`, message: "expected a string" });
1072
+ const origin = decodeVec3(item.origin, `${path}.origin`, errors);
1073
+ if (typeof item.cellSize !== "number")
1074
+ errors.push({ path: `${path}.cellSize`, message: "expected a number" });
1075
+ if (typeof item.cols !== "number")
1076
+ errors.push({ path: `${path}.cols`, message: "expected a number" });
1077
+ if (typeof item.rows !== "number")
1078
+ errors.push({ path: `${path}.rows`, message: "expected a number" });
1079
+ const cellsValue = item.cells;
1080
+ const validCells = isPlainObject(cellsValue);
1081
+ if (!validCells)
1082
+ errors.push({ path: `${path}.cells`, message: "expected an object" });
1083
+ if (typeof item.id !== "string" ||
1084
+ typeof item.kind !== "string" ||
1085
+ origin === null ||
1086
+ typeof item.cellSize !== "number" ||
1087
+ typeof item.cols !== "number" ||
1088
+ typeof item.rows !== "number" ||
1089
+ !validCells) {
1090
+ return null;
1091
+ }
1092
+ const cells = {};
1093
+ for (const [key, value] of Object.entries(cellsValue)) {
1094
+ if (typeof value !== "string") {
1095
+ errors.push({ path: `${path}.cells.${key}`, message: "expected a string value id" });
1096
+ continue;
1097
+ }
1098
+ cells[key] = value;
1099
+ }
1100
+ const layer = {
1101
+ id: item.id,
1102
+ kind: item.kind,
1103
+ origin,
1104
+ cellSize: item.cellSize,
1105
+ cols: item.cols,
1106
+ rows: item.rows,
1107
+ cells,
1108
+ };
1109
+ if (typeof item.label === "string")
1110
+ layer.label = item.label;
1111
+ if (typeof item.axes === "string" && GRID_AXES.has(item.axes))
1112
+ layer.axes = item.axes;
1113
+ if (typeof item.empty === "string")
1114
+ layer.empty = item.empty;
1115
+ if (typeof item.visible === "boolean")
1116
+ layer.visible = item.visible;
1117
+ if (typeof item.parentId === "string")
1118
+ layer.parentId = item.parentId;
1119
+ if (typeof item.schemaVersion === "number")
1120
+ layer.schemaVersion = item.schemaVersion;
1121
+ if (Array.isArray(item.palette)) {
1122
+ layer.palette = decodeArray(item.palette, `${path}.palette`, decodeGridPaletteEntry, errors);
1123
+ }
1124
+ const meta = decodeMeta(item.meta, `${path}.meta`, errors);
1125
+ if (meta !== undefined)
1126
+ layer.meta = meta;
1127
+ return migrateGridLayer(layer);
1128
+ }
1129
+ /**
1130
+ * The authoritative decoder/migrator for an editor document arriving from outside this process
1131
+ * (disk, network, an agent's `import_document` RPC): validates every field with a path-specific
1132
+ * diagnostic on failure instead of trusting the shape, then migrates forward — routes any embedded
1133
+ * terrain through {@link migrateTerrainSnapshot} and normalizes `version` to the one this build
1134
+ * understands. Every other loader in this module funnels through here.
1135
+ * @internal
1136
+ */
1137
+ export function decodeEditorDocument(raw) {
1138
+ if (!isPlainObject(raw)) {
1139
+ return { ok: false, errors: [{ path: "$", message: "editor document must be an object" }] };
1140
+ }
1141
+ const errors = [];
1142
+ const markers = decodeArray(raw.markers, "$.markers", decodeMarker, errors);
1143
+ const volumes = decodeArray(raw.volumes, "$.volumes", decodeVolume, errors);
1144
+ const paths = decodeArray(raw.paths, "$.paths", decodePath, errors);
1145
+ const annotations = decodeArray(raw.annotations, "$.annotations", decodeNote, errors);
1146
+ const prefabs = decodeArray(raw.prefabs, "$.prefabs", decodePrefab, errors);
1147
+ const collections = decodeArray(raw.collections, "$.collections", decodeCollection, errors);
1148
+ const catalogs = decodeArray(raw.catalogs, "$.catalogs", decodeCatalog, errors);
1149
+ const grids = raw.grids === undefined ? undefined : decodeArray(raw.grids, "$.grids", decodeGridLayer, errors);
1150
+ const directives = raw.directives === undefined ? undefined : decodeArray(raw.directives, "$.directives", decodeDirective, errors);
1151
+ if (raw.terrain !== undefined && !isPlainObject(raw.terrain)) {
1152
+ errors.push({ path: "$.terrain", message: "expected an object" });
1153
+ }
1154
+ const minimap = raw.minimap === undefined ? undefined : decodeMinimapBake(raw.minimap, "$.minimap", errors);
1155
+ const environment = raw.environment === undefined ? undefined : decodeEnvironment(raw.environment, "$.environment", errors);
1156
+ // Placeable object ids form one document-global namespace (selection, parenting, and removal all
1157
+ // treat them that way), so a document that reuses an id — even across two different collections —
1158
+ // is malformed and rejected here with the offending path, rather than silently loading a scene
1159
+ // where one id shadows another. Combine paths (merge/duplicate/overlay) re-id instead; this is the
1160
+ // single-document decode boundary, where a collision is an authoring error, not something to paper over.
1161
+ const seenIds = new Set();
1162
+ for (const [collection, list] of [
1163
+ ["markers", markers],
1164
+ ["volumes", volumes],
1165
+ ["paths", paths],
1166
+ ["annotations", annotations],
1167
+ ]) {
1168
+ list.forEach((item, index) => {
1169
+ if (seenIds.has(item.id)) {
1170
+ errors.push({ path: `$.${collection}[${index}].id`, message: `duplicate id "${item.id}"` });
1171
+ }
1172
+ else {
1173
+ seenIds.add(item.id);
1174
+ }
1175
+ });
1176
+ }
1177
+ if (errors.length > 0)
1178
+ return { ok: false, errors };
1179
+ const terrain = raw.terrain === undefined ? undefined : migrateTerrainSnapshot(raw.terrain);
1180
+ const ui = decodeEditorUiDocument(raw.ui);
1181
+ return {
1182
+ ok: true,
1183
+ document: {
1184
+ version: 1,
1185
+ markers,
1186
+ volumes,
1187
+ paths,
1188
+ annotations,
1189
+ prefabs,
1190
+ collections,
1191
+ catalogs,
1192
+ ...(grids === undefined ? {} : { grids }),
1193
+ ...(terrain === undefined ? {} : { terrain }),
1194
+ ...(ui === undefined ? {} : { ui }),
1195
+ ...(directives === undefined ? {} : { directives }),
1196
+ ...(minimap === undefined ? {} : { minimap }),
1197
+ ...(environment === undefined ? {} : { environment }),
1198
+ },
1199
+ };
1200
+ }
1201
+ /** Parses JSON text back into a normalized editor document, throwing a message that names every
1202
+ * field that failed to decode when the shape is malformed.
1203
+ * @internal
1204
+ */
103
1205
  export function importEditorDocumentJson(raw) {
104
1206
  const parsed = JSON.parse(raw);
105
- if (parsed === null || typeof parsed !== "object") {
106
- throw new Error("editor document must be an object");
1207
+ const decoded = decodeEditorDocument(parsed);
1208
+ if (!decoded.ok) {
1209
+ throw new Error(`invalid editor document: ${decoded.errors.map((e) => `${e.path} ${e.message}`).join("; ")}`);
107
1210
  }
108
- return normalizeEditorLayers(parsed);
1211
+ return decoded.document;
1212
+ }
1213
+ function upsertById(base, overlay) {
1214
+ const overlayIds = new Set(overlay.map((item) => item.id));
1215
+ return [...base.filter((item) => !overlayIds.has(item.id)), ...overlay];
1216
+ }
1217
+ /**
1218
+ * Applies a saved editor document on top of a game's derived layers: overlay objects replace
1219
+ * same-id base objects and new overlay objects are appended, so editor saves win over source
1220
+ * data until they are folded back in.
1221
+ * @internal
1222
+ */
1223
+ export function applyEditorDocumentOverlay(base, overlay) {
1224
+ const terrain = overlay.terrain ?? base.terrain;
1225
+ const minimap = overlay.minimap ?? base.minimap;
1226
+ const environment = overlay.environment ?? base.environment;
1227
+ const ui = overlay.ui === undefined
1228
+ ? cloneEditorUiDocument(base.ui)
1229
+ : {
1230
+ panels: {
1231
+ ...(base.ui?.panels ?? {}),
1232
+ ...Object.fromEntries(Object.entries(overlay.ui.panels).map(([id, panel]) => [id, { ...panel }])),
1233
+ },
1234
+ };
1235
+ const grids = upsertGrids(base.grids, overlay.grids);
1236
+ return {
1237
+ version: 1,
1238
+ markers: upsertById(base.markers, overlay.markers),
1239
+ volumes: upsertById(base.volumes, overlay.volumes),
1240
+ paths: upsertById(base.paths, overlay.paths),
1241
+ annotations: upsertById(base.annotations, overlay.annotations),
1242
+ prefabs: upsertById(base.prefabs, overlay.prefabs),
1243
+ collections: upsertById(base.collections, overlay.collections),
1244
+ catalogs: upsertCatalogs(base.catalogs, overlay.catalogs),
1245
+ ...(grids === undefined ? {} : { grids }),
1246
+ ...(terrain === undefined ? {} : { terrain }),
1247
+ ...(ui === undefined ? {} : { ui }),
1248
+ ...(minimap === undefined ? {} : { minimap: cloneMinimapBake(minimap) }),
1249
+ ...(environment === undefined ? {} : { environment: cloneEnvironment(environment) }),
1250
+ ...(base.directives === undefined && overlay.directives === undefined
1251
+ ? {}
1252
+ : { directives: upsertById(base.directives ?? [], overlay.directives ?? []) }),
1253
+ };
109
1254
  }
110
- /** Computes the world-space min/max bounds spanning every object in a document, or null if empty. */
1255
+ /** Computes the world-space min/max bounds spanning every object in a document, or null if empty.
1256
+ * @internal
1257
+ */
111
1258
  export function editorDocumentBounds(doc) {
112
1259
  let minX = Infinity;
113
1260
  let minY = Infinity;