@pascal-app/core 1.0.0-beta.5 → 1.0.1

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 (417) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -0
  3. package/dist/capture/index.d.ts +3 -0
  4. package/dist/capture/index.d.ts.map +1 -0
  5. package/dist/capture/index.js +2 -0
  6. package/dist/capture/schema.d.ts +417 -0
  7. package/dist/capture/schema.d.ts.map +1 -0
  8. package/dist/capture/schema.js +350 -0
  9. package/dist/capture/source.d.ts +60 -0
  10. package/dist/capture/source.d.ts.map +1 -0
  11. package/dist/capture/source.js +254 -0
  12. package/dist/events/bus.d.ts +58 -7
  13. package/dist/events/bus.d.ts.map +1 -1
  14. package/dist/events/hidden-wall-pointer-hold.d.ts +28 -0
  15. package/dist/events/hidden-wall-pointer-hold.d.ts.map +1 -0
  16. package/dist/events/hidden-wall-pointer-hold.js +37 -0
  17. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +4 -4
  18. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
  19. package/dist/hooks/spatial-grid/floor-placed-elevation.js +7 -12
  20. package/dist/hooks/spatial-grid/floor-placed-footprints.d.ts +4 -0
  21. package/dist/hooks/spatial-grid/floor-placed-footprints.d.ts.map +1 -0
  22. package/dist/hooks/spatial-grid/floor-placed-footprints.js +7 -0
  23. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +5 -8
  24. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  25. package/dist/hooks/spatial-grid/spatial-grid-manager.js +23 -106
  26. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +2931 -2
  27. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  28. package/dist/hooks/spatial-grid/spatial-grid-sync.js +207 -30
  29. package/dist/hooks/spatial-grid/support-host-patch.d.ts +45 -1
  30. package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -1
  31. package/dist/hooks/spatial-grid/support-host-patch.js +174 -9
  32. package/dist/hooks/spatial-grid/use-spatial-query.d.ts +1 -1
  33. package/dist/hooks/spatial-grid/use-spatial-query.js +1 -1
  34. package/dist/index.d.ts +74 -64
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +74 -61
  37. package/dist/lib/asset-storage.d.ts.map +1 -1
  38. package/dist/lib/asset-storage.js +4 -1
  39. package/dist/lib/door-operation.d.ts +1 -1
  40. package/dist/lib/item-polygon-overlap.d.ts +6 -0
  41. package/dist/lib/item-polygon-overlap.d.ts.map +1 -0
  42. package/dist/lib/item-polygon-overlap.js +67 -0
  43. package/dist/lib/level-name.d.ts +1 -1
  44. package/dist/lib/measurement-geometry.d.ts +4 -4
  45. package/dist/lib/plan-footprint.d.ts +41 -0
  46. package/dist/lib/plan-footprint.d.ts.map +1 -0
  47. package/dist/lib/plan-footprint.js +58 -0
  48. package/dist/lib/polygon-union.d.ts +4 -0
  49. package/dist/lib/polygon-union.d.ts.map +1 -0
  50. package/dist/lib/polygon-union.js +306 -0
  51. package/dist/lib/roof-overlap.d.ts +32 -0
  52. package/dist/lib/roof-overlap.d.ts.map +1 -0
  53. package/dist/lib/roof-overlap.js +72 -0
  54. package/dist/lib/room-topology-index.d.ts +1 -1
  55. package/dist/lib/room-topology-index.js +1 -1
  56. package/dist/lib/selection-proxy.d.ts +1 -1
  57. package/dist/lib/slab-polygon.d.ts +36 -3
  58. package/dist/lib/slab-polygon.d.ts.map +1 -1
  59. package/dist/lib/slab-polygon.js +135 -29
  60. package/dist/lib/space-detection.d.ts +12 -1
  61. package/dist/lib/space-detection.d.ts.map +1 -1
  62. package/dist/lib/space-detection.js +58 -25
  63. package/dist/lib/terrain-brush.d.ts +1 -1
  64. package/dist/lib/terrain-brush.js +1 -1
  65. package/dist/lib/terrain-codec.d.ts +2 -2
  66. package/dist/lib/terrain-codec.js +1 -1
  67. package/dist/lib/terrain-raycast.d.ts +1 -1
  68. package/dist/lib/terrain-raycast.js +1 -1
  69. package/dist/lib/terrain-source.d.ts +3 -3
  70. package/dist/lib/terrain-source.js +3 -3
  71. package/dist/lib/terrain-support.d.ts +1 -1
  72. package/dist/lib/terrain-support.js +3 -3
  73. package/dist/lib/unit-containment.d.ts +19 -0
  74. package/dist/lib/unit-containment.d.ts.map +1 -0
  75. package/dist/lib/unit-containment.js +158 -0
  76. package/dist/lib/unit-report.d.ts +19 -0
  77. package/dist/lib/unit-report.d.ts.map +1 -0
  78. package/dist/lib/unit-report.js +36 -0
  79. package/dist/lib/wall-distance.d.ts +2 -2
  80. package/dist/lib/wall-distance.js +1 -1
  81. package/dist/lib/zone-quantities.d.ts +1 -1
  82. package/dist/lib/zone-quantities.js +7 -7
  83. package/dist/material-library.d.ts +1 -1
  84. package/dist/material-library.js +1 -1
  85. package/dist/procedural-items/fixtures.d.ts +261 -0
  86. package/dist/procedural-items/fixtures.d.ts.map +1 -0
  87. package/dist/procedural-items/fixtures.js +349 -0
  88. package/dist/procedural-items/index.d.ts +10 -0
  89. package/dist/procedural-items/index.d.ts.map +1 -0
  90. package/dist/procedural-items/index.js +9 -0
  91. package/dist/procedural-items/integration.d.ts +92 -0
  92. package/dist/procedural-items/integration.d.ts.map +1 -0
  93. package/dist/procedural-items/integration.js +19 -0
  94. package/dist/procedural-items/library-colors.d.ts +56 -0
  95. package/dist/procedural-items/library-colors.d.ts.map +1 -0
  96. package/dist/procedural-items/library-colors.js +84 -0
  97. package/dist/procedural-items/materials.d.ts +5 -0
  98. package/dist/procedural-items/materials.d.ts.map +1 -0
  99. package/dist/procedural-items/materials.js +47 -0
  100. package/dist/procedural-items/node.d.ts +280 -0
  101. package/dist/procedural-items/node.d.ts.map +1 -0
  102. package/dist/procedural-items/node.js +71 -0
  103. package/dist/procedural-items/placement.d.ts +7 -0
  104. package/dist/procedural-items/placement.d.ts.map +1 -0
  105. package/dist/procedural-items/placement.js +48 -0
  106. package/dist/procedural-items/query.d.ts +50 -0
  107. package/dist/procedural-items/query.d.ts.map +1 -0
  108. package/dist/procedural-items/query.js +298 -0
  109. package/dist/procedural-items/recipe.d.ts +144 -0
  110. package/dist/procedural-items/recipe.d.ts.map +1 -0
  111. package/dist/procedural-items/recipe.js +372 -0
  112. package/dist/procedural-items/spatial.d.ts +19 -0
  113. package/dist/procedural-items/spatial.d.ts.map +1 -0
  114. package/dist/procedural-items/spatial.js +39 -0
  115. package/dist/registry/__bench__/relations-resolver.bench.js +2 -2
  116. package/dist/registry/handles.d.ts +36 -7
  117. package/dist/registry/handles.d.ts.map +1 -1
  118. package/dist/registry/index.d.ts +6 -7
  119. package/dist/registry/index.d.ts.map +1 -1
  120. package/dist/registry/index.js +4 -5
  121. package/dist/registry/registry.d.ts +13 -6
  122. package/dist/registry/registry.d.ts.map +1 -1
  123. package/dist/registry/registry.js +37 -4
  124. package/dist/registry/relations-resolver.d.ts +2 -2
  125. package/dist/registry/relations-resolver.js +1 -1
  126. package/dist/registry/scene-api.d.ts +18 -2
  127. package/dist/registry/scene-api.d.ts.map +1 -1
  128. package/dist/registry/scene-api.js +50 -2
  129. package/dist/registry/subtree.d.ts +1 -1
  130. package/dist/registry/subtree.js +2 -2
  131. package/dist/registry/types.d.ts +246 -21
  132. package/dist/registry/types.d.ts.map +1 -1
  133. package/dist/registry/use-registry-version.js +1 -1
  134. package/dist/registry/validate-relations.d.ts +4 -0
  135. package/dist/registry/validate-relations.d.ts.map +1 -0
  136. package/dist/registry/validate-relations.js +22 -0
  137. package/dist/schema/__bench__/node-parsers.bench.d.ts +24 -0
  138. package/dist/schema/__bench__/node-parsers.bench.d.ts.map +1 -0
  139. package/dist/schema/__bench__/node-parsers.bench.js +314 -0
  140. package/dist/schema/__fixtures__/node-fixtures.d.ts +28 -0
  141. package/dist/schema/__fixtures__/node-fixtures.d.ts.map +1 -0
  142. package/dist/schema/__fixtures__/node-fixtures.js +127 -0
  143. package/dist/schema/base.d.ts +1 -1
  144. package/dist/schema/base.d.ts.map +1 -1
  145. package/dist/schema/base.js +9 -2
  146. package/dist/schema/collections.d.ts +1 -1
  147. package/dist/schema/collections.js +1 -1
  148. package/dist/schema/compiled-node-parsers.d.ts +40 -0
  149. package/dist/schema/compiled-node-parsers.d.ts.map +1 -0
  150. package/dist/schema/compiled-node-parsers.js +95 -0
  151. package/dist/schema/index.d.ts +73 -66
  152. package/dist/schema/index.d.ts.map +1 -1
  153. package/dist/schema/index.js +65 -57
  154. package/dist/schema/material.d.ts +1 -0
  155. package/dist/schema/material.d.ts.map +1 -1
  156. package/dist/schema/material.js +2 -1
  157. package/dist/schema/nodes/automatic-downspout.d.ts +17 -0
  158. package/dist/schema/nodes/automatic-downspout.d.ts.map +1 -0
  159. package/dist/schema/nodes/automatic-downspout.js +237 -0
  160. package/dist/schema/nodes/block.d.ts +107 -0
  161. package/dist/schema/nodes/block.d.ts.map +1 -0
  162. package/dist/schema/nodes/block.js +206 -0
  163. package/dist/schema/nodes/box-vent.d.ts +8 -2
  164. package/dist/schema/nodes/box-vent.d.ts.map +1 -1
  165. package/dist/schema/nodes/box-vent.js +4 -2
  166. package/dist/schema/nodes/building.d.ts +2 -2
  167. package/dist/schema/nodes/building.d.ts.map +1 -1
  168. package/dist/schema/nodes/building.js +8 -5
  169. package/dist/schema/nodes/cabinet.d.ts +26 -4
  170. package/dist/schema/nodes/cabinet.d.ts.map +1 -1
  171. package/dist/schema/nodes/cabinet.js +29 -8
  172. package/dist/schema/nodes/ceiling.d.ts +2 -2
  173. package/dist/schema/nodes/ceiling.js +5 -5
  174. package/dist/schema/nodes/chimney.d.ts +2 -2
  175. package/dist/schema/nodes/chimney.js +2 -2
  176. package/dist/schema/nodes/column.d.ts +7 -6
  177. package/dist/schema/nodes/column.d.ts.map +1 -1
  178. package/dist/schema/nodes/column.js +3 -2
  179. package/dist/schema/nodes/construction-dimension.d.ts +1 -1
  180. package/dist/schema/nodes/construction-dimension.js +2 -2
  181. package/dist/schema/nodes/cupola.d.ts +9 -1
  182. package/dist/schema/nodes/cupola.d.ts.map +1 -1
  183. package/dist/schema/nodes/cupola.js +4 -2
  184. package/dist/schema/nodes/door.d.ts +1 -1
  185. package/dist/schema/nodes/door.js +2 -2
  186. package/dist/schema/nodes/dormer.d.ts +45 -2
  187. package/dist/schema/nodes/dormer.d.ts.map +1 -1
  188. package/dist/schema/nodes/dormer.js +133 -9
  189. package/dist/schema/nodes/downspout.d.ts +11 -1
  190. package/dist/schema/nodes/downspout.d.ts.map +1 -1
  191. package/dist/schema/nodes/downspout.js +26 -2
  192. package/dist/schema/nodes/duct-fitting.d.ts +18 -1
  193. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -1
  194. package/dist/schema/nodes/duct-fitting.js +26 -5
  195. package/dist/schema/nodes/duct-segment.d.ts +23 -1
  196. package/dist/schema/nodes/duct-segment.d.ts.map +1 -1
  197. package/dist/schema/nodes/duct-segment.js +21 -1
  198. package/dist/schema/nodes/duct-terminal.d.ts +1 -1
  199. package/dist/schema/nodes/duct-terminal.js +1 -1
  200. package/dist/schema/nodes/elevator.d.ts +1 -1
  201. package/dist/schema/nodes/elevator.js +2 -2
  202. package/dist/schema/nodes/eyebrow-vent.d.ts +8 -2
  203. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -1
  204. package/dist/schema/nodes/eyebrow-vent.js +4 -2
  205. package/dist/schema/nodes/fence.d.ts +1 -1
  206. package/dist/schema/nodes/fence.js +2 -2
  207. package/dist/schema/nodes/guide.d.ts +1 -1
  208. package/dist/schema/nodes/guide.js +2 -2
  209. package/dist/schema/nodes/gutter.d.ts +36 -2
  210. package/dist/schema/nodes/gutter.d.ts.map +1 -1
  211. package/dist/schema/nodes/gutter.js +310 -3
  212. package/dist/schema/nodes/hanger-overrides.d.ts +7 -0
  213. package/dist/schema/nodes/hanger-overrides.d.ts.map +1 -0
  214. package/dist/schema/nodes/hanger-overrides.js +6 -0
  215. package/dist/schema/nodes/hvac-equipment.d.ts +1 -1
  216. package/dist/schema/nodes/hvac-equipment.js +1 -1
  217. package/dist/schema/nodes/imported-mesh.d.ts +40 -0
  218. package/dist/schema/nodes/imported-mesh.d.ts.map +1 -0
  219. package/dist/schema/nodes/imported-mesh.js +27 -0
  220. package/dist/schema/nodes/item.d.ts +3 -2
  221. package/dist/schema/nodes/item.d.ts.map +1 -1
  222. package/dist/schema/nodes/item.js +8 -3
  223. package/dist/schema/nodes/lean-to-extension.d.ts +212 -0
  224. package/dist/schema/nodes/lean-to-extension.d.ts.map +1 -0
  225. package/dist/schema/nodes/lean-to-extension.js +114 -0
  226. package/dist/schema/nodes/level.d.ts +2 -2
  227. package/dist/schema/nodes/level.d.ts.map +1 -1
  228. package/dist/schema/nodes/level.js +1 -1
  229. package/dist/schema/nodes/lineset.d.ts +1 -1
  230. package/dist/schema/nodes/lineset.js +1 -1
  231. package/dist/schema/nodes/liquid-line.d.ts +1 -1
  232. package/dist/schema/nodes/liquid-line.js +1 -1
  233. package/dist/schema/nodes/measurement.d.ts +1 -1
  234. package/dist/schema/nodes/measurement.js +2 -2
  235. package/dist/schema/nodes/pipe-fitting.d.ts +10 -2
  236. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -1
  237. package/dist/schema/nodes/pipe-fitting.js +7 -3
  238. package/dist/schema/nodes/pipe-segment.d.ts +24 -2
  239. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -1
  240. package/dist/schema/nodes/pipe-segment.js +21 -1
  241. package/dist/schema/nodes/pipe-trap.d.ts +2 -2
  242. package/dist/schema/nodes/pipe-trap.js +1 -1
  243. package/dist/schema/nodes/ridge-vent.d.ts +2 -2
  244. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
  245. package/dist/schema/nodes/ridge-vent.js +7 -4
  246. package/dist/schema/nodes/roof-segment-shape.d.ts +5 -1
  247. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -1
  248. package/dist/schema/nodes/roof-segment-shape.js +65 -2
  249. package/dist/schema/nodes/roof-segment-walls.d.ts +4 -2
  250. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -1
  251. package/dist/schema/nodes/roof-segment-walls.js +2 -2
  252. package/dist/schema/nodes/roof-segment.d.ts +42 -2
  253. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  254. package/dist/schema/nodes/roof-segment.js +81 -4
  255. package/dist/schema/nodes/roof.d.ts +23 -2
  256. package/dist/schema/nodes/roof.d.ts.map +1 -1
  257. package/dist/schema/nodes/roof.js +17 -3
  258. package/dist/schema/nodes/scan.d.ts +19 -2
  259. package/dist/schema/nodes/scan.d.ts.map +1 -1
  260. package/dist/schema/nodes/scan.js +13 -3
  261. package/dist/schema/nodes/shelf.d.ts +3 -3
  262. package/dist/schema/nodes/shelf.d.ts.map +1 -1
  263. package/dist/schema/nodes/shelf.js +3 -4
  264. package/dist/schema/nodes/site.d.ts +1 -1
  265. package/dist/schema/nodes/site.js +2 -2
  266. package/dist/schema/nodes/skylight.d.ts +5 -5
  267. package/dist/schema/nodes/skylight.js +2 -2
  268. package/dist/schema/nodes/slab.d.ts +1 -1
  269. package/dist/schema/nodes/slab.js +3 -3
  270. package/dist/schema/nodes/solar-panel.d.ts +1 -1
  271. package/dist/schema/nodes/solar-panel.js +3 -3
  272. package/dist/schema/nodes/spawn.d.ts +1 -1
  273. package/dist/schema/nodes/spawn.js +1 -1
  274. package/dist/schema/nodes/stair-segment.d.ts +1 -1
  275. package/dist/schema/nodes/stair-segment.js +2 -2
  276. package/dist/schema/nodes/stair.d.ts +2 -2
  277. package/dist/schema/nodes/stair.js +3 -3
  278. package/dist/schema/nodes/structural-grid.d.ts +1 -1
  279. package/dist/schema/nodes/structural-grid.js +1 -1
  280. package/dist/schema/nodes/turbine-vent.d.ts +8 -2
  281. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -1
  282. package/dist/schema/nodes/turbine-vent.js +4 -2
  283. package/dist/schema/nodes/unit.d.ts +33 -0
  284. package/dist/schema/nodes/unit.d.ts.map +1 -0
  285. package/dist/schema/nodes/unit.js +13 -0
  286. package/dist/schema/nodes/wall.d.ts +5 -5
  287. package/dist/schema/nodes/wall.d.ts.map +1 -1
  288. package/dist/schema/nodes/wall.js +13 -6
  289. package/dist/schema/nodes/window.d.ts +10 -3
  290. package/dist/schema/nodes/window.d.ts.map +1 -1
  291. package/dist/schema/nodes/window.js +6 -2
  292. package/dist/schema/nodes/zone.d.ts +2 -2
  293. package/dist/schema/nodes/zone.js +2 -2
  294. package/dist/schema/scene-material.js +2 -2
  295. package/dist/schema/types.d.ts +815 -118
  296. package/dist/schema/types.d.ts.map +1 -1
  297. package/dist/schema/types.js +79 -46
  298. package/dist/services/alignment-anchors.d.ts +6 -11
  299. package/dist/services/alignment-anchors.d.ts.map +1 -1
  300. package/dist/services/alignment-anchors.js +7 -34
  301. package/dist/services/drag-session.d.ts +4 -4
  302. package/dist/services/drag-session.js +1 -1
  303. package/dist/services/hosting.d.ts +4 -10
  304. package/dist/services/hosting.d.ts.map +1 -1
  305. package/dist/services/hosting.js +6 -16
  306. package/dist/services/index.d.ts +16 -13
  307. package/dist/services/index.d.ts.map +1 -1
  308. package/dist/services/index.js +16 -13
  309. package/dist/services/level-height.d.ts +2 -2
  310. package/dist/services/level-height.js +3 -3
  311. package/dist/services/movement.d.ts +3 -3
  312. package/dist/services/movement.js +2 -2
  313. package/dist/services/port-connectivity.d.ts +1 -1
  314. package/dist/services/port-connectivity.js +1 -1
  315. package/dist/services/riser-diagram.d.ts +1 -1
  316. package/dist/services/shelf-board.d.ts +5 -0
  317. package/dist/services/shelf-board.d.ts.map +1 -0
  318. package/dist/services/shelf-board.js +15 -0
  319. package/dist/services/storey.d.ts +2 -2
  320. package/dist/services/storey.js +2 -2
  321. package/dist/services/surface-hosting-procedural-parity.fixtures.d.ts +13 -0
  322. package/dist/services/surface-hosting-procedural-parity.fixtures.d.ts.map +1 -0
  323. package/dist/services/surface-hosting-procedural-parity.fixtures.js +4691 -0
  324. package/dist/services/surface-hosting-shelf-parity.fixtures.d.ts +1060 -0
  325. package/dist/services/surface-hosting-shelf-parity.fixtures.d.ts.map +1 -0
  326. package/dist/services/surface-hosting-shelf-parity.fixtures.js +3127 -0
  327. package/dist/services/surface-hosting.d.ts +88 -0
  328. package/dist/services/surface-hosting.d.ts.map +1 -0
  329. package/dist/services/surface-hosting.js +268 -0
  330. package/dist/services/surface-region.d.ts +8 -0
  331. package/dist/services/surface-region.d.ts.map +1 -0
  332. package/dist/services/surface-region.js +44 -0
  333. package/dist/services/system-graph.d.ts +12 -1
  334. package/dist/services/system-graph.d.ts.map +1 -1
  335. package/dist/services/system-graph.js +36 -6
  336. package/dist/services/validate-dwv.d.ts +1 -1
  337. package/dist/services/validate-dwv.js +1 -1
  338. package/dist/store/actions/node-actions.d.ts +9 -2
  339. package/dist/store/actions/node-actions.d.ts.map +1 -1
  340. package/dist/store/actions/node-actions.js +475 -24
  341. package/dist/store/history-control.d.ts +3 -3
  342. package/dist/store/history-invalidation.d.ts +3 -0
  343. package/dist/store/history-invalidation.d.ts.map +1 -0
  344. package/dist/store/history-invalidation.js +72 -0
  345. package/dist/store/scene-hydration.d.ts +5 -0
  346. package/dist/store/scene-hydration.d.ts.map +1 -0
  347. package/dist/store/scene-hydration.js +57 -0
  348. package/dist/store/use-interactive.d.ts +2 -2
  349. package/dist/store/use-live-terrain.d.ts +1 -1
  350. package/dist/store/use-scene.d.ts +7 -4
  351. package/dist/store/use-scene.d.ts.map +1 -1
  352. package/dist/store/use-scene.js +361 -87
  353. package/dist/systems/elevator/elevator-dispatch.d.ts +2 -2
  354. package/dist/systems/elevator/elevator-dispatch.js +1 -1
  355. package/dist/systems/elevator/elevator-geometry.d.ts +1 -1
  356. package/dist/systems/elevator/elevator-opening-sync.d.ts +1 -1
  357. package/dist/systems/elevator/elevator-opening-sync.js +2 -2
  358. package/dist/systems/elevator/elevator-opening-system.js +3 -3
  359. package/dist/systems/elevator/elevator-runtime.d.ts +3 -3
  360. package/dist/systems/elevator/elevator-runtime.js +3 -3
  361. package/dist/systems/elevator/elevator-service.d.ts +1 -1
  362. package/dist/systems/elevator/elevator-service.js +1 -1
  363. package/dist/systems/fence/fence-centerline.d.ts +2 -2
  364. package/dist/systems/fence/fence-centerline.js +2 -2
  365. package/dist/systems/fence/fence-spline.d.ts +2 -2
  366. package/dist/systems/roof/roof-elevation-system.d.ts +3 -0
  367. package/dist/systems/roof/roof-elevation-system.d.ts.map +1 -0
  368. package/dist/systems/roof/roof-elevation-system.js +94 -0
  369. package/dist/systems/roof/roof-elevation.d.ts +4 -0
  370. package/dist/systems/roof/roof-elevation.d.ts.map +1 -0
  371. package/dist/systems/roof/roof-elevation.js +95 -0
  372. package/dist/systems/roof/roof-footprint.d.ts +17 -0
  373. package/dist/systems/roof/roof-footprint.d.ts.map +1 -0
  374. package/dist/systems/roof/roof-footprint.js +83 -0
  375. package/dist/systems/slab/slab-placement.d.ts +1 -1
  376. package/dist/systems/slab/slab-support.d.ts +1 -1
  377. package/dist/systems/slab/slab-support.js +4 -4
  378. package/dist/systems/stair/stair-flight.d.ts +17 -0
  379. package/dist/systems/stair/stair-flight.d.ts.map +1 -0
  380. package/dist/systems/stair/stair-flight.js +27 -0
  381. package/dist/systems/stair/stair-footprint.d.ts +1 -1
  382. package/dist/systems/stair/stair-opening-preview.d.ts +1621 -1267
  383. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  384. package/dist/systems/stair/stair-opening-preview.js +2 -2
  385. package/dist/systems/stair/stair-opening-sync.d.ts +1 -1
  386. package/dist/systems/stair/stair-opening-sync.js +6 -6
  387. package/dist/systems/stair/stair-opening-system.d.ts +1 -0
  388. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
  389. package/dist/systems/stair/stair-opening-system.js +100 -103
  390. package/dist/systems/stair/stair-rise.d.ts +1 -1
  391. package/dist/systems/stair/stair-rise.d.ts.map +1 -1
  392. package/dist/systems/stair/stair-rise.js +23 -24
  393. package/dist/systems/wall/wall-curve.d.ts +3 -2
  394. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  395. package/dist/systems/wall/wall-curve.js +73 -0
  396. package/dist/systems/wall/wall-footprint.d.ts +3 -2
  397. package/dist/systems/wall/wall-footprint.d.ts.map +1 -1
  398. package/dist/systems/wall/wall-footprint.js +3 -2
  399. package/dist/systems/wall/wall-mitering.d.ts +1 -1
  400. package/dist/systems/wall/wall-mitering.js +1 -1
  401. package/dist/systems/wall/wall-move.d.ts +1 -1
  402. package/dist/systems/wall/wall-top.d.ts +1 -1
  403. package/dist/systems/wall/wall-topology.d.ts +2 -2
  404. package/dist/systems/wall/wall-topology.js +4 -4
  405. package/dist/utils/clone-scene-graph.d.ts +3 -3
  406. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  407. package/dist/utils/clone-scene-graph.js +35 -3
  408. package/dist/utils/scene-migrations.d.ts +3 -3
  409. package/dist/utils/scene-migrations.js +3 -3
  410. package/dist/utils/vertical-scene-migration.js +5 -5
  411. package/dist/validation/validate-build-json.d.ts +7 -1
  412. package/dist/validation/validate-build-json.d.ts.map +1 -1
  413. package/dist/validation/validate-build-json.js +92 -4
  414. package/package.json +23 -5
  415. package/dist/systems/elevator/elevator-runtime-system.d.ts +0 -2
  416. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +0 -1
  417. package/dist/systems/elevator/elevator-runtime-system.js +0 -8
@@ -1 +1 @@
1
- {"version":3,"file":"spatial-grid-sync.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/spatial-grid-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAY,QAAQ,EAAY,MAAM,cAAc,CAAA;AAY/F,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAmBpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,GAAG,IAAI,CASf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,SAAS,EAClB,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,GAChC,SAAS,GAAG,IAAI,CAgBlB;AAKD,wBAAgB,mBAAmB,IAAI,MAAM,IAAI,CAgIhD;AAMD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,QAcnC;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,QAOnC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,QAqBnC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,QAgDnC;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,QAUnC"}
1
+ {"version":3,"file":"spatial-grid-sync.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/spatial-grid-sync.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAY,QAAQ,EAAY,MAAM,cAAc,CAAA;AAY/F,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAmBpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,GAAG,IAAI,CASf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,SAAS,EAClB,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,GAChC,SAAS,GAAG,IAAI,CAgBlB;AAKD,wBAAgB,mBAAmB,IAAI,MAAM,IAAI,CAwNhD;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAA;AAE5C,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,MAAM,CAmBR;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,QAgBnC;AAMD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,QAcnC;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,QAOnC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,EAClC,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAQ,QA6BtB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,QAgDnC;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,QAUnC"}
@@ -1,12 +1,13 @@
1
- import { getRenderableSlabPolygon } from '../../lib/slab-polygon';
2
- import { isLevelAtSiteDatum, isLevelBaseConsumer } from '../../lib/terrain-support';
3
- import { nodeRegistry } from '../../registry';
4
- import { getLevelBelow } from '../../services/storey';
5
- import useLiveTerrain from '../../store/use-live-terrain';
6
- import useScene from '../../store/use-scene';
7
- import { getFloorPlacedFootprints } from './floor-placed-elevation';
8
- import { itemOverlapsPolygon, spatialGridManager, wallOverlapsPolygon, } from './spatial-grid-manager';
9
- import { GROUND_SUPPORT_ID } from './support-host-id';
1
+ import { subtractPolygonsFromPolygon } from '../../lib/polygon-union.js';
2
+ import { getRenderableSlabPolygon } from '../../lib/slab-polygon.js';
3
+ import { isLevelAtSiteDatum, isLevelBaseConsumer } from '../../lib/terrain-support.js';
4
+ import { nodeRegistry } from '../../registry/index.js';
5
+ import { getLevelBelow } from '../../services/storey.js';
6
+ import useLiveTerrain from '../../store/use-live-terrain.js';
7
+ import useScene from '../../store/use-scene.js';
8
+ import { getFloorPlacedFootprints } from './floor-placed-elevation.js';
9
+ import { itemOverlapsPolygon, spatialGridManager, wallOverlapsPolygon, } from './spatial-grid-manager.js';
10
+ import { GROUND_SUPPORT_ID } from './support-host-id.js';
10
11
  export function resolveLevelId(node, nodes) {
11
12
  // If the node itself is a level
12
13
  if (node.type === 'level')
@@ -98,13 +99,55 @@ export function initSpatialGridSync() {
98
99
  const markDirty = (id) => store.getState().markDirty(id);
99
100
  // Subscribe to all changes
100
101
  const unsubscribeScene = store.subscribe((state, prevState) => {
102
+ if (state.nodes === prevState.nodes)
103
+ return;
104
+ // A bulk slab change (scene load/reload, paste, import) used to run
105
+ // markNodesOverlappingSlab per slab — two full node scans each, O(slabs ×
106
+ // nodes). Above the threshold, dirty every possible slab dependent once (a
107
+ // superset of the per-slab result) and skip the per-slab scans below.
108
+ const bulkSlabs = countBulkSlabChanges(state.nodes, prevState.nodes) >= BULK_SLAB_CHANGE_THRESHOLD;
109
+ if (bulkSlabs)
110
+ markAllSlabDependents(state.nodes, markDirty);
111
+ const changedSlabContextLevels = new Set();
112
+ const checkSlabContext = (id) => {
113
+ const previous = prevState.nodes[id];
114
+ const next = state.nodes[id];
115
+ if (previous === next)
116
+ return;
117
+ const wallChanged = (previous?.type === 'wall' || next?.type === 'wall') &&
118
+ (previous?.type !== 'wall' ||
119
+ next?.type !== 'wall' ||
120
+ previous.parentId !== next.parentId ||
121
+ previous.start !== next.start ||
122
+ previous.end !== next.end ||
123
+ previous.thickness !== next.thickness ||
124
+ previous.curveOffset !== next.curveOffset);
125
+ const slabChanged = (previous?.type === 'slab' || next?.type === 'slab') &&
126
+ (previous?.type !== 'slab' ||
127
+ next?.type !== 'slab' ||
128
+ previous.parentId !== next.parentId ||
129
+ previous.polygon !== next.polygon ||
130
+ previous.elevation !== next.elevation);
131
+ if (!(wallChanged || slabChanged))
132
+ return;
133
+ if (previous)
134
+ changedSlabContextLevels.add(resolveLevelId(previous, prevState.nodes));
135
+ if (next)
136
+ changedSlabContextLevels.add(resolveLevelId(next, state.nodes));
137
+ };
138
+ for (const id in prevState.nodes)
139
+ checkSlabContext(id);
140
+ for (const id in state.nodes) {
141
+ if (!prevState.nodes[id])
142
+ checkSlabContext(id);
143
+ }
101
144
  // Detect added nodes
102
145
  for (const [id, node] of Object.entries(state.nodes)) {
103
146
  if (!prevState.nodes[id]) {
104
147
  const levelId = resolveLevelId(node, state.nodes);
105
148
  spatialGridManager.handleNodeCreated(node, levelId);
106
149
  // When a slab is added, mark overlapping items/walls dirty
107
- if (node.type === 'slab') {
150
+ if (node.type === 'slab' && !bulkSlabs) {
108
151
  markNodesOverlappingSlab(node, state.nodes, markDirty);
109
152
  markCoveringDependentsBelow(levelId, state.nodes, markDirty);
110
153
  }
@@ -122,8 +165,8 @@ export function initSpatialGridSync() {
122
165
  const levelId = resolveLevelId(node, prevState.nodes);
123
166
  spatialGridManager.handleNodeDeleted(id, node.type, levelId);
124
167
  // When a slab is removed, mark items/walls that were on it dirty (using current state)
125
- if (node.type === 'slab') {
126
- markNodesOverlappingSlab(node, state.nodes, markDirty);
168
+ if (node.type === 'slab' && !bulkSlabs) {
169
+ markNodesOverlappingSlab(node, state.nodes, markDirty, prevState.nodes);
127
170
  markCoveringDependentsBelow(levelId, state.nodes, markDirty);
128
171
  }
129
172
  // Deleting a sculpted site drops the ground back to the datum, so its
@@ -160,7 +203,9 @@ export function initSpatialGridSync() {
160
203
  const levelId = resolveLevelId(node, state.nodes);
161
204
  spatialGridManager.handleNodeUpdated(node, levelId);
162
205
  }
163
- markSlabChangeDependents(prev, node, state.nodes, markDirty);
206
+ if (!bulkSlabs) {
207
+ markSlabChangeDependents(prev, node, state.nodes, markDirty, prevState.nodes);
208
+ }
164
209
  }
165
210
  else if (node.type === 'level' && prev.type === 'level') {
166
211
  if (node.height !== prev.height) {
@@ -188,6 +233,38 @@ export function initSpatialGridSync() {
188
233
  }
189
234
  }
190
235
  }
236
+ // Unchanged slabs can lose an adopted wall band or a sibling seam. Their
237
+ // stored polygons cannot identify objects standing on the former boundary.
238
+ if (changedSlabContextLevels.size === 0)
239
+ return;
240
+ const beforeContext = slabBoundaryContext(prevState.nodes, changedSlabContextLevels);
241
+ const afterContext = slabBoundaryContext(state.nodes, changedSlabContextLevels);
242
+ for (const context of afterContext.values()) {
243
+ for (const slab of context.slabs) {
244
+ const previous = prevState.nodes[slab.id];
245
+ if (previous?.type !== 'slab')
246
+ continue;
247
+ if (slab.parentId !== previous.parentId ||
248
+ slab.polygon !== previous.polygon ||
249
+ slab.elevation !== previous.elevation ||
250
+ slab.holes !== previous.holes)
251
+ continue;
252
+ const previousContext = beforeContext.get(resolveLevelId(previous, prevState.nodes));
253
+ const beforePolygon = cachedSlabPolygon(previous, previousContext);
254
+ const afterPolygon = cachedSlabPolygon(slab, context);
255
+ if (beforePolygon.length === afterPolygon.length &&
256
+ beforePolygon.every((point, i) => arraysEqual(point, afterPolygon[i])))
257
+ continue;
258
+ // Support only changed in the gained/lost bands, not across the slab interior.
259
+ const changedBands = [
260
+ ...subtractPolygonsFromPolygon(beforePolygon, [afterPolygon]),
261
+ ...subtractPolygonsFromPolygon(afterPolygon, [beforePolygon]),
262
+ ];
263
+ for (const polygon of changedBands) {
264
+ markNodesOverlappingPolygon(resolveLevelId(slab, state.nodes), polygon, state.nodes, markDirty, context.consumers);
265
+ }
266
+ }
267
+ }
191
268
  });
192
269
  // Live terrain is deliberately not written into `useScene` per dab: doing so
193
270
  // would encode the whole field, flood history, and wake every scene subscriber.
@@ -202,6 +279,61 @@ export function initSpatialGridSync() {
202
279
  unsubscribeLiveTerrain();
203
280
  };
204
281
  }
282
+ /**
283
+ * Bulk slab-change guard. A scene load, reload, paste or import changes tens to
284
+ * thousands of slabs in one store write, and scanning every node twice per slab
285
+ * is O(slabs × nodes): a 4,600-slab scene blocked the main thread for ~5 s on
286
+ * every load. When at least this many slabs are added, removed or reshaped in
287
+ * one write, `markAllSlabDependents` dirties every node any slab could affect,
288
+ * once, and the per-slab scans are skipped. The marks are a superset of the
289
+ * per-slab result — and `setScene` marks every node dirty right after `set()`
290
+ * regardless — so load-time behaviour is unchanged; the saving is the scans.
291
+ */
292
+ export const BULK_SLAB_CHANGE_THRESHOLD = 32;
293
+ export function countBulkSlabChanges(nodes, prevNodes) {
294
+ let count = 0;
295
+ for (const id in nodes) {
296
+ const node = nodes[id];
297
+ if (node.type !== 'slab')
298
+ continue;
299
+ const prev = prevNodes[id];
300
+ if (prev?.type !== 'slab' ||
301
+ prev.polygon !== node.polygon ||
302
+ prev.elevation !== node.elevation ||
303
+ prev.holes !== node.holes) {
304
+ count++;
305
+ }
306
+ }
307
+ for (const id in prevNodes) {
308
+ if (prevNodes[id].type === 'slab' && !nodes[id])
309
+ count++;
310
+ }
311
+ return count;
312
+ }
313
+ /**
314
+ * Every node a slab change can dirty, without looking at any slab: walls and
315
+ * ceilings (overlap and covering-below rules), stairs (deck attachment) and
316
+ * level-hosted floor-placed kinds (the generic re-elevation sweep).
317
+ */
318
+ export function markAllSlabDependents(nodes, markDirty) {
319
+ for (const id in nodes) {
320
+ const node = nodes[id];
321
+ if (node.type === 'wall' || node.type === 'ceiling' || node.type === 'stair') {
322
+ markDirty(node.id);
323
+ continue;
324
+ }
325
+ const floorPlaced = nodeRegistry.get(node.type)?.capabilities?.floorPlaced;
326
+ if (!floorPlaced)
327
+ continue;
328
+ if (floorPlaced.applies && !floorPlaced.applies(node))
329
+ continue;
330
+ const parentId = node.parentId;
331
+ const parent = parentId ? nodes[parentId] : null;
332
+ if (parent && parent.type !== 'level')
333
+ continue;
334
+ markDirty(node.id);
335
+ }
336
+ }
205
337
  function arraysEqual(a, b) {
206
338
  return a.length === b.length && a.every((v, i) => v === b[i]);
207
339
  }
@@ -240,12 +372,13 @@ export function markDeckAttachedStairs(slabId, nodes, markDirty) {
240
372
  * Dirty every consumer of a slab's top or underside. Kept pure so committed
241
373
  * scene writes and live handle previews use the same dependency boundary.
242
374
  */
243
- export function markSlabChangeDependents(previous, next, nodes, markDirty) {
244
- const supportChanged = next.polygon !== previous.polygon ||
375
+ export function markSlabChangeDependents(previous, next, nodes, markDirty, previousNodes = nodes) {
376
+ const supportChanged = next.parentId !== previous.parentId ||
377
+ next.polygon !== previous.polygon ||
245
378
  next.elevation !== previous.elevation ||
246
379
  next.holes !== previous.holes;
247
380
  if (supportChanged) {
248
- markNodesOverlappingSlab(previous, nodes, markDirty);
381
+ markNodesOverlappingSlab(previous, nodes, markDirty, previousNodes);
249
382
  markNodesOverlappingSlab(next, nodes, markDirty);
250
383
  }
251
384
  if (next.elevation !== previous.elevation) {
@@ -254,7 +387,14 @@ export function markSlabChangeDependents(previous, next, nodes, markDirty) {
254
387
  if (supportChanged ||
255
388
  next.thickness !== previous.thickness ||
256
389
  next.recessed !== previous.recessed) {
257
- markCoveringDependentsBelow(resolveLevelId(next, nodes), nodes, markDirty);
390
+ const nextLevelId = resolveLevelId(next, nodes);
391
+ markCoveringDependentsBelow(nextLevelId, nodes, markDirty);
392
+ if (next.parentId !== previous.parentId) {
393
+ const previousLevelId = resolveLevelId(previous, previousNodes);
394
+ if (previousLevelId !== nextLevelId) {
395
+ markCoveringDependentsBelow(previousLevelId, nodes, markDirty);
396
+ }
397
+ }
258
398
  }
259
399
  }
260
400
  /**
@@ -344,18 +484,8 @@ export function markCoveringDependentsBelow(slabLevelId, nodes, markDirty) {
344
484
  }
345
485
  }
346
486
  }
347
- /**
348
- * Mark all floor items and walls that may be affected by a slab change as dirty.
349
- */
350
- function markNodesOverlappingSlab(slab, nodes, markDirty) {
351
- if (slab.polygon.length < 3)
352
- return;
487
+ function renderableSlabPolygon(slab, nodes) {
353
488
  const slabLevelId = resolveLevelId(slab, nodes);
354
- // Walls AND floor-placed nodes follow the slab's RENDERED footprint
355
- // (band-adopted edges reach the wall's outer face), so the dirty gate
356
- // must test the same polygon the support queries re-evaluate — a stored
357
- // polygon that stops short of the wall body would otherwise never
358
- // re-elevate nodes sitting over the adopted band.
359
489
  const levelWalls = [];
360
490
  const siblingSlabs = [];
361
491
  for (const node of Object.values(nodes)) {
@@ -368,8 +498,20 @@ function markNodesOverlappingSlab(slab, nodes, markDirty) {
368
498
  siblingSlabs.push(node);
369
499
  }
370
500
  }
371
- const renderedPolygon = getRenderableSlabPolygon(slab, { walls: levelWalls, siblingSlabs });
372
- for (const node of Object.values(nodes)) {
501
+ return getRenderableSlabPolygon(slab, { walls: levelWalls, siblingSlabs });
502
+ }
503
+ /**
504
+ * Mark all floor items and walls that may be affected by a slab change as dirty.
505
+ */
506
+ function markNodesOverlappingSlab(slab, nodes, markDirty, contextNodes = nodes) {
507
+ if (slab.polygon.length < 3)
508
+ return;
509
+ const slabLevelId = resolveLevelId(slab, contextNodes);
510
+ const renderedPolygon = renderableSlabPolygon(slab, contextNodes);
511
+ markNodesOverlappingPolygon(slabLevelId, renderedPolygon, nodes, markDirty);
512
+ }
513
+ function markNodesOverlappingPolygon(slabLevelId, renderedPolygon, nodes, markDirty, candidates = Object.values(nodes)) {
514
+ for (const node of candidates) {
373
515
  if (node.type === 'wall') {
374
516
  const wall = node;
375
517
  if (resolveLevelId(node, nodes) !== slabLevelId)
@@ -412,3 +554,38 @@ function markNodesOverlappingSlab(slab, nodes, markDirty) {
412
554
  }
413
555
  }
414
556
  }
557
+ function slabBoundaryContext(nodes, levels) {
558
+ const contexts = new Map();
559
+ for (const id in nodes) {
560
+ const node = nodes[id];
561
+ const floorPlaced = nodeRegistry.get(node.type)?.capabilities?.floorPlaced;
562
+ if (node.type !== 'wall' && node.type !== 'slab' && !floorPlaced)
563
+ continue;
564
+ const levelId = resolveLevelId(node, nodes);
565
+ if (!levels.has(levelId))
566
+ continue;
567
+ let context = contexts.get(levelId);
568
+ if (!context) {
569
+ context = { walls: [], slabs: [], consumers: [], polygons: new Map() };
570
+ contexts.set(levelId, context);
571
+ }
572
+ if (node.type === 'wall')
573
+ context.walls.push(node);
574
+ if (node.type === 'slab')
575
+ context.slabs.push(node);
576
+ if (node.type === 'wall' || floorPlaced)
577
+ context.consumers.push(node);
578
+ }
579
+ return contexts;
580
+ }
581
+ function cachedSlabPolygon(slab, context) {
582
+ let polygon = context.polygons.get(slab.id);
583
+ if (!polygon) {
584
+ polygon = getRenderableSlabPolygon(slab, {
585
+ walls: context.walls,
586
+ siblingSlabs: context.slabs.filter((sibling) => sibling.id !== slab.id),
587
+ });
588
+ context.polygons.set(slab.id, polygon);
589
+ }
590
+ return polygon;
591
+ }
@@ -1,4 +1,4 @@
1
- import type { AnyNode, FenceNode, WallNode } from '../../schema';
1
+ import type { AnyNode, AnyNodeId, FenceNode, WallNode } from '../../schema/index.js';
2
2
  export type SupportSlabPatch = {
3
3
  supportSlabId: string | undefined;
4
4
  };
@@ -13,8 +13,29 @@ export type SupportSlabPatchOptions = {
13
13
  maxElevation?: number | null;
14
14
  /** Pointer- or snap-decided host. Ground is a first-class support source. */
15
15
  preferredSlabId?: string | null;
16
+ /** Persist even an unambiguous host so later overlapping slabs cannot re-elect it. */
17
+ pinSupport?: boolean;
18
+ };
19
+ export type FrozenFloorPlacementOptions = {
20
+ /** Canonical position before floor/support lift is applied. */
21
+ position: [number, number, number];
22
+ rotation?: unknown;
23
+ /** Exact level-local elevation hit on a non-slab construction surface. */
24
+ elevation: number;
25
+ /** Slab/ground supporting that construction surface, when known. */
26
+ preferredSlabId?: string | null;
16
27
  };
17
28
  export declare function resolveSupportSlabPatch(node: AnyNode, nodes: Record<string, AnyNode>, options?: SupportSlabPatchOptions): SupportSlabPatch;
29
+ /**
30
+ * Freeze an exact pointed node-top elevation into a floor-placed node's
31
+ * existing canonical Y offset, while pinning the slab/ground beneath that
32
+ * surface. This deliberately does not create a live hosting edge to the
33
+ * pointed node: arbitrary mesh faces can be edited or sloped, so placement
34
+ * captures the plane the user chose at commit time.
35
+ */
36
+ export declare function resolveFrozenFloorPlacementPatch(node: AnyNode, nodes: Record<string, AnyNode>, options: FrozenFloorPlacementOptions): SupportSlabPatch & {
37
+ position: [number, number, number];
38
+ };
18
39
  export declare function resolveWallSupportSlabPatch(wall: WallNode, nodes: Record<string, AnyNode>, options?: SupportSlabPatchOptions): SupportSlabPatch;
19
40
  /**
20
41
  * Re-elect a moved wall without discarding an explicit construction source.
@@ -48,4 +69,27 @@ export type FenceSupportInput = Pick<FenceNode, 'start' | 'end' | 'curveOffset'
48
69
  * via the same absent-host default. Pure; exported for tests.
49
70
  */
50
71
  export declare function resolveFenceSupportSlabPatch(fence: FenceSupportInput, nodes: Record<string, AnyNode>, options?: SupportSlabPatchOptions): SupportSlabPatch;
72
+ export type FenceConstructionOptions = {
73
+ supportCap?: number | null;
74
+ preferredSupportSlabId?: string | null;
75
+ constructionElevation?: number | null;
76
+ };
77
+ export declare function resolveFenceConstructionSupport(fence: FenceNode, levelId: string, nodes: Record<string, AnyNode>, options?: FenceConstructionOptions): FenceNode;
78
+ export type WallConstructionOptions = {
79
+ supportCap?: number | null;
80
+ preferredSupportSlabId?: string | null;
81
+ constructionElevation?: number | null;
82
+ constructionHeight?: number | null;
83
+ flatConstructionBase?: boolean;
84
+ constructionSourceNodeId?: AnyNodeId | null;
85
+ };
86
+ export declare function resolveTerrainWallConstructionOptions(nodes: Record<string, AnyNode>, levelId: string, point: readonly [number, number], defaults?: Record<string, unknown>): WallConstructionOptions | undefined;
87
+ export type WallConstructionResolution = {
88
+ walls: WallNode[];
89
+ sourceSupportUpdate: {
90
+ id: AnyNodeId;
91
+ data: SupportSlabPatch;
92
+ } | null;
93
+ };
94
+ export declare function resolveWallConstruction(nodes: Record<string, AnyNode>, levelId: string, walls: readonly WallNode[], options?: WallConstructionOptions): WallConstructionResolution;
51
95
  //# sourceMappingURL=support-host-patch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"support-host-patch.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/support-host-patch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAa,SAAS,EAAY,QAAQ,EAAE,MAAM,cAAc,CAAA;AAKrF,MAAM,MAAM,gBAAgB,GAAG;IAAE,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAA;AAEpE,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,6EAA6E;IAC7E,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC,CAAA;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE,uBAAuB,GAChC,gBAAgB,CAiDlB;AAED,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE,uBAAuB,GAChC,gBAAgB,CAiDlB;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,gBAAgB,CAIlB;AAED,sFAAsF;AACtF,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAClC,SAAS,EACT,OAAO,GAAG,KAAK,GAAG,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,CACpE,CAAA;AA4BD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,iBAAiB,EACxB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE,uBAAuB,GAChC,gBAAgB,CA4ClB"}
1
+ {"version":3,"file":"support-host-patch.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/support-host-patch.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,9 @@
1
- import { nodeRegistry } from '../../registry';
2
- import { getWallCurveFrameAt, isCurvedWall } from '../../systems/wall/wall-curve';
3
- import { GROUND_SUPPORT_ID, getFloorPlacedFootprints } from './floor-placed-elevation';
4
- import { SUPPORT_ELEVATION_EPSILON, spatialGridManager } from './spatial-grid-manager';
1
+ import { levelBaseElevationAt, terrainSupportLift } from '../../lib/terrain-support.js';
2
+ import { nodeRegistry } from '../../registry/index.js';
3
+ import { DEFAULT_LEVEL_HEIGHT } from '../../services/level-height.js';
4
+ import { getWallCurveFrameAt, isCurvedWall } from '../../systems/wall/wall-curve.js';
5
+ import { GROUND_SUPPORT_ID, getFloorPlacedElevation, getFloorPlacedFootprints, } from './floor-placed-elevation.js';
6
+ import { SUPPORT_ELEVATION_EPSILON, spatialGridManager } from './spatial-grid-manager.js';
5
7
  export function resolveSupportSlabPatch(node, nodes, options) {
6
8
  const floorPlaced = nodeRegistry.get(node.type)?.capabilities?.floorPlaced;
7
9
  if (!floorPlaced || (floorPlaced.applies && !floorPlaced.applies(node))) {
@@ -13,6 +15,21 @@ export function resolveSupportSlabPatch(node, nodes, options) {
13
15
  return { supportSlabId: undefined };
14
16
  const maxElevation = options?.maxElevation;
15
17
  const footprints = getFloorPlacedFootprints(floorPlaced, node, { nodes });
18
+ if (options?.preferredSlabId === GROUND_SUPPORT_ID) {
19
+ return { supportSlabId: GROUND_SUPPORT_ID };
20
+ }
21
+ if (options?.preferredSlabId) {
22
+ for (const footprint of footprints) {
23
+ const position = footprint.position ?? node.position;
24
+ if (!Array.isArray(position) || position.length !== 3)
25
+ continue;
26
+ const elevation = spatialGridManager.getHostSlabElevationForFootprint(parent.id, options.preferredSlabId, position, footprint.dimensions, footprint.rotation);
27
+ if (elevation !== null &&
28
+ (maxElevation == null || elevation <= maxElevation + SUPPORT_ELEVATION_EPSILON)) {
29
+ return { supportSlabId: options.preferredSlabId };
30
+ }
31
+ }
32
+ }
16
33
  const candidateElevations = new Set();
17
34
  let winner = null;
18
35
  let cappedOut = false;
@@ -32,12 +49,54 @@ export function resolveSupportSlabPatch(node, nodes, options) {
32
49
  }
33
50
  }
34
51
  if (winner !== null) {
35
- return { supportSlabId: candidateElevations.size >= 2 ? winner.slabId : undefined };
52
+ return {
53
+ supportSlabId: options?.pinSupport || candidateElevations.size >= 2 ? winner.slabId : undefined,
54
+ };
36
55
  }
37
56
  // Capped election chose the ground while overlapping slabs sit above the
38
57
  // cap: persist the ground host, or the uncapped per-frame election would
39
58
  // lift the committed node back onto the deck.
40
- return { supportSlabId: cappedOut ? GROUND_SUPPORT_ID : undefined };
59
+ return {
60
+ supportSlabId: options?.pinSupport || cappedOut ? GROUND_SUPPORT_ID : undefined,
61
+ };
62
+ }
63
+ /**
64
+ * Freeze an exact pointed node-top elevation into a floor-placed node's
65
+ * existing canonical Y offset, while pinning the slab/ground beneath that
66
+ * surface. This deliberately does not create a live hosting edge to the
67
+ * pointed node: arbitrary mesh faces can be edited or sloped, so placement
68
+ * captures the plane the user chose at commit time.
69
+ */
70
+ export function resolveFrozenFloorPlacementPatch(node, nodes, options) {
71
+ const effectiveNode = {
72
+ ...node,
73
+ position: options.position,
74
+ ...(options.rotation !== undefined ? { rotation: options.rotation } : {}),
75
+ };
76
+ const floorPlaced = nodeRegistry.get(effectiveNode.type)?.capabilities?.floorPlaced;
77
+ if (!floorPlaced || (floorPlaced.applies && !floorPlaced.applies(effectiveNode))) {
78
+ return { supportSlabId: undefined, position: options.position };
79
+ }
80
+ const supportPatch = resolveSupportSlabPatch(effectiveNode, nodes, {
81
+ maxElevation: options.elevation,
82
+ preferredSlabId: options.preferredSlabId,
83
+ pinSupport: true,
84
+ });
85
+ const pinnedNode = { ...effectiveNode, ...supportPatch };
86
+ const supportElevation = getFloorPlacedElevation({
87
+ node: pinnedNode,
88
+ nodes,
89
+ position: options.position,
90
+ rotation: options.rotation,
91
+ });
92
+ return {
93
+ ...supportPatch,
94
+ position: [
95
+ options.position[0],
96
+ options.position[1] + options.elevation - supportElevation,
97
+ options.position[2],
98
+ ],
99
+ };
41
100
  }
42
101
  export function resolveWallSupportSlabPatch(wall, nodes, options) {
43
102
  const parent = wall.parentId ? nodes[wall.parentId] : null;
@@ -138,6 +197,9 @@ export function resolveFenceSupportSlabPatch(fence, nodes, options) {
138
197
  const points = fenceCenterlinePoints(fence);
139
198
  const candidateElevations = new Set();
140
199
  let winner = null;
200
+ if (options?.preferredSlabId === GROUND_SUPPORT_ID) {
201
+ return { supportSlabId: GROUND_SUPPORT_ID };
202
+ }
141
203
  for (let i = 1; i < points.length; i++) {
142
204
  const [ax, az] = points[i - 1];
143
205
  const [bx, bz] = points[i];
@@ -149,6 +211,13 @@ export function resolveFenceSupportSlabPatch(fence, nodes, options) {
149
211
  // getItemFootprint's rotation convention: local +X maps to
150
212
  // (cos yRot, sin yRot) in XZ, so the segment angle aligns the band.
151
213
  const rotation = [0, Math.atan2(bz - az, bx - ax), 0];
214
+ if (options?.preferredSlabId) {
215
+ const preferredElevation = spatialGridManager.getHostSlabElevationForFootprint(parent.id, options.preferredSlabId, position, dimensions, rotation);
216
+ if (preferredElevation !== null &&
217
+ (maxElevation == null || preferredElevation <= maxElevation + SUPPORT_ELEVATION_EPSILON)) {
218
+ return { supportSlabId: options.preferredSlabId };
219
+ }
220
+ }
152
221
  const candidates = spatialGridManager.getSupportCandidatesForFootprint(parent.id, position, dimensions, rotation);
153
222
  for (const candidate of candidates)
154
223
  candidateElevations.add(candidate.elevation);
@@ -157,8 +226,104 @@ export function resolveFenceSupportSlabPatch(fence, nodes, options) {
157
226
  winner = { slabId: support.slabId, elevation: support.elevation };
158
227
  }
159
228
  }
160
- if (winner === null)
161
- return { supportSlabId: undefined };
229
+ if (winner === null) {
230
+ return { supportSlabId: options?.pinSupport ? GROUND_SUPPORT_ID : undefined };
231
+ }
162
232
  const persist = candidateElevations.size >= 2 || winner.elevation > SUPPORT_ELEVATION_EPSILON;
163
- return { supportSlabId: persist ? winner.slabId : undefined };
233
+ return { supportSlabId: options?.pinSupport || persist ? winner.slabId : undefined };
234
+ }
235
+ export function resolveFenceConstructionSupport(fence, levelId, nodes, options) {
236
+ const supportPatch = resolveFenceSupportSlabPatch({ ...fence, parentId: levelId }, nodes, {
237
+ maxElevation: options?.supportCap ?? null,
238
+ preferredSlabId: options?.preferredSupportSlabId ?? null,
239
+ pinSupport: options?.constructionElevation != null,
240
+ });
241
+ const host = supportPatch.supportSlabId ? nodes[supportPatch.supportSlabId] : null;
242
+ const baseElevation = host?.type === 'slab'
243
+ ? host.elevation
244
+ : levelBaseElevationAt(nodes, levelId, fence.start[0], fence.start[1]);
245
+ const supportOffset = options?.constructionElevation == null
246
+ ? fence.supportOffset
247
+ : options.constructionElevation - baseElevation;
248
+ return {
249
+ ...fence,
250
+ ...supportPatch,
251
+ supportOffset: supportOffset != null && Math.abs(supportOffset) > 1e-6 ? supportOffset : undefined,
252
+ };
253
+ }
254
+ export function resolveTerrainWallConstructionOptions(nodes, levelId, point, defaults) {
255
+ const constructionElevation = terrainSupportLift(nodes, levelId, point[0], point[1]);
256
+ if (constructionElevation == null)
257
+ return undefined;
258
+ const level = nodes[levelId];
259
+ const constructionHeight = typeof defaults?.height === 'number'
260
+ ? defaults.height
261
+ : level?.type === 'level'
262
+ ? (level.height ?? DEFAULT_LEVEL_HEIGHT)
263
+ : DEFAULT_LEVEL_HEIGHT;
264
+ return {
265
+ constructionElevation,
266
+ constructionHeight,
267
+ supportCap: constructionElevation,
268
+ };
269
+ }
270
+ export function resolveWallConstruction(nodes, levelId, walls, options) {
271
+ let resolvedNodes = nodes;
272
+ let sourceSupportUpdate = null;
273
+ const constructionSourceNodeId = options?.constructionSourceNodeId;
274
+ const constructionSourceIsSlab = constructionSourceNodeId
275
+ ? nodes[constructionSourceNodeId]?.type === 'slab'
276
+ : false;
277
+ if (constructionSourceNodeId) {
278
+ const sourceNode = nodes[constructionSourceNodeId];
279
+ const currentSupport = sourceNode && 'supportSlabId' in sourceNode
280
+ ? sourceNode.supportSlabId
281
+ : undefined;
282
+ if (sourceNode && currentSupport == null) {
283
+ const data = resolveSupportSlabPatch(sourceNode, nodes, { pinSupport: true });
284
+ if (data.supportSlabId != null) {
285
+ sourceSupportUpdate = { id: constructionSourceNodeId, data };
286
+ resolvedNodes = {
287
+ ...nodes,
288
+ [constructionSourceNodeId]: { ...sourceNode, ...data },
289
+ };
290
+ }
291
+ }
292
+ }
293
+ const resolvedWalls = walls.map((createdWall) => {
294
+ const wallWithParent = { ...createdWall, parentId: levelId };
295
+ const terrainBase = terrainSupportLift(resolvedNodes, levelId, createdWall.start[0], createdWall.start[1]);
296
+ const wallOptions = options?.preferredSupportSlabId === GROUND_SUPPORT_ID &&
297
+ terrainBase == null &&
298
+ !options.flatConstructionBase
299
+ ? undefined
300
+ : options;
301
+ const flatConstructionBase = wallOptions?.flatConstructionBase === true && !constructionSourceIsSlab;
302
+ const preferredSupportSlabId = flatConstructionBase
303
+ ? GROUND_SUPPORT_ID
304
+ : (wallOptions?.preferredSupportSlabId ??
305
+ (wallOptions?.constructionElevation != null && terrainBase != null
306
+ ? GROUND_SUPPORT_ID
307
+ : null));
308
+ const supportPatch = resolveWallSupportSlabPatch(wallWithParent, resolvedNodes, {
309
+ maxElevation: wallOptions?.supportCap ?? null,
310
+ preferredSlabId: preferredSupportSlabId,
311
+ });
312
+ const sourceSupport = spatialGridManager.getSlabSupportForWall(levelId, createdWall.start, createdWall.end, createdWall.curveOffset, createdWall.thickness, supportPatch.supportSlabId, wallOptions?.supportCap ?? null);
313
+ const groundDraft = preferredSupportSlabId === GROUND_SUPPORT_ID && (terrainBase != null || flatConstructionBase);
314
+ const supportOffset = groundDraft && wallOptions?.constructionElevation != null
315
+ ? wallOptions.constructionElevation - sourceSupport.elevation
316
+ : undefined;
317
+ const preserveDraftHeight = groundDraft &&
318
+ createdWall.height == null &&
319
+ wallOptions?.constructionHeight != null &&
320
+ wallOptions.constructionElevation != null;
321
+ return {
322
+ ...wallWithParent,
323
+ ...supportPatch,
324
+ height: preserveDraftHeight ? wallOptions.constructionHeight : createdWall.height,
325
+ supportOffset: supportOffset != null && Math.abs(supportOffset) > 1e-6 ? supportOffset : undefined,
326
+ };
327
+ });
328
+ return { walls: resolvedWalls, sourceSupportUpdate };
164
329
  }
@@ -1,4 +1,4 @@
1
- import type { CeilingNode, LevelNode, WallNode } from '../../schema';
1
+ import type { CeilingNode, LevelNode, WallNode } from '../../schema/index.js';
2
2
  export declare function useSpatialQuery(): {
3
3
  canPlaceOnFloor: (levelId: LevelNode["id"], position: [number, number, number], dimensions: [number, number, number], rotation: [number, number, number], ignoreIds?: string[]) => {
4
4
  valid: boolean;
@@ -1,5 +1,5 @@
1
1
  import { useCallback } from 'react';
2
- import { spatialGridManager } from './spatial-grid-manager';
2
+ import { spatialGridManager } from './spatial-grid-manager.js';
3
3
  export function useSpatialQuery() {
4
4
  const canPlaceOnFloor = useCallback((levelId, position, dimensions, rotation, ignoreIds) => {
5
5
  return spatialGridManager.canPlaceOnFloor(levelId, position, dimensions, rotation, ignoreIds);