@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,5 @@
1
- import { FLAT_FIELD, withNormal } from "./terrain.js";
1
+ import { fractalNoise, FLAT_FIELD, withNormal } from "./terrain.js";
2
+ /** Smooth/linear/hard brush weight for a sample `distance` from the brush center. */
2
3
  export function brushWeight(distance, radius, falloff) {
3
4
  if (radius <= 0)
4
5
  return distance <= 0 ? 1 : 0;
@@ -15,6 +16,13 @@ export function brushWeight(distance, radius, falloff) {
15
16
  function clampIndex(value, max) {
16
17
  return value < 0 ? 0 : value > max ? max : value;
17
18
  }
19
+ function clampRange(value, min, max) {
20
+ if (min !== undefined && value < min)
21
+ return min;
22
+ if (max !== undefined && value > max)
23
+ return max;
24
+ return value;
25
+ }
18
26
  export function createEditableTerrain(config) {
19
27
  const base = config.base ?? FLAT_FIELD;
20
28
  const bounds = config.bounds;
@@ -27,12 +35,22 @@ export function createEditableTerrain(config) {
27
35
  const vertsZ = rows + 1;
28
36
  const offsets = new Float32Array(vertsX * vertsZ);
29
37
  const surfaces = new Array(cols * rows).fill(null);
38
+ const cellCount = cols * rows;
39
+ let layers = [];
40
+ // Lazily allocated: null until the first blend, so single-layer terrains carry no weight buffer.
41
+ let weights = null;
30
42
  function gridX(x) {
31
43
  return ((x - bounds.minX) / spanX) * cols;
32
44
  }
33
45
  function gridZ(z) {
34
46
  return ((z - bounds.minZ) / spanZ) * rows;
35
47
  }
48
+ function vertexWorldX(gx) {
49
+ return bounds.minX + (gx / cols) * spanX;
50
+ }
51
+ function vertexWorldZ(gz) {
52
+ return bounds.minZ + (gz / rows) * spanZ;
53
+ }
36
54
  function heightOffsetAt(x, z) {
37
55
  const fx = gridX(x);
38
56
  const fz = gridZ(z);
@@ -52,10 +70,55 @@ export function createEditableTerrain(config) {
52
70
  }
53
71
  const sampleHeight = (x, z) => base.sampleHeight(x, z) + heightOffsetAt(x, z);
54
72
  const sampleNormal = withNormal(sampleHeight);
55
- function editHeight(edit) {
73
+ function totalHeightAtVertex(gx, gz) {
74
+ const wx = vertexWorldX(gx);
75
+ const wz = vertexWorldZ(gz);
76
+ return base.sampleHeight(wx, wz) + offsets[gz * vertsX + gx];
77
+ }
78
+ function neighborAverageHeight(gx, gz) {
79
+ let sum = 0;
80
+ let count = 0;
81
+ for (const [dx, dz] of [
82
+ [-1, 0],
83
+ [1, 0],
84
+ [0, -1],
85
+ [0, 1],
86
+ ]) {
87
+ const nx = gx + dx;
88
+ const nz = gz + dz;
89
+ if (nx < 0 || nx >= vertsX || nz < 0 || nz >= vertsZ)
90
+ continue;
91
+ sum += totalHeightAtVertex(nx, nz);
92
+ count += 1;
93
+ }
94
+ return count === 0 ? totalHeightAtVertex(gx, gz) : sum / count;
95
+ }
96
+ function writeOffset(index, next, limit, wx, wz, record) {
97
+ let value = next;
98
+ if (limit !== undefined) {
99
+ value = clampRange(base.sampleHeight(wx, wz) + value, limit.min, limit.max) - base.sampleHeight(wx, wz);
100
+ }
101
+ const before = offsets[index];
102
+ if (value === before)
103
+ return false;
104
+ offsets[index] = value;
105
+ record?.(index, before, value);
106
+ return true;
107
+ }
108
+ function editHeight(edit, record) {
56
109
  const falloff = edit.falloff ?? "smooth";
110
+ const shape = edit.shape ?? "circle";
57
111
  const strength = edit.strength ?? 1;
58
- const target = edit.mode === "flatten" ? edit.target ?? sampleHeight(edit.center[0], edit.center[1]) : 0;
112
+ const limit = edit.heightLimit;
113
+ const flattenTarget = edit.mode === "flatten" ? edit.target ?? sampleHeight(edit.center[0], edit.center[1]) : 0;
114
+ const noiseConfig = {
115
+ seed: edit.seed ?? 1337,
116
+ frequency: edit.frequency ?? 0.15,
117
+ octaves: 3,
118
+ lacunarity: 2,
119
+ persistence: 0.5,
120
+ ridged: false,
121
+ };
59
122
  const rCells = Math.ceil(edit.radius / cellSize) + 1;
60
123
  const cx = gridX(edit.center[0]);
61
124
  const cz = gridZ(edit.center[1]);
@@ -68,32 +131,105 @@ export function createEditableTerrain(config) {
68
131
  const gx = Math.round(cx) + ix;
69
132
  if (gx < 0 || gx >= vertsX)
70
133
  continue;
71
- const wx = bounds.minX + (gx / cols) * spanX;
72
- const wz = bounds.minZ + (gz / rows) * spanZ;
73
- const dist = Math.hypot(wx - edit.center[0], wz - edit.center[1]);
134
+ const wx = vertexWorldX(gx);
135
+ const wz = vertexWorldZ(gz);
136
+ const dx = wx - edit.center[0];
137
+ const dz = wz - edit.center[1];
138
+ const dist = shape === "square" ? Math.max(Math.abs(dx), Math.abs(dz)) : Math.hypot(dx, dz);
74
139
  const weight = brushWeight(dist, edit.radius, falloff);
75
140
  if (weight <= 0)
76
141
  continue;
77
142
  const index = gz * vertsX + gx;
143
+ const current = offsets[index];
144
+ let next = current;
78
145
  if (edit.mode === "raise")
79
- offsets[index] += strength * weight;
146
+ next = current + strength * weight;
80
147
  else if (edit.mode === "lower")
81
- offsets[index] -= strength * weight;
148
+ next = current - strength * weight;
149
+ else if (edit.mode === "noise")
150
+ next = current + fractalNoise(wx, wz, noiseConfig) * strength * weight;
151
+ else if (edit.mode === "smooth") {
152
+ const target = neighborAverageHeight(gx, gz);
153
+ const currentTotal = base.sampleHeight(wx, wz) + current;
154
+ next = current + (target - currentTotal) * weight * strength;
155
+ }
82
156
  else {
83
- const current = base.sampleHeight(wx, wz) + offsets[index];
84
- offsets[index] += (target - current) * weight * strength;
157
+ const currentTotal = base.sampleHeight(wx, wz) + current;
158
+ next = current + (flattenTarget - currentTotal) * weight * strength;
85
159
  }
86
- changed += 1;
160
+ if (writeOffset(index, next, limit, wx, wz, record))
161
+ changed += 1;
87
162
  }
88
163
  }
89
164
  return changed;
90
165
  }
91
- function paintSurface(edit) {
92
- const surface = edit.surface ?? null;
166
+ function editRamp(edit, record) {
167
+ const to = edit.to;
168
+ if (to === undefined)
169
+ return 0;
170
+ const falloff = edit.falloff ?? "smooth";
171
+ const strength = edit.strength ?? 1;
172
+ const limit = edit.heightLimit;
173
+ const [ax, az] = edit.center;
174
+ const [bx, bz] = to;
175
+ const hFrom = edit.target ?? sampleHeight(ax, az);
176
+ const hTo = edit.targetTo ?? sampleHeight(bx, bz);
177
+ const segX = bx - ax;
178
+ const segZ = bz - az;
179
+ const segLenSq = segX * segX + segZ * segZ || 1;
180
+ const minGx = clampIndex(Math.floor(gridX(Math.min(ax, bx) - edit.radius)), vertsX - 1);
181
+ const maxGx = clampIndex(Math.ceil(gridX(Math.max(ax, bx) + edit.radius)), vertsX - 1);
182
+ const minGz = clampIndex(Math.floor(gridZ(Math.min(az, bz) - edit.radius)), vertsZ - 1);
183
+ const maxGz = clampIndex(Math.ceil(gridZ(Math.max(az, bz) + edit.radius)), vertsZ - 1);
184
+ let changed = 0;
185
+ for (let gz = minGz; gz <= maxGz; gz += 1) {
186
+ for (let gx = minGx; gx <= maxGx; gx += 1) {
187
+ const wx = vertexWorldX(gx);
188
+ const wz = vertexWorldZ(gz);
189
+ let t = ((wx - ax) * segX + (wz - az) * segZ) / segLenSq;
190
+ t = t < 0 ? 0 : t > 1 ? 1 : t;
191
+ const px = ax + segX * t;
192
+ const pz = az + segZ * t;
193
+ const weight = brushWeight(Math.hypot(wx - px, wz - pz), edit.radius, falloff);
194
+ if (weight <= 0)
195
+ continue;
196
+ const index = gz * vertsX + gx;
197
+ const current = offsets[index];
198
+ const target = hFrom + (hTo - hFrom) * t;
199
+ const currentTotal = base.sampleHeight(wx, wz) + current;
200
+ const next = current + (target - currentTotal) * weight * strength;
201
+ if (writeOffset(index, next, limit, wx, wz, record))
202
+ changed += 1;
203
+ }
204
+ }
205
+ return changed;
206
+ }
207
+ function cellWorldX(gx) {
208
+ return bounds.minX + ((gx + 0.5) / cols) * spanX;
209
+ }
210
+ function cellWorldZ(gz) {
211
+ return bounds.minZ + ((gz + 0.5) / rows) * spanZ;
212
+ }
213
+ function writeSurface(index, surface, record) {
214
+ const before = surfaces[index] ?? null;
215
+ if (before === surface)
216
+ return false;
217
+ surfaces[index] = surface;
218
+ record?.(index, before, surface);
219
+ return true;
220
+ }
221
+ /**
222
+ * Visits every cell in a brush footprint on the CELL grid, yielding each cell's flat `index`, its
223
+ * {@link brushWeight} `weight`, world center (`wx`,`wz`), and raw `dist` from the brush center.
224
+ * Shared by the cell-grid brushes ({@link paintSurface}, {@link blendPaint}); each applies its own
225
+ * inclusion test on the yielded values (paint uses `dist <= radius`, blend uses `weight > 0`).
226
+ */
227
+ function forEachBrushCell(edit, visit) {
228
+ const falloff = edit.falloff ?? "smooth";
229
+ const shape = edit.shape ?? "circle";
93
230
  const rCells = Math.ceil(edit.radius / cellSize) + 1;
94
231
  const cx = gridX(edit.center[0]);
95
232
  const cz = gridZ(edit.center[1]);
96
- let painted = 0;
97
233
  for (let iz = -rCells; iz <= rCells; iz += 1) {
98
234
  const gz = Math.floor(cz) + iz;
99
235
  if (gz < 0 || gz >= rows)
@@ -102,36 +238,276 @@ export function createEditableTerrain(config) {
102
238
  const gx = Math.floor(cx) + ix;
103
239
  if (gx < 0 || gx >= cols)
104
240
  continue;
105
- const wx = bounds.minX + ((gx + 0.5) / cols) * spanX;
106
- const wz = bounds.minZ + ((gz + 0.5) / rows) * spanZ;
107
- if (Math.hypot(wx - edit.center[0], wz - edit.center[1]) > edit.radius)
108
- continue;
109
- surfaces[gz * cols + gx] = surface;
241
+ const wx = cellWorldX(gx);
242
+ const wz = cellWorldZ(gz);
243
+ const dx = wx - edit.center[0];
244
+ const dz = wz - edit.center[1];
245
+ const dist = shape === "square" ? Math.max(Math.abs(dx), Math.abs(dz)) : Math.hypot(dx, dz);
246
+ visit(gz * cols + gx, brushWeight(dist, edit.radius, falloff), wx, wz, dist);
247
+ }
248
+ }
249
+ }
250
+ function paintSurface(edit, record) {
251
+ const surface = edit.surface ?? null;
252
+ let painted = 0;
253
+ // Binary inclusion by raw distance (includes dist == radius), independent of falloff shaping.
254
+ forEachBrushCell(edit, (index, _weight, _wx, _wz, dist) => {
255
+ if (dist > edit.radius)
256
+ return;
257
+ if (writeSurface(index, surface, record))
110
258
  painted += 1;
259
+ });
260
+ return painted;
261
+ }
262
+ function localNormal(wx, wz) {
263
+ const eps = Math.max(cellSize, 0.5);
264
+ const hx = sampleHeight(wx + eps, wz) - sampleHeight(wx - eps, wz);
265
+ const hz = sampleHeight(wx, wz + eps) - sampleHeight(wx, wz - eps);
266
+ const nx = -hx;
267
+ const nz = -hz;
268
+ const ny = 2 * eps;
269
+ const length = Math.hypot(nx, ny, nz) || 1;
270
+ return [nx / length, ny / length, nz / length];
271
+ }
272
+ function autoPaint(rule, record) {
273
+ let painted = 0;
274
+ for (let gz = 0; gz < rows; gz += 1) {
275
+ for (let gx = 0; gx < cols; gx += 1) {
276
+ const wx = cellWorldX(gx);
277
+ const wz = cellWorldZ(gz);
278
+ const height = sampleHeight(wx, wz);
279
+ if (rule.minHeight !== undefined && height < rule.minHeight)
280
+ continue;
281
+ if (rule.maxHeight !== undefined && height > rule.maxHeight)
282
+ continue;
283
+ if (rule.minSlope !== undefined || rule.maxSlope !== undefined) {
284
+ const [nx, ny, nz] = localNormal(wx, wz);
285
+ const slope = Math.hypot(nx, nz) / Math.max(ny, 1e-9);
286
+ if (rule.minSlope !== undefined && slope < rule.minSlope)
287
+ continue;
288
+ if (rule.maxSlope !== undefined && slope > rule.maxSlope)
289
+ continue;
290
+ }
291
+ if (writeSurface(gz * cols + gx, rule.surface, record))
292
+ painted += 1;
111
293
  }
112
294
  }
113
295
  return painted;
114
296
  }
115
- function surfaceAt(x, z) {
297
+ function fillSurface(surface, record) {
298
+ let painted = 0;
299
+ for (let index = 0; index < surfaces.length; index += 1) {
300
+ if (writeSurface(index, surface, record))
301
+ painted += 1;
302
+ }
303
+ return painted;
304
+ }
305
+ function cellIndexAt(x, z) {
116
306
  const gx = Math.floor(gridX(x));
117
307
  const gz = Math.floor(gridZ(z));
118
308
  if (gx < 0 || gx >= cols || gz < 0 || gz >= rows)
309
+ return -1;
310
+ return gz * cols + gx;
311
+ }
312
+ function dominantLayerAt(cell) {
313
+ if (weights === null || layers.length === 0)
314
+ return -1;
315
+ const base = cell * layers.length;
316
+ let best = -1;
317
+ let bestWeight = 0;
318
+ for (let l = 0; l < layers.length; l += 1) {
319
+ const w = weights[base + l];
320
+ if (w > bestWeight) {
321
+ bestWeight = w;
322
+ best = l;
323
+ }
324
+ }
325
+ return best;
326
+ }
327
+ function surfaceAt(x, z) {
328
+ const cell = cellIndexAt(x, z);
329
+ if (cell < 0)
119
330
  return null;
120
- return surfaces[gz * cols + gx] ?? null;
331
+ const dominant = dominantLayerAt(cell);
332
+ if (dominant >= 0)
333
+ return layers[dominant].surface;
334
+ return surfaces[cell] ?? null;
335
+ }
336
+ function weightsAt(x, z) {
337
+ const cell = cellIndexAt(x, z);
338
+ if (cell < 0 || weights === null || layers.length === 0)
339
+ return [];
340
+ const base = cell * layers.length;
341
+ return Array.from({ length: layers.length }, (_, l) => weights[base + l]);
342
+ }
343
+ /** Materializes the weight buffer, seeding each cell to weight 1 on its current dominant surface. */
344
+ function ensureWeights() {
345
+ if (weights !== null)
346
+ return weights;
347
+ const buffer = new Float32Array(cellCount * Math.max(1, layers.length));
348
+ if (layers.length > 0) {
349
+ for (let cell = 0; cell < cellCount; cell += 1) {
350
+ const surface = surfaces[cell];
351
+ const layer = surface === null ? -1 : layers.findIndex((entry) => entry.surface === surface);
352
+ if (layer >= 0)
353
+ buffer[cell * layers.length + layer] = 1;
354
+ }
355
+ }
356
+ weights = buffer;
357
+ return buffer;
358
+ }
359
+ function blendCell(cell, target, amount, record) {
360
+ const buffer = ensureWeights();
361
+ const L = layers.length;
362
+ const base = cell * L;
363
+ const prevTarget = buffer[base + target];
364
+ const nextTarget = prevTarget + (1 - prevTarget) * amount;
365
+ if (nextTarget === prevTarget)
366
+ return false;
367
+ let othersSum = 0;
368
+ for (let l = 0; l < L; l += 1)
369
+ if (l !== target)
370
+ othersSum += buffer[base + l];
371
+ const remainder = 1 - nextTarget;
372
+ for (let l = 0; l < L; l += 1) {
373
+ const prev = buffer[base + l];
374
+ const next = l === target ? nextTarget : othersSum > 0 ? (prev / othersSum) * remainder : 0;
375
+ if (next !== prev) {
376
+ buffer[base + l] = next;
377
+ record?.(base + l, prev, next);
378
+ }
379
+ }
380
+ return true;
381
+ }
382
+ function blendPaint(edit, record) {
383
+ const surface = edit.surface;
384
+ if (surface === undefined || layers.length === 0)
385
+ return 0;
386
+ const target = layers.findIndex((entry) => entry.surface === surface);
387
+ if (target < 0)
388
+ return 0;
389
+ const strength = edit.strength ?? 1;
390
+ let painted = 0;
391
+ forEachBrushCell(edit, (index, weight) => {
392
+ if (weight <= 0)
393
+ return;
394
+ if (blendCell(index, target, weight * strength, record))
395
+ painted += 1;
396
+ });
397
+ return painted;
398
+ }
399
+ function runHeightEdit(edit, record) {
400
+ if (edit.mode === "ramp")
401
+ return editRamp(edit, record);
402
+ return editHeight(edit, record);
403
+ }
404
+ function surfaceDeltaFrom(run) {
405
+ const indices = [];
406
+ const before = [];
407
+ const after = [];
408
+ run((index, prev, next) => {
409
+ indices.push(index);
410
+ before.push(prev);
411
+ after.push(next);
412
+ });
413
+ return { indices, before, after };
121
414
  }
122
415
  const field = {
416
+ cols,
417
+ rows,
418
+ cellSize,
123
419
  sampleHeight,
124
420
  sampleNormal,
125
421
  ...(base.bounds === undefined ? {} : { bounds: base.bounds }),
126
422
  ...(base.waterLevel === undefined ? {} : { waterLevel: base.waterLevel }),
127
423
  apply(edit) {
128
- return edit.mode === "paint" ? paintSurface(edit) : editHeight(edit);
424
+ return edit.mode === "paint" ? paintSurface(edit, null) : runHeightEdit(edit, null);
425
+ },
426
+ applyRecording(edit, record) {
427
+ return runHeightEdit(edit, record);
428
+ },
429
+ editDelta(edit) {
430
+ const indices = [];
431
+ const before = [];
432
+ const after = [];
433
+ runHeightEdit(edit, (index, prev, next) => {
434
+ indices.push(index);
435
+ before.push(prev);
436
+ after.push(next);
437
+ });
438
+ return { indices, before, after };
439
+ },
440
+ applyDelta(delta) {
441
+ for (let i = 0; i < delta.indices.length; i += 1)
442
+ offsets[delta.indices[i]] = delta.after[i];
443
+ },
444
+ revertDelta(delta) {
445
+ for (let i = 0; i < delta.indices.length; i += 1)
446
+ offsets[delta.indices[i]] = delta.before[i];
447
+ },
448
+ paintRecording(edit, record) {
449
+ return paintSurface(edit, record);
450
+ },
451
+ paintDelta(edit) {
452
+ return surfaceDeltaFrom((record) => paintSurface(edit, record));
453
+ },
454
+ fillSurfaceDelta(surface) {
455
+ return surfaceDeltaFrom((record) => fillSurface(surface, record));
456
+ },
457
+ autoPaintDelta(rule) {
458
+ return surfaceDeltaFrom((record) => autoPaint(rule, record));
459
+ },
460
+ applySurfaceDelta(delta) {
461
+ for (let i = 0; i < delta.indices.length; i += 1)
462
+ surfaces[delta.indices[i]] = delta.after[i] ?? null;
463
+ },
464
+ revertSurfaceDelta(delta) {
465
+ for (let i = 0; i < delta.indices.length; i += 1)
466
+ surfaces[delta.indices[i]] = delta.before[i] ?? null;
129
467
  },
468
+ get layers() {
469
+ return layers;
470
+ },
471
+ setLayers(next) {
472
+ layers = next.map((layer) => ({ ...layer }));
473
+ // Layer count changed shape: drop the weight buffer, blends re-seed from surfaces on demand.
474
+ weights = null;
475
+ },
476
+ blendRecording(edit, record) {
477
+ return blendPaint(edit, record);
478
+ },
479
+ blendPaintDelta(edit) {
480
+ const indices = [];
481
+ const before = [];
482
+ const after = [];
483
+ blendPaint(edit, (index, prev, next) => {
484
+ indices.push(index);
485
+ before.push(prev);
486
+ after.push(next);
487
+ });
488
+ return { layerCount: layers.length, indices, before, after };
489
+ },
490
+ applyWeightDelta(delta) {
491
+ if (delta.layerCount !== layers.length)
492
+ return;
493
+ const buffer = ensureWeights();
494
+ for (let i = 0; i < delta.indices.length; i += 1)
495
+ buffer[delta.indices[i]] = delta.after[i];
496
+ },
497
+ revertWeightDelta(delta) {
498
+ if (delta.layerCount !== layers.length)
499
+ return;
500
+ const buffer = ensureWeights();
501
+ for (let i = 0; i < delta.indices.length; i += 1)
502
+ buffer[delta.indices[i]] = delta.before[i];
503
+ },
504
+ weightsAt,
130
505
  heightOffsetAt,
131
506
  surfaceAt,
132
507
  reset() {
133
508
  offsets.fill(0);
134
509
  surfaces.fill(null);
510
+ weights = null;
135
511
  },
136
512
  snapshot() {
137
513
  return {
@@ -141,6 +517,8 @@ export function createEditableTerrain(config) {
141
517
  rows,
142
518
  offsets: Array.from(offsets),
143
519
  surfaces: surfaces.slice(),
520
+ ...(layers.length === 0 ? {} : { layers: layers.map((layer) => ({ ...layer })) }),
521
+ ...(weights === null ? {} : { weights: Array.from(weights) }),
144
522
  };
145
523
  },
146
524
  restore(snapshot) {
@@ -150,10 +528,237 @@ export function createEditableTerrain(config) {
150
528
  const surfaceCount = Math.min(surfaces.length, snapshot.surfaces.length);
151
529
  for (let index = 0; index < surfaceCount; index += 1)
152
530
  surfaces[index] = snapshot.surfaces[index] ?? null;
531
+ layers = (snapshot.layers ?? []).map((layer) => ({ ...layer }));
532
+ if (snapshot.weights === undefined || layers.length === 0) {
533
+ weights = null;
534
+ }
535
+ else {
536
+ const buffer = new Float32Array(cellCount * layers.length);
537
+ const copy = Math.min(buffer.length, snapshot.weights.length);
538
+ for (let index = 0; index < copy; index += 1)
539
+ buffer[index] = snapshot.weights[index];
540
+ weights = buffer;
541
+ }
153
542
  },
154
543
  };
155
544
  return field;
156
545
  }
546
+ /**
547
+ * The shared first/latest Map accumulator behind every `begin*Stroke`. Records each key's first
548
+ * `before` and latest `after` across many stamps, then assembles the net changed entries (skipping
549
+ * keys whose latest equals their first). Each stroke supplies its own recording call via `recordStamp`
550
+ * and wraps {@link StrokeChanges} into its concrete delta shape.
551
+ */
552
+ function createStroke(recordStamp) {
553
+ const first = new Map();
554
+ const latest = new Map();
555
+ return {
556
+ stamp(edit) {
557
+ return recordStamp(edit, (index, before, after) => {
558
+ if (!first.has(index))
559
+ first.set(index, before);
560
+ latest.set(index, after);
561
+ });
562
+ },
563
+ changes() {
564
+ const indices = [];
565
+ const before = [];
566
+ const after = [];
567
+ for (const [index, next] of latest) {
568
+ // Every latest key was set in `first` on the same callback, so this is always present.
569
+ const prev = first.get(index);
570
+ if (next === prev)
571
+ continue;
572
+ indices.push(index);
573
+ before.push(prev);
574
+ after.push(next);
575
+ }
576
+ return { indices, before, after };
577
+ },
578
+ isEmpty() {
579
+ return latest.size === 0;
580
+ },
581
+ };
582
+ }
583
+ /** Opens a stroke recorder over `terrain`; stamp edits into it, then read one net delta. */
584
+ export function beginTerraformStroke(terrain) {
585
+ const stroke = createStroke((edit, record) => terrain.applyRecording(edit, record));
586
+ return {
587
+ stamp: stroke.stamp,
588
+ delta: () => stroke.changes(),
589
+ isEmpty: stroke.isEmpty,
590
+ };
591
+ }
592
+ /** Opens a paint-stroke recorder over `terrain`; stamp paint edits into it, then read one net delta. */
593
+ export function beginSurfaceStroke(terrain) {
594
+ const stroke = createStroke((edit, record) => terrain.paintRecording(edit, record));
595
+ return {
596
+ stamp: stroke.stamp,
597
+ delta: () => stroke.changes(),
598
+ isEmpty: stroke.isEmpty,
599
+ };
600
+ }
601
+ /** Returns a new snapshot with a surface delta's `after` ids applied (copy-on-write). */
602
+ export function applySurfaceDeltaToSnapshot(snapshot, delta) {
603
+ const surfaces = snapshot.surfaces.slice();
604
+ for (let i = 0; i < delta.indices.length; i += 1)
605
+ surfaces[delta.indices[i]] = delta.after[i] ?? null;
606
+ return { ...snapshot, surfaces };
607
+ }
608
+ /** Returns a new snapshot with a surface delta's `before` ids restored (copy-on-write undo). */
609
+ export function revertSurfaceDeltaFromSnapshot(snapshot, delta) {
610
+ const surfaces = snapshot.surfaces.slice();
611
+ for (let i = 0; i < delta.indices.length; i += 1)
612
+ surfaces[delta.indices[i]] = delta.before[i] ?? null;
613
+ return { ...snapshot, surfaces };
614
+ }
615
+ /** The distinct non-null surface ids painted in a snapshot, in first-seen order. */
616
+ function distinctSurfaces(snapshot) {
617
+ const seen = new Set();
618
+ const out = [];
619
+ for (const surface of snapshot.surfaces) {
620
+ if (surface !== null && !seen.has(surface)) {
621
+ seen.add(surface);
622
+ out.push(surface);
623
+ }
624
+ }
625
+ return out;
626
+ }
627
+ /**
628
+ * Upgrades a pre-2.0 snapshot in place-safe (copy-on-write) form: derives a {@link TerrainMaterialLayer}
629
+ * stack from the distinct painted surfaces (first-seen order, default params) when none exists.
630
+ * Leaves the lazy `weights` buffer absent — a single-layer terrain stays compact until blended.
631
+ * Idempotent: a snapshot that already carries `layers` is returned unchanged.
632
+ */
633
+ export function migrateTerrainSnapshot(snapshot) {
634
+ if (snapshot.layers !== undefined)
635
+ return snapshot;
636
+ const layers = distinctSurfaces(snapshot).map((surface) => ({
637
+ id: surface,
638
+ surface,
639
+ roughness: 0.9,
640
+ tiling: 8,
641
+ }));
642
+ return { ...snapshot, layers };
643
+ }
644
+ /** Materializes a snapshot's blend weights, seeding weight 1 on each cell's dominant surface. */
645
+ function materializeSnapshotWeights(snapshot) {
646
+ const layers = snapshot.layers ?? [];
647
+ const cells = snapshot.cols * snapshot.rows;
648
+ const buffer = new Array(cells * Math.max(1, layers.length)).fill(0);
649
+ if (snapshot.weights !== undefined) {
650
+ const copy = Math.min(buffer.length, snapshot.weights.length);
651
+ for (let i = 0; i < copy; i += 1)
652
+ buffer[i] = snapshot.weights[i];
653
+ return buffer;
654
+ }
655
+ if (layers.length > 0) {
656
+ for (let cell = 0; cell < cells; cell += 1) {
657
+ const surface = snapshot.surfaces[cell] ?? null;
658
+ const layer = surface === null ? -1 : layers.findIndex((entry) => entry.surface === surface);
659
+ if (layer >= 0)
660
+ buffer[cell * layers.length + layer] = 1;
661
+ }
662
+ }
663
+ return buffer;
664
+ }
665
+ /**
666
+ * Returns a new snapshot with a weight delta's `after` values applied (materializing weights first).
667
+ * @internal — undo/redo plumbing behind the `blendTerrain` command.
668
+ */
669
+ export function applyWeightDeltaToSnapshot(snapshot, delta) {
670
+ if (delta.layerCount !== (snapshot.layers?.length ?? 0))
671
+ return snapshot;
672
+ const weights = materializeSnapshotWeights(snapshot);
673
+ for (let i = 0; i < delta.indices.length; i += 1)
674
+ weights[delta.indices[i]] = delta.after[i];
675
+ return { ...snapshot, weights };
676
+ }
677
+ /**
678
+ * Returns a new snapshot with a weight delta's `before` values restored (copy-on-write undo).
679
+ * @internal — undo/redo plumbing behind the `blendTerrain` command.
680
+ */
681
+ export function revertWeightDeltaFromSnapshot(snapshot, delta) {
682
+ if (delta.layerCount !== (snapshot.layers?.length ?? 0))
683
+ return snapshot;
684
+ const weights = materializeSnapshotWeights(snapshot);
685
+ for (let i = 0; i < delta.indices.length; i += 1)
686
+ weights[delta.indices[i]] = delta.before[i];
687
+ return { ...snapshot, weights };
688
+ }
689
+ /**
690
+ * Opens a blend-stroke recorder over `terrain`; stamp blend edits into it, then read one net delta.
691
+ * @internal — the stroke recorder behind the blend brush and the `blendTerrain` command.
692
+ */
693
+ export function beginBlendStroke(terrain) {
694
+ const stroke = createStroke((edit, record) => terrain.blendRecording(edit, record));
695
+ return {
696
+ stamp: stroke.stamp,
697
+ // layerCount is read at delta() time, guarding replay after the layer stack changed shape.
698
+ delta: () => ({ layerCount: terrain.layers.length, ...stroke.changes() }),
699
+ isEmpty: stroke.isEmpty,
700
+ };
701
+ }
702
+ /**
703
+ * The world-space {@link Aabb} spanning the vertices a stroke's `indices` touched (indices into the
704
+ * `(cols+1)×(rows+1)` offset grid), expanded by `margin` meters. Drives partial preview-mesh
705
+ * rebuilds — only the dirty submesh re-samples instead of the whole heightfield. Null if empty.
706
+ * @internal — the editor's preview-mesh rebuild uses this; not a game-facing primitive.
707
+ */
708
+ export function dirtyBoundsFromIndices(snapshot, indices, margin = 0) {
709
+ if (indices.length === 0)
710
+ return null;
711
+ const vertsX = snapshot.cols + 1;
712
+ const spanX = Math.max(snapshot.bounds.maxX - snapshot.bounds.minX, snapshot.cellSize);
713
+ const spanZ = Math.max(snapshot.bounds.maxZ - snapshot.bounds.minZ, snapshot.cellSize);
714
+ let minX = Infinity;
715
+ let minZ = Infinity;
716
+ let maxX = -Infinity;
717
+ let maxZ = -Infinity;
718
+ for (const index of indices) {
719
+ const gx = index % vertsX;
720
+ const gz = Math.floor(index / vertsX);
721
+ const wx = snapshot.bounds.minX + (gx / snapshot.cols) * spanX;
722
+ const wz = snapshot.bounds.minZ + (gz / snapshot.rows) * spanZ;
723
+ if (wx < minX)
724
+ minX = wx;
725
+ if (wx > maxX)
726
+ maxX = wx;
727
+ if (wz < minZ)
728
+ minZ = wz;
729
+ if (wz > maxZ)
730
+ maxZ = wz;
731
+ }
732
+ return { minX: minX - margin, minZ: minZ - margin, maxX: maxX + margin, maxZ: maxZ + margin };
733
+ }
734
+ /** A fresh, unedited terrain snapshot sized to `bounds`/`cellSize` — the seed for a new sculpt document. */
735
+ export function createTerrainSnapshot(config) {
736
+ return createEditableTerrain(config).snapshot();
737
+ }
738
+ /** Rebuilds a live {@link EditableTerrain} from a snapshot, layered over `base` ground. */
739
+ export function editableTerrainFromSnapshot(snapshot, base) {
740
+ const terrain = createEditableTerrain({
741
+ bounds: snapshot.bounds,
742
+ cellSize: snapshot.cellSize,
743
+ ...(base === undefined ? {} : { base }),
744
+ });
745
+ terrain.restore(snapshot);
746
+ return terrain;
747
+ }
748
+ /** Returns a new snapshot with a delta's `after` offsets applied (copy-on-write — inputs untouched). */
749
+ export function applyDeltaToSnapshot(snapshot, delta) {
750
+ const offsets = snapshot.offsets.slice();
751
+ for (let i = 0; i < delta.indices.length; i += 1)
752
+ offsets[delta.indices[i]] = delta.after[i];
753
+ return { ...snapshot, offsets };
754
+ }
755
+ /** Returns a new snapshot with a delta's `before` offsets restored (copy-on-write undo). */
756
+ export function revertDeltaFromSnapshot(snapshot, delta) {
757
+ const offsets = snapshot.offsets.slice();
758
+ for (let i = 0; i < delta.indices.length; i += 1)
759
+ offsets[delta.indices[i]] = delta.before[i];
760
+ return { ...snapshot, offsets };
761
+ }
157
762
  export function createTerraformBrush(terrain, config = {}) {
158
763
  let radius = config.radius ?? 3;
159
764
  let strength = config.strength ?? 0.5;