@pascal-app/core 0.9.1 → 1.0.0-beta.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 (378) hide show
  1. package/README.md +17 -10
  2. package/dist/events/bus.d.ts +49 -2
  3. package/dist/events/bus.d.ts.map +1 -1
  4. package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
  5. package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
  6. package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
  7. package/dist/hooks/scene-registry/scene-registry.d.ts +8 -1
  8. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  9. package/dist/hooks/scene-registry/scene-registry.js +27 -1
  10. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +17 -1
  11. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.js +61 -8
  13. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +135 -28
  14. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  15. package/dist/hooks/spatial-grid/spatial-grid-manager.js +427 -271
  16. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +46 -1
  17. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  18. package/dist/hooks/spatial-grid/spatial-grid-sync.js +208 -9
  19. package/dist/hooks/spatial-grid/support-host-id.d.ts +3 -0
  20. package/dist/hooks/spatial-grid/support-host-id.d.ts.map +1 -0
  21. package/dist/hooks/spatial-grid/support-host-id.js +2 -0
  22. package/dist/hooks/spatial-grid/support-host-patch.d.ts +51 -0
  23. package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -0
  24. package/dist/hooks/spatial-grid/support-host-patch.js +164 -0
  25. package/dist/index.d.ts +33 -11
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +32 -10
  28. package/dist/lib/level-name.d.ts +4 -0
  29. package/dist/lib/level-name.d.ts.map +1 -0
  30. package/dist/lib/level-name.js +10 -0
  31. package/dist/lib/measurement-geometry.d.ts +23 -0
  32. package/dist/lib/measurement-geometry.d.ts.map +1 -0
  33. package/dist/lib/measurement-geometry.js +249 -0
  34. package/dist/lib/polygon-geometry.d.ts +0 -1
  35. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  36. package/dist/lib/polygon-geometry.js +0 -18
  37. package/dist/lib/selection-proxy.d.ts +4 -0
  38. package/dist/lib/selection-proxy.d.ts.map +1 -0
  39. package/dist/lib/selection-proxy.js +12 -0
  40. package/dist/lib/slab-polygon.d.ts +31 -2
  41. package/dist/lib/slab-polygon.d.ts.map +1 -1
  42. package/dist/lib/slab-polygon.js +557 -31
  43. package/dist/lib/slots.d.ts +14 -0
  44. package/dist/lib/slots.d.ts.map +1 -0
  45. package/dist/lib/slots.js +26 -0
  46. package/dist/lib/space-detection.d.ts +40 -6
  47. package/dist/lib/space-detection.d.ts.map +1 -1
  48. package/dist/lib/space-detection.js +574 -192
  49. package/dist/lib/terrain-brush.d.ts +163 -0
  50. package/dist/lib/terrain-brush.d.ts.map +1 -0
  51. package/dist/lib/terrain-brush.js +359 -0
  52. package/dist/lib/terrain-codec.d.ts +51 -0
  53. package/dist/lib/terrain-codec.d.ts.map +1 -0
  54. package/dist/lib/terrain-codec.js +167 -0
  55. package/dist/lib/terrain-field.d.ts +150 -0
  56. package/dist/lib/terrain-field.d.ts.map +1 -0
  57. package/dist/lib/terrain-field.js +248 -0
  58. package/dist/lib/terrain-raycast.d.ts +44 -0
  59. package/dist/lib/terrain-raycast.d.ts.map +1 -0
  60. package/dist/lib/terrain-raycast.js +188 -0
  61. package/dist/lib/terrain-source.d.ts +60 -0
  62. package/dist/lib/terrain-source.d.ts.map +1 -0
  63. package/dist/lib/terrain-source.js +90 -0
  64. package/dist/lib/terrain-support.d.ts +60 -0
  65. package/dist/lib/terrain-support.d.ts.map +1 -0
  66. package/dist/lib/terrain-support.js +150 -0
  67. package/dist/lib/wall-distance.d.ts +60 -0
  68. package/dist/lib/wall-distance.d.ts.map +1 -0
  69. package/dist/lib/wall-distance.js +85 -0
  70. package/dist/lib/zone-quantities.d.ts +21 -0
  71. package/dist/lib/zone-quantities.d.ts.map +1 -0
  72. package/dist/lib/zone-quantities.js +428 -0
  73. package/dist/material-library.d.ts +28 -1
  74. package/dist/material-library.d.ts.map +1 -1
  75. package/dist/material-library.js +2233 -441
  76. package/dist/registry/handles.d.ts +91 -3
  77. package/dist/registry/handles.d.ts.map +1 -1
  78. package/dist/registry/index.d.ts +3 -3
  79. package/dist/registry/index.d.ts.map +1 -1
  80. package/dist/registry/index.js +1 -1
  81. package/dist/registry/registry.d.ts +46 -3
  82. package/dist/registry/registry.d.ts.map +1 -1
  83. package/dist/registry/registry.js +82 -1
  84. package/dist/registry/subtree.d.ts.map +1 -1
  85. package/dist/registry/subtree.js +8 -1
  86. package/dist/registry/types.d.ts +781 -39
  87. package/dist/registry/types.d.ts.map +1 -1
  88. package/dist/schema/index.d.ts +27 -8
  89. package/dist/schema/index.d.ts.map +1 -1
  90. package/dist/schema/index.js +23 -6
  91. package/dist/schema/material.d.ts +3 -2
  92. package/dist/schema/material.d.ts.map +1 -1
  93. package/dist/schema/material.js +7 -5
  94. package/dist/schema/nodes/box-vent.d.ts +2 -2
  95. package/dist/schema/nodes/cabinet.d.ts +536 -0
  96. package/dist/schema/nodes/cabinet.d.ts.map +1 -0
  97. package/dist/schema/nodes/cabinet.js +139 -0
  98. package/dist/schema/nodes/ceiling.d.ts +4 -3
  99. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  100. package/dist/schema/nodes/ceiling.js +11 -1
  101. package/dist/schema/nodes/chimney.d.ts +4 -4
  102. package/dist/schema/nodes/column.d.ts +4 -2
  103. package/dist/schema/nodes/column.d.ts.map +1 -1
  104. package/dist/schema/nodes/column.js +5 -0
  105. package/dist/schema/nodes/construction-dimension.d.ts +195 -0
  106. package/dist/schema/nodes/construction-dimension.d.ts.map +1 -0
  107. package/dist/schema/nodes/construction-dimension.js +151 -0
  108. package/dist/schema/nodes/cupola.d.ts +2 -2
  109. package/dist/schema/nodes/door.d.ts +42 -5
  110. package/dist/schema/nodes/door.d.ts.map +1 -1
  111. package/dist/schema/nodes/door.js +32 -0
  112. package/dist/schema/nodes/dormer.d.ts +8 -8
  113. package/dist/schema/nodes/dormer.js +1 -1
  114. package/dist/schema/nodes/downspout.d.ts +2 -2
  115. package/dist/schema/nodes/duct-fitting.d.ts +91 -0
  116. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
  117. package/dist/schema/nodes/duct-fitting.js +92 -0
  118. package/dist/schema/nodes/duct-segment.d.ts +61 -0
  119. package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
  120. package/dist/schema/nodes/duct-segment.js +73 -0
  121. package/dist/schema/nodes/duct-terminal.d.ts +58 -0
  122. package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
  123. package/dist/schema/nodes/duct-terminal.js +53 -0
  124. package/dist/schema/nodes/elevator.d.ts +3 -2
  125. package/dist/schema/nodes/elevator.d.ts.map +1 -1
  126. package/dist/schema/nodes/elevator.js +3 -0
  127. package/dist/schema/nodes/eyebrow-vent.d.ts +2 -2
  128. package/dist/schema/nodes/fence.d.ts +20 -2
  129. package/dist/schema/nodes/fence.d.ts.map +1 -1
  130. package/dist/schema/nodes/fence.js +33 -2
  131. package/dist/schema/nodes/gutter.d.ts +2 -2
  132. package/dist/schema/nodes/hvac-equipment.d.ts +64 -0
  133. package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
  134. package/dist/schema/nodes/hvac-equipment.js +57 -0
  135. package/dist/schema/nodes/item.d.ts +9 -0
  136. package/dist/schema/nodes/item.d.ts.map +1 -1
  137. package/dist/schema/nodes/item.js +24 -0
  138. package/dist/schema/nodes/level.d.ts +2 -1
  139. package/dist/schema/nodes/level.d.ts.map +1 -1
  140. package/dist/schema/nodes/level.js +12 -31
  141. package/dist/schema/nodes/lineset.d.ts +42 -0
  142. package/dist/schema/nodes/lineset.d.ts.map +1 -0
  143. package/dist/schema/nodes/lineset.js +38 -0
  144. package/dist/schema/nodes/liquid-line.d.ts +34 -0
  145. package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
  146. package/dist/schema/nodes/liquid-line.js +24 -0
  147. package/dist/schema/nodes/measurement.d.ts +306 -0
  148. package/dist/schema/nodes/measurement.d.ts.map +1 -0
  149. package/dist/schema/nodes/measurement.js +87 -0
  150. package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
  151. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
  152. package/dist/schema/nodes/pipe-fitting.js +45 -0
  153. package/dist/schema/nodes/pipe-segment.d.ts +48 -0
  154. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
  155. package/dist/schema/nodes/pipe-segment.js +36 -0
  156. package/dist/schema/nodes/pipe-trap.d.ts +43 -0
  157. package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
  158. package/dist/schema/nodes/pipe-trap.js +36 -0
  159. package/dist/schema/nodes/ridge-vent.d.ts +18 -3
  160. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
  161. package/dist/schema/nodes/ridge-vent.js +221 -3
  162. package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
  163. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
  164. package/dist/schema/nodes/roof-segment-shape.js +318 -0
  165. package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
  166. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
  167. package/dist/schema/nodes/roof-segment-walls.js +330 -0
  168. package/dist/schema/nodes/roof-segment.d.ts +84 -8
  169. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  170. package/dist/schema/nodes/roof-segment.js +290 -5
  171. package/dist/schema/nodes/roof.d.ts +8 -8
  172. package/dist/schema/nodes/shelf.d.ts +4 -2
  173. package/dist/schema/nodes/shelf.d.ts.map +1 -1
  174. package/dist/schema/nodes/shelf.js +8 -0
  175. package/dist/schema/nodes/site.d.ts +9 -0
  176. package/dist/schema/nodes/site.d.ts.map +1 -1
  177. package/dist/schema/nodes/site.js +8 -6
  178. package/dist/schema/nodes/skylight.d.ts +10 -10
  179. package/dist/schema/nodes/slab.d.ts +8 -2
  180. package/dist/schema/nodes/slab.d.ts.map +1 -1
  181. package/dist/schema/nodes/slab.js +18 -2
  182. package/dist/schema/nodes/solar-panel.d.ts +4 -4
  183. package/dist/schema/nodes/spawn.d.ts +1 -0
  184. package/dist/schema/nodes/spawn.d.ts.map +1 -1
  185. package/dist/schema/nodes/spawn.js +2 -0
  186. package/dist/schema/nodes/stair-segment.d.ts +4 -4
  187. package/dist/schema/nodes/stair.d.ts +16 -13
  188. package/dist/schema/nodes/stair.d.ts.map +1 -1
  189. package/dist/schema/nodes/stair.js +11 -1
  190. package/dist/schema/nodes/structural-grid.d.ts +27 -0
  191. package/dist/schema/nodes/structural-grid.d.ts.map +1 -0
  192. package/dist/schema/nodes/structural-grid.js +17 -0
  193. package/dist/schema/nodes/turbine-vent.d.ts +2 -2
  194. package/dist/schema/nodes/wall.d.ts +248 -6
  195. package/dist/schema/nodes/wall.d.ts.map +1 -1
  196. package/dist/schema/nodes/wall.js +266 -0
  197. package/dist/schema/nodes/window.d.ts +40 -3
  198. package/dist/schema/nodes/window.d.ts.map +1 -1
  199. package/dist/schema/nodes/window.js +30 -0
  200. package/dist/schema/nodes/zone.d.ts +22 -0
  201. package/dist/schema/nodes/zone.d.ts.map +1 -1
  202. package/dist/schema/nodes/zone.js +21 -0
  203. package/dist/schema/scene-material.d.ts +41 -0
  204. package/dist/schema/scene-material.d.ts.map +1 -0
  205. package/dist/schema/scene-material.js +9 -0
  206. package/dist/schema/terrain.d.ts +28 -0
  207. package/dist/schema/terrain.d.ts.map +1 -0
  208. package/dist/schema/terrain.js +30 -0
  209. package/dist/schema/types.d.ts +1300 -97
  210. package/dist/schema/types.d.ts.map +1 -1
  211. package/dist/schema/types.js +27 -0
  212. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts +80 -0
  213. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts.map +1 -0
  214. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.js +89 -0
  215. package/dist/services/alignment-anchors.d.ts.map +1 -1
  216. package/dist/services/alignment-anchors.js +30 -1
  217. package/dist/services/alignment.d.ts +5 -0
  218. package/dist/services/alignment.d.ts.map +1 -1
  219. package/dist/services/alignment.js +2 -0
  220. package/dist/services/hosting.d.ts +8 -0
  221. package/dist/services/hosting.d.ts.map +1 -1
  222. package/dist/services/hosting.js +13 -4
  223. package/dist/services/index.d.ts +9 -2
  224. package/dist/services/index.d.ts.map +1 -1
  225. package/dist/services/index.js +9 -2
  226. package/dist/services/level-height.d.ts +27 -0
  227. package/dist/services/level-height.d.ts.map +1 -0
  228. package/dist/services/level-height.js +94 -0
  229. package/dist/services/opening-guides.d.ts +141 -0
  230. package/dist/services/opening-guides.d.ts.map +1 -0
  231. package/dist/services/opening-guides.js +267 -0
  232. package/dist/services/port-connectivity.d.ts +115 -0
  233. package/dist/services/port-connectivity.d.ts.map +1 -0
  234. package/dist/services/port-connectivity.js +300 -0
  235. package/dist/services/riser-diagram.d.ts +40 -0
  236. package/dist/services/riser-diagram.d.ts.map +1 -0
  237. package/dist/services/riser-diagram.js +110 -0
  238. package/dist/services/snap.d.ts +9 -1
  239. package/dist/services/snap.d.ts.map +1 -1
  240. package/dist/services/snap.js +21 -1
  241. package/dist/services/storey.d.ts +103 -0
  242. package/dist/services/storey.d.ts.map +1 -0
  243. package/dist/services/storey.js +296 -0
  244. package/dist/services/system-graph.d.ts +30 -0
  245. package/dist/services/system-graph.d.ts.map +1 -0
  246. package/dist/services/system-graph.js +164 -0
  247. package/dist/services/validate-dwv.d.ts +32 -0
  248. package/dist/services/validate-dwv.d.ts.map +1 -0
  249. package/dist/services/validate-dwv.js +118 -0
  250. package/dist/store/actions/node-actions.d.ts.map +1 -1
  251. package/dist/store/actions/node-actions.js +141 -4
  252. package/dist/store/history-control.d.ts +31 -0
  253. package/dist/store/history-control.d.ts.map +1 -1
  254. package/dist/store/history-control.js +130 -0
  255. package/dist/store/use-live-terrain.d.ts +65 -0
  256. package/dist/store/use-live-terrain.d.ts.map +1 -0
  257. package/dist/store/use-live-terrain.js +31 -0
  258. package/dist/store/use-live-transforms.d.ts +8 -0
  259. package/dist/store/use-live-transforms.d.ts.map +1 -1
  260. package/dist/store/use-scene.d.ts +47 -3
  261. package/dist/store/use-scene.d.ts.map +1 -1
  262. package/dist/store/use-scene.js +992 -55
  263. package/dist/systems/elevator/elevator-opening-system.d.ts +1 -0
  264. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -1
  265. package/dist/systems/elevator/elevator-opening-system.js +27 -21
  266. package/dist/systems/elevator/elevator-service.d.ts +0 -2
  267. package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
  268. package/dist/systems/elevator/elevator-service.js +2 -24
  269. package/dist/systems/fence/fence-centerline.d.ts +12 -0
  270. package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
  271. package/dist/systems/fence/fence-centerline.js +37 -0
  272. package/dist/systems/fence/fence-spline.d.ts +39 -0
  273. package/dist/systems/fence/fence-spline.d.ts.map +1 -0
  274. package/dist/systems/fence/fence-spline.js +196 -0
  275. package/dist/systems/slab/slab-placement.d.ts +11 -0
  276. package/dist/systems/slab/slab-placement.d.ts.map +1 -0
  277. package/dist/systems/slab/slab-placement.js +14 -0
  278. package/dist/systems/slab/slab-support.d.ts +137 -0
  279. package/dist/systems/slab/slab-support.d.ts.map +1 -0
  280. package/dist/systems/slab/slab-support.js +557 -0
  281. package/dist/systems/stair/stair-footprint.d.ts.map +1 -1
  282. package/dist/systems/stair/stair-footprint.js +21 -11
  283. package/dist/systems/stair/stair-opening-preview.d.ts +1300 -505
  284. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  285. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  286. package/dist/systems/stair/stair-opening-sync.js +19 -33
  287. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
  288. package/dist/systems/stair/stair-opening-system.js +32 -4
  289. package/dist/systems/stair/stair-rise.d.ts +22 -0
  290. package/dist/systems/stair/stair-rise.d.ts.map +1 -0
  291. package/dist/systems/stair/stair-rise.js +79 -0
  292. package/dist/systems/wall/wall-curve.d.ts +10 -0
  293. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  294. package/dist/systems/wall/wall-curve.js +1 -1
  295. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  296. package/dist/systems/wall/wall-mitering.js +44 -8
  297. package/dist/systems/wall/wall-top.d.ts +40 -0
  298. package/dist/systems/wall/wall-top.d.ts.map +1 -0
  299. package/dist/systems/wall/wall-top.js +46 -0
  300. package/dist/utils/clone-scene-graph.d.ts +1 -0
  301. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  302. package/dist/utils/clone-scene-graph.js +56 -5
  303. package/dist/utils/heal-scene-graph.d.ts +18 -0
  304. package/dist/utils/heal-scene-graph.d.ts.map +1 -0
  305. package/dist/utils/heal-scene-graph.js +106 -0
  306. package/dist/validation/validate-build-json.d.ts +3 -0
  307. package/dist/validation/validate-build-json.d.ts.map +1 -1
  308. package/dist/validation/validate-build-json.js +90 -5
  309. package/package.json +4 -4
  310. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +0 -2
  311. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +0 -1
  312. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +0 -273
  313. package/dist/lib/space-detection-pause.test.d.ts +0 -2
  314. package/dist/lib/space-detection-pause.test.d.ts.map +0 -1
  315. package/dist/lib/space-detection-pause.test.js +0 -42
  316. package/dist/lib/space-detection.test.d.ts +0 -2
  317. package/dist/lib/space-detection.test.d.ts.map +0 -1
  318. package/dist/lib/space-detection.test.js +0 -77
  319. package/dist/registry/registry.test.d.ts +0 -2
  320. package/dist/registry/registry.test.d.ts.map +0 -1
  321. package/dist/registry/registry.test.js +0 -183
  322. package/dist/registry/relations-resolver.test.d.ts +0 -2
  323. package/dist/registry/relations-resolver.test.d.ts.map +0 -1
  324. package/dist/registry/relations-resolver.test.js +0 -183
  325. package/dist/registry/scene-api.test.d.ts +0 -2
  326. package/dist/registry/scene-api.test.d.ts.map +0 -1
  327. package/dist/registry/scene-api.test.js +0 -183
  328. package/dist/registry/subtree.test.d.ts +0 -2
  329. package/dist/registry/subtree.test.d.ts.map +0 -1
  330. package/dist/registry/subtree.test.js +0 -133
  331. package/dist/schema/asset-url.test.d.ts +0 -2
  332. package/dist/schema/asset-url.test.d.ts.map +0 -1
  333. package/dist/schema/asset-url.test.js +0 -134
  334. package/dist/services/alignment-anchors.test.d.ts +0 -2
  335. package/dist/services/alignment-anchors.test.d.ts.map +0 -1
  336. package/dist/services/alignment-anchors.test.js +0 -383
  337. package/dist/services/alignment.test.d.ts +0 -2
  338. package/dist/services/alignment.test.d.ts.map +0 -1
  339. package/dist/services/alignment.test.js +0 -92
  340. package/dist/services/drag-session.test.d.ts +0 -2
  341. package/dist/services/drag-session.test.d.ts.map +0 -1
  342. package/dist/services/drag-session.test.js +0 -219
  343. package/dist/services/hosting.test.d.ts +0 -2
  344. package/dist/services/hosting.test.d.ts.map +0 -1
  345. package/dist/services/hosting.test.js +0 -199
  346. package/dist/services/movement.test.d.ts +0 -2
  347. package/dist/services/movement.test.d.ts.map +0 -1
  348. package/dist/services/movement.test.js +0 -133
  349. package/dist/services/single-undo-dance.test.d.ts +0 -2
  350. package/dist/services/single-undo-dance.test.d.ts.map +0 -1
  351. package/dist/services/single-undo-dance.test.js +0 -210
  352. package/dist/services/snap.test.d.ts +0 -2
  353. package/dist/services/snap.test.d.ts.map +0 -1
  354. package/dist/services/snap.test.js +0 -95
  355. package/dist/store/actions/node-mutation-sanitize.test.d.ts +0 -2
  356. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +0 -1
  357. package/dist/store/actions/node-mutation-sanitize.test.js +0 -147
  358. package/dist/store/actions/reparent.test.d.ts +0 -2
  359. package/dist/store/actions/reparent.test.d.ts.map +0 -1
  360. package/dist/store/actions/reparent.test.js +0 -212
  361. package/dist/store/use-scene-elevator-migration.test.d.ts +0 -2
  362. package/dist/store/use-scene-elevator-migration.test.d.ts.map +0 -1
  363. package/dist/store/use-scene-elevator-migration.test.js +0 -109
  364. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +0 -2
  365. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +0 -1
  366. package/dist/systems/elevator/elevator-opening-sync.test.js +0 -146
  367. package/dist/systems/elevator/elevator-runtime.test.d.ts +0 -2
  368. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +0 -1
  369. package/dist/systems/elevator/elevator-runtime.test.js +0 -64
  370. package/dist/systems/stair/stair-opening-preview.test.d.ts +0 -2
  371. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +0 -1
  372. package/dist/systems/stair/stair-opening-preview.test.js +0 -80
  373. package/dist/systems/stair/stair-opening-sync.test.d.ts +0 -2
  374. package/dist/systems/stair/stair-opening-sync.test.d.ts.map +0 -1
  375. package/dist/systems/stair/stair-opening-sync.test.js +0 -446
  376. package/dist/utils/clone-scene-graph.test.d.ts +0 -2
  377. package/dist/utils/clone-scene-graph.test.d.ts.map +0 -1
  378. package/dist/utils/clone-scene-graph.test.js +0 -59
@@ -1,4 +1,4 @@
1
- import type { AnyNode, AnyNodeId } from '../../schema';
1
+ import type { AnyNode, AnyNodeId, LevelNode, SlabNode } from '../../schema';
2
2
  export declare function resolveLevelId(node: AnyNode, nodes: Record<string, AnyNode>): string;
3
3
  /**
4
4
  * Walks the parent chain of `nodeId` and returns the id of the first ancestor
@@ -28,4 +28,49 @@ export declare function findLevelAncestorId(nodeId: AnyNodeId, nodes: Record<str
28
28
  */
29
29
  export declare function resolveBuildingForLevel(levelId: AnyNodeId, nodes: Record<AnyNodeId, AnyNode>): AnyNodeId | null;
30
30
  export declare function initSpatialGridSync(): () => void;
31
+ /**
32
+ * A level's stored height moved: plane-bound walls follow the new plane,
33
+ * stair rise re-derives, and ceilings/fences re-resolve their clamp — mark
34
+ * them all so their systems rebuild. Restacking the level containers alone
35
+ * leaves their geometry stale.
36
+ */
37
+ export declare function markLevelHeightDependents(level: LevelNode, nodes: Record<string, AnyNode>, markDirty: (id: AnyNodeId) => void): void;
38
+ /**
39
+ * A deck slab's walking surface moved: stairs attached to it via
40
+ * `deckSlabId` derive their rise from that elevation, so their geometry
41
+ * (and rise-derived affordances) must rebuild.
42
+ */
43
+ export declare function markDeckAttachedStairs(slabId: string, nodes: Record<string, AnyNode>, markDirty: (id: AnyNodeId) => void): void;
44
+ /**
45
+ * Dirty every consumer of a slab's top or underside. Kept pure so committed
46
+ * scene writes and live handle previews use the same dependency boundary.
47
+ */
48
+ export declare function markSlabChangeDependents(previous: SlabNode, next: SlabNode, nodes: Record<string, AnyNode>, markDirty: (id: AnyNodeId) => void): void;
49
+ /**
50
+ * The sculpted ground moved: every node the terrain *supports* must re-elevate.
51
+ *
52
+ * The other rules in this file gate on a footprint overlapping the changed
53
+ * surface. Terrain has no such gate — a stroke rewrites a field that spans the
54
+ * whole lot, and the resolver samples it at each node's own XZ — so the sweep is
55
+ * every floor-placed node on a storey at grade, plus every wall whose explicit
56
+ * terrain infill samples that field. During a live stroke it fires per dab, but
57
+ * dirty ids coalesce in a Set until the frame systems consume them; the scene
58
+ * graph itself is still written only once on commit.
59
+ *
60
+ * Without this a sculpt silently desyncs the scene from its own ground. Nothing
61
+ * re-runs `getFloorPlacedElevation`, so the React commit that rebinds a node
62
+ * group's base Y leaves it there: a column that was resting on a hillside drops
63
+ * to the datum and stays buried under the terrain it used to stand on.
64
+ *
65
+ * Gated on `isLevelAtSiteDatum` — the same predicate `terrainSupportLift` uses to
66
+ * decide whether it drapes at all, so the two cannot disagree about which storey
67
+ * is on the ground.
68
+ */
69
+ export declare function markTerrainSupportDependents(nodes: Record<string, AnyNode>, markDirty: (id: AnyNodeId) => void): void;
70
+ /**
71
+ * A slab on `slabLevelId` was created/deleted or changed shape/placement:
72
+ * the covering bound (slab underside) over the level BELOW moved, so that
73
+ * level's plane-bound walls and clamped ceilings must rebuild.
74
+ */
75
+ export declare function markCoveringDependentsBelow(slabLevelId: string, nodes: Record<string, AnyNode>, markDirty: (id: AnyNodeId) => void): void;
31
76
  //# sourceMappingURL=spatial-grid-sync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spatial-grid-sync.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/spatial-grid-sync.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAsB,MAAM,cAAc,CAAA;AAS1E,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,CAgFhD"}
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,QAoCnC;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,7 +1,12 @@
1
+ import { getRenderableSlabPolygon } from '../../lib/slab-polygon';
2
+ import { isLevelAtSiteDatum } from '../../lib/terrain-support';
1
3
  import { nodeRegistry } from '../../registry';
4
+ import { getLevelBelow } from '../../services/storey';
5
+ import useLiveTerrain from '../../store/use-live-terrain';
2
6
  import useScene from '../../store/use-scene';
3
7
  import { getFloorPlacedFootprints } from './floor-placed-elevation';
4
8
  import { itemOverlapsPolygon, spatialGridManager, wallOverlapsPolygon, } from './spatial-grid-manager';
9
+ import { GROUND_SUPPORT_ID } from './support-host-id';
5
10
  export function resolveLevelId(node, nodes) {
6
11
  // If the node itself is a level
7
12
  if (node.type === 'level')
@@ -92,7 +97,7 @@ export function initSpatialGridSync() {
92
97
  // 2. Then subscribe to future changes
93
98
  const markDirty = (id) => store.getState().markDirty(id);
94
99
  // Subscribe to all changes
95
- const unsubscribe = store.subscribe((state, prevState) => {
100
+ const unsubscribeScene = store.subscribe((state, prevState) => {
96
101
  // Detect added nodes
97
102
  for (const [id, node] of Object.entries(state.nodes)) {
98
103
  if (!prevState.nodes[id]) {
@@ -101,6 +106,13 @@ export function initSpatialGridSync() {
101
106
  // When a slab is added, mark overlapping items/walls dirty
102
107
  if (node.type === 'slab') {
103
108
  markNodesOverlappingSlab(node, state.nodes, markDirty);
109
+ markCoveringDependentsBelow(levelId, state.nodes, markDirty);
110
+ }
111
+ // A site arriving with terrain already on it (scene load, paste,
112
+ // imported elevation data) is the same event as a stroke: ground exists
113
+ // where flat ground was assumed.
114
+ if (node.type === 'site' && node.terrain) {
115
+ markTerrainSupportDependents(state.nodes, markDirty);
104
116
  }
105
117
  }
106
118
  }
@@ -112,6 +124,12 @@ export function initSpatialGridSync() {
112
124
  // When a slab is removed, mark items/walls that were on it dirty (using current state)
113
125
  if (node.type === 'slab') {
114
126
  markNodesOverlappingSlab(node, state.nodes, markDirty);
127
+ markCoveringDependentsBelow(levelId, state.nodes, markDirty);
128
+ }
129
+ // Deleting a sculpted site drops the ground back to the datum, so its
130
+ // contents have to come down with it.
131
+ if (node.type === 'site' && node.terrain) {
132
+ markTerrainSupportDependents(state.nodes, markDirty);
115
133
  }
116
134
  }
117
135
  }
@@ -135,23 +153,186 @@ export function initSpatialGridSync() {
135
153
  }
136
154
  }
137
155
  else if (node.type === 'slab' && prev.type === 'slab') {
138
- if (node.polygon !== prev.polygon ||
156
+ const supportChanged = node.polygon !== prev.polygon ||
139
157
  node.elevation !== prev.elevation ||
140
- node.holes !== prev.holes) {
158
+ node.holes !== prev.holes;
159
+ if (supportChanged) {
141
160
  const levelId = resolveLevelId(node, state.nodes);
142
161
  spatialGridManager.handleNodeUpdated(node, levelId);
143
- // Mark nodes overlapping old polygon and new polygon as dirty
144
- markNodesOverlappingSlab(prev, state.nodes, markDirty);
145
- markNodesOverlappingSlab(node, state.nodes, markDirty);
162
+ }
163
+ markSlabChangeDependents(prev, node, state.nodes, markDirty);
164
+ }
165
+ else if (node.type === 'level' && prev.type === 'level') {
166
+ if (node.height !== prev.height) {
167
+ markLevelHeightDependents(node, state.nodes, markDirty);
168
+ }
169
+ }
170
+ else if (node.type === 'site' && prev.type === 'site') {
171
+ // Object identity, not deep equality: the store is
172
+ // immutable-by-convention, so a sculpt commit necessarily produces a new
173
+ // `terrain` object and an unrelated site edit (polygon, name) keeps the
174
+ // old one. The same reasoning `terrain-source`'s field cache is keyed on.
175
+ if (node.terrain !== prev.terrain) {
176
+ markTerrainSupportDependents(state.nodes, markDirty);
177
+ }
178
+ }
179
+ else if (node.type === 'wall' && prev.type === 'wall') {
180
+ if (node.start !== prev.start ||
181
+ node.end !== prev.end ||
182
+ node.curveOffset !== prev.curveOffset ||
183
+ node.thickness !== prev.thickness) {
184
+ // Rendered slab polygons adopt wall bands, so a wall reshape
185
+ // must reach the manager to refresh its wall map and drop the
186
+ // level's rendered-polygon cache.
187
+ spatialGridManager.handleNodeUpdated(node, resolveLevelId(node, state.nodes));
146
188
  }
147
189
  }
148
190
  }
149
191
  });
150
- return unsubscribe;
192
+ // Live terrain is deliberately not written into `useScene` per dab: doing so
193
+ // would encode the whole field, flood history, and wake every scene subscriber.
194
+ // Reuse the committed-terrain dependency sweep against the transient field
195
+ // instead. Dirty marks coalesce in their Set until the next frame, while an
196
+ // `end` notification also restores every dependent after an abandoned stroke.
197
+ const unsubscribeLiveTerrain = useLiveTerrain.subscribe(() => {
198
+ markTerrainSupportDependents(store.getState().nodes, markDirty);
199
+ });
200
+ return () => {
201
+ unsubscribeScene();
202
+ unsubscribeLiveTerrain();
203
+ };
151
204
  }
152
205
  function arraysEqual(a, b) {
153
206
  return a.length === b.length && a.every((v, i) => v === b[i]);
154
207
  }
208
+ /**
209
+ * A level's stored height moved: plane-bound walls follow the new plane,
210
+ * stair rise re-derives, and ceilings/fences re-resolve their clamp — mark
211
+ * them all so their systems rebuild. Restacking the level containers alone
212
+ * leaves their geometry stale.
213
+ */
214
+ export function markLevelHeightDependents(level, nodes, markDirty) {
215
+ for (const childId of level.children) {
216
+ const child = nodes[childId];
217
+ if (!child)
218
+ continue;
219
+ if (child.type === 'wall' ||
220
+ child.type === 'stair' ||
221
+ child.type === 'ceiling' ||
222
+ child.type === 'fence') {
223
+ markDirty(child.id);
224
+ }
225
+ }
226
+ }
227
+ /**
228
+ * A deck slab's walking surface moved: stairs attached to it via
229
+ * `deckSlabId` derive their rise from that elevation, so their geometry
230
+ * (and rise-derived affordances) must rebuild.
231
+ */
232
+ export function markDeckAttachedStairs(slabId, nodes, markDirty) {
233
+ for (const node of Object.values(nodes)) {
234
+ if (node.type === 'stair' && node.deckSlabId === slabId) {
235
+ markDirty(node.id);
236
+ }
237
+ }
238
+ }
239
+ /**
240
+ * Dirty every consumer of a slab's top or underside. Kept pure so committed
241
+ * scene writes and live handle previews use the same dependency boundary.
242
+ */
243
+ export function markSlabChangeDependents(previous, next, nodes, markDirty) {
244
+ const supportChanged = next.polygon !== previous.polygon ||
245
+ next.elevation !== previous.elevation ||
246
+ next.holes !== previous.holes;
247
+ if (supportChanged) {
248
+ markNodesOverlappingSlab(previous, nodes, markDirty);
249
+ markNodesOverlappingSlab(next, nodes, markDirty);
250
+ }
251
+ if (next.elevation !== previous.elevation) {
252
+ markDeckAttachedStairs(next.id, nodes, markDirty);
253
+ }
254
+ if (supportChanged ||
255
+ next.thickness !== previous.thickness ||
256
+ next.recessed !== previous.recessed) {
257
+ markCoveringDependentsBelow(resolveLevelId(next, nodes), nodes, markDirty);
258
+ }
259
+ }
260
+ /**
261
+ * The sculpted ground moved: every node the terrain *supports* must re-elevate.
262
+ *
263
+ * The other rules in this file gate on a footprint overlapping the changed
264
+ * surface. Terrain has no such gate — a stroke rewrites a field that spans the
265
+ * whole lot, and the resolver samples it at each node's own XZ — so the sweep is
266
+ * every floor-placed node on a storey at grade, plus every wall whose explicit
267
+ * terrain infill samples that field. During a live stroke it fires per dab, but
268
+ * dirty ids coalesce in a Set until the frame systems consume them; the scene
269
+ * graph itself is still written only once on commit.
270
+ *
271
+ * Without this a sculpt silently desyncs the scene from its own ground. Nothing
272
+ * re-runs `getFloorPlacedElevation`, so the React commit that rebinds a node
273
+ * group's base Y leaves it there: a column that was resting on a hillside drops
274
+ * to the datum and stays buried under the terrain it used to stand on.
275
+ *
276
+ * Gated on `isLevelAtSiteDatum` — the same predicate `terrainSupportLift` uses to
277
+ * decide whether it drapes at all, so the two cannot disagree about which storey
278
+ * is on the ground.
279
+ */
280
+ export function markTerrainSupportDependents(nodes, markDirty) {
281
+ const gradeLevels = new Map();
282
+ const isGrade = (levelId) => {
283
+ let cached = gradeLevels.get(levelId);
284
+ if (cached === undefined) {
285
+ cached = isLevelAtSiteDatum(nodes, levelId);
286
+ gradeLevels.set(levelId, cached);
287
+ }
288
+ return cached;
289
+ };
290
+ for (const node of Object.values(nodes)) {
291
+ if (node.type === 'slab' && node.fillToTerrain === true) {
292
+ if (isGrade(resolveLevelId(node, nodes)))
293
+ markDirty(node.id);
294
+ continue;
295
+ }
296
+ if (node.type === 'wall') {
297
+ if (node.supportSlabId !== GROUND_SUPPORT_ID && node.fillToTerrain !== true)
298
+ continue;
299
+ if (!isGrade(resolveLevelId(node, nodes)))
300
+ continue;
301
+ markDirty(node.id);
302
+ continue;
303
+ }
304
+ const floorPlaced = nodeRegistry.get(node.type)?.capabilities?.floorPlaced;
305
+ if (!floorPlaced)
306
+ continue;
307
+ if (floorPlaced.applies && !floorPlaced.applies(node))
308
+ continue;
309
+ // Items hosted on a shelf or table inherit Y from the parent group; only
310
+ // level-parented nodes read the ground. Mirrors the resolver's own gate.
311
+ const parentId = node.parentId;
312
+ const parent = parentId ? nodes[parentId] : null;
313
+ if (parent && parent.type !== 'level')
314
+ continue;
315
+ if (!isGrade(resolveLevelId(node, nodes)))
316
+ continue;
317
+ markDirty(node.id);
318
+ }
319
+ }
320
+ /**
321
+ * A slab on `slabLevelId` was created/deleted or changed shape/placement:
322
+ * the covering bound (slab underside) over the level BELOW moved, so that
323
+ * level's plane-bound walls and clamped ceilings must rebuild.
324
+ */
325
+ export function markCoveringDependentsBelow(slabLevelId, nodes, markDirty) {
326
+ const below = getLevelBelow(slabLevelId, nodes);
327
+ if (!below)
328
+ return;
329
+ for (const childId of below.children) {
330
+ const child = nodes[childId];
331
+ if (child?.type === 'wall' || child?.type === 'ceiling') {
332
+ markDirty(child.id);
333
+ }
334
+ }
335
+ }
155
336
  /**
156
337
  * Mark all floor items and walls that may be affected by a slab change as dirty.
157
338
  */
@@ -159,6 +340,24 @@ function markNodesOverlappingSlab(slab, nodes, markDirty) {
159
340
  if (slab.polygon.length < 3)
160
341
  return;
161
342
  const slabLevelId = resolveLevelId(slab, nodes);
343
+ // Walls AND floor-placed nodes follow the slab's RENDERED footprint
344
+ // (band-adopted edges reach the wall's outer face), so the dirty gate
345
+ // must test the same polygon the support queries re-evaluate — a stored
346
+ // polygon that stops short of the wall body would otherwise never
347
+ // re-elevate nodes sitting over the adopted band.
348
+ const levelWalls = [];
349
+ const siblingSlabs = [];
350
+ for (const node of Object.values(nodes)) {
351
+ if (node.type === 'wall' && resolveLevelId(node, nodes) === slabLevelId) {
352
+ levelWalls.push(node);
353
+ }
354
+ else if (node.type === 'slab' &&
355
+ node.id !== slab.id &&
356
+ resolveLevelId(node, nodes) === slabLevelId) {
357
+ siblingSlabs.push(node);
358
+ }
359
+ }
360
+ const renderedPolygon = getRenderableSlabPolygon(slab, { walls: levelWalls, siblingSlabs });
162
361
  for (const node of Object.values(nodes)) {
163
362
  if (node.type === 'wall') {
164
363
  const wall = node;
@@ -169,7 +368,7 @@ function markNodesOverlappingSlab(slab, nodes, markDirty) {
169
368
  end: wall.end,
170
369
  curveOffset: wall.curveOffset ?? 0,
171
370
  thickness: wall.thickness,
172
- }, slab.polygon)) {
371
+ }, renderedPolygon)) {
173
372
  markDirty(node.id);
174
373
  }
175
374
  continue;
@@ -195,7 +394,7 @@ function markNodesOverlappingSlab(slab, nodes, markDirty) {
195
394
  if (!position)
196
395
  continue;
197
396
  for (const footprint of getFloorPlacedFootprints(floorPlaced, node, { nodes })) {
198
- if (itemOverlapsPolygon(footprint.position ?? position, footprint.dimensions, footprint.rotation, slab.polygon, 0.01)) {
397
+ if (itemOverlapsPolygon(footprint.position ?? position, footprint.dimensions, footprint.rotation, renderedPolygon, 0.01)) {
199
398
  markDirty(node.id);
200
399
  break;
201
400
  }
@@ -0,0 +1,3 @@
1
+ /** Persisted support host meaning the level base rather than a slab. */
2
+ export declare const GROUND_SUPPORT_ID = "ground";
3
+ //# sourceMappingURL=support-host-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"support-host-id.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/support-host-id.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,eAAO,MAAM,iBAAiB,WAAW,CAAA"}
@@ -0,0 +1,2 @@
1
+ /** Persisted support host meaning the level base rather than a slab. */
2
+ export const GROUND_SUPPORT_ID = 'ground';
@@ -0,0 +1,51 @@
1
+ import type { AnyNode, FenceNode, WallNode } from '../../schema';
2
+ export type SupportSlabPatch = {
3
+ supportSlabId: string | undefined;
4
+ };
5
+ export type SupportSlabPatchOptions = {
6
+ /**
7
+ * Pointer-decided support cap (level-local Y) — see
8
+ * `FloorPlacedElevationArgs.maxElevation`. When set, the persisted host
9
+ * reproduces the CAPPED election: the elected lower slab wins over a
10
+ * deck hanging above the cap, and `GROUND_SUPPORT_ID` is stored when the
11
+ * ground is elected while capped-out slabs still overlap the footprint.
12
+ */
13
+ maxElevation?: number | null;
14
+ /** Pointer- or snap-decided host. Ground is a first-class support source. */
15
+ preferredSlabId?: string | null;
16
+ };
17
+ export declare function resolveSupportSlabPatch(node: AnyNode, nodes: Record<string, AnyNode>, options?: SupportSlabPatchOptions): SupportSlabPatch;
18
+ export declare function resolveWallSupportSlabPatch(wall: WallNode, nodes: Record<string, AnyNode>, options?: SupportSlabPatchOptions): SupportSlabPatch;
19
+ /**
20
+ * Re-elect a moved wall without discarding an explicit construction source.
21
+ *
22
+ * Move tools change only the wall's plan geometry. A persisted host therefore
23
+ * remains preferred when it still supports the new footprint; the normal
24
+ * resolver falls back when a slab no longer overlaps. Ground is also an
25
+ * explicit source, so preserving it keeps terrain-hosted walls on terrain.
26
+ */
27
+ export declare function resolveMovedWallSupportSlabPatch(wall: WallNode, nodes: Record<string, AnyNode>): SupportSlabPatch;
28
+ /** Fence-like shape the fence host election needs — plain segment, arc, or spline. */
29
+ export type FenceSupportInput = Pick<FenceNode, 'start' | 'end' | 'curveOffset' | 'path' | 'thickness' | 'parentId'>;
30
+ /**
31
+ * Support-host patch for a fence: elect the slab the fence line stands on
32
+ * and persist it as `supportSlabId` (the fence lift resolves absent =
33
+ * level floor — see `packages/nodes/src/fence/lift.ts`).
34
+ *
35
+ * The centerline (chord, sampled arc, or spline path) is turned into thin
36
+ * band footprints and run through the same candidate machinery items use.
37
+ * `options.maxElevation` is the pointer-decided cap: aiming at the floor
38
+ * under a deck elects the floor, aiming at the deck top elects the deck.
39
+ *
40
+ * Persist rule: the items ambiguity rule (stacked candidates disagree)
41
+ * PLUS the elevated-host case — a winner sitting meaningfully above the
42
+ * level floor must be persisted even when unambiguous (a balcony deck with
43
+ * nothing underneath), or the commit loses the election entirely since
44
+ * fences run no per-frame election. A single default ground slab (its top
45
+ * within `SUPPORT_ELEVATION_EPSILON` of the floor) stays unpersisted so
46
+ * plain fences keep sitting at the level base. A capped-out election (all
47
+ * overlapping slabs above the aimed-at ground) also resolves to the floor
48
+ * via the same absent-host default. Pure; exported for tests.
49
+ */
50
+ export declare function resolveFenceSupportSlabPatch(fence: FenceSupportInput, nodes: Record<string, AnyNode>, options?: SupportSlabPatchOptions): SupportSlabPatch;
51
+ //# sourceMappingURL=support-host-patch.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,164 @@
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';
5
+ export function resolveSupportSlabPatch(node, nodes, options) {
6
+ const floorPlaced = nodeRegistry.get(node.type)?.capabilities?.floorPlaced;
7
+ if (!floorPlaced || (floorPlaced.applies && !floorPlaced.applies(node))) {
8
+ return { supportSlabId: undefined };
9
+ }
10
+ const parentId = node.parentId ?? null;
11
+ const parent = parentId ? nodes[parentId] : null;
12
+ if (parent?.type !== 'level')
13
+ return { supportSlabId: undefined };
14
+ const maxElevation = options?.maxElevation;
15
+ const footprints = getFloorPlacedFootprints(floorPlaced, node, { nodes });
16
+ const candidateElevations = new Set();
17
+ let winner = null;
18
+ let cappedOut = false;
19
+ for (const footprint of footprints) {
20
+ const position = footprint.position ?? node.position;
21
+ if (!Array.isArray(position) || position.length !== 3)
22
+ continue;
23
+ const candidates = spatialGridManager.getSupportCandidatesForFootprint(parent.id, position, footprint.dimensions, footprint.rotation);
24
+ for (const candidate of candidates)
25
+ candidateElevations.add(candidate.elevation);
26
+ const support = spatialGridManager.getSlabSupportForItem(parent.id, position, footprint.dimensions, footprint.rotation, maxElevation);
27
+ if (support.slabId && (!winner || support.elevation > winner.elevation)) {
28
+ winner = { slabId: support.slabId, elevation: support.elevation };
29
+ }
30
+ if (maxElevation != null && support.slabId === null && candidates.length > 0) {
31
+ cappedOut = true;
32
+ }
33
+ }
34
+ if (winner !== null) {
35
+ return { supportSlabId: candidateElevations.size >= 2 ? winner.slabId : undefined };
36
+ }
37
+ // Capped election chose the ground while overlapping slabs sit above the
38
+ // cap: persist the ground host, or the uncapped per-frame election would
39
+ // lift the committed node back onto the deck.
40
+ return { supportSlabId: cappedOut ? GROUND_SUPPORT_ID : undefined };
41
+ }
42
+ export function resolveWallSupportSlabPatch(wall, nodes, options) {
43
+ const parent = wall.parentId ? nodes[wall.parentId] : null;
44
+ if (parent?.type !== 'level')
45
+ return { supportSlabId: undefined };
46
+ if (options?.preferredSlabId === GROUND_SUPPORT_ID) {
47
+ return { supportSlabId: GROUND_SUPPORT_ID };
48
+ }
49
+ // Winner under the pointer cap (when given): a deck hanging above the
50
+ // aimed-at surface can't capture the elected base, so a wall drawn at the
51
+ // floor underneath it persists the floor slab the user actually targeted.
52
+ const support = spatialGridManager.getSlabSupportForWall(parent.id, wall.start, wall.end, wall.curveOffset, wall.thickness, options?.preferredSlabId, options?.maxElevation);
53
+ const candidateElevations = new Set();
54
+ for (const node of Object.values(nodes)) {
55
+ if (node.type !== 'slab' || node.parentId !== parent.id)
56
+ continue;
57
+ const candidate = node;
58
+ const preferred = spatialGridManager.getSlabSupportForWall(parent.id, wall.start, wall.end, wall.curveOffset, wall.thickness, candidate.id);
59
+ if (preferred.electedSlabId === candidate.id) {
60
+ candidateElevations.add(candidate.elevation);
61
+ }
62
+ }
63
+ if (support.electedSlabId === options?.preferredSlabId) {
64
+ return { supportSlabId: support.electedSlabId ?? undefined };
65
+ }
66
+ if (options?.maxElevation != null &&
67
+ support.electedSlabId === null &&
68
+ candidateElevations.size > 0) {
69
+ return { supportSlabId: GROUND_SUPPORT_ID };
70
+ }
71
+ return {
72
+ supportSlabId: candidateElevations.size >= 2 ? (support.electedSlabId ?? undefined) : undefined,
73
+ };
74
+ }
75
+ /**
76
+ * Re-elect a moved wall without discarding an explicit construction source.
77
+ *
78
+ * Move tools change only the wall's plan geometry. A persisted host therefore
79
+ * remains preferred when it still supports the new footprint; the normal
80
+ * resolver falls back when a slab no longer overlaps. Ground is also an
81
+ * explicit source, so preserving it keeps terrain-hosted walls on terrain.
82
+ */
83
+ export function resolveMovedWallSupportSlabPatch(wall, nodes) {
84
+ return resolveWallSupportSlabPatch(wall, nodes, {
85
+ preferredSlabId: wall.supportSlabId ?? null,
86
+ });
87
+ }
88
+ /** Sample count for a curved (sagitta) fence centerline, matching the wall band test. */
89
+ const FENCE_CURVE_SUPPORT_SAMPLES = 16;
90
+ /** Fallback fence thickness (schema default) when the node carries none. */
91
+ const DEFAULT_FENCE_THICKNESS = 0.08;
92
+ /** Minimum band depth so the footprint survives the election's polygon inset. */
93
+ const MIN_FENCE_SUPPORT_BAND = 0.05;
94
+ function fenceCenterlinePoints(fence) {
95
+ if (fence.path && fence.path.length >= 2) {
96
+ return fence.path.map((point) => [point[0], point[1]]);
97
+ }
98
+ const wallLike = { start: fence.start, end: fence.end, curveOffset: fence.curveOffset ?? 0 };
99
+ if ((fence.curveOffset ?? 0) !== 0 && isCurvedWall(wallLike)) {
100
+ const points = [];
101
+ for (let i = 0; i <= FENCE_CURVE_SUPPORT_SAMPLES; i++) {
102
+ const frame = getWallCurveFrameAt(wallLike, i / FENCE_CURVE_SUPPORT_SAMPLES);
103
+ points.push([frame.point.x, frame.point.y]);
104
+ }
105
+ return points;
106
+ }
107
+ return [
108
+ [fence.start[0], fence.start[1]],
109
+ [fence.end[0], fence.end[1]],
110
+ ];
111
+ }
112
+ /**
113
+ * Support-host patch for a fence: elect the slab the fence line stands on
114
+ * and persist it as `supportSlabId` (the fence lift resolves absent =
115
+ * level floor — see `packages/nodes/src/fence/lift.ts`).
116
+ *
117
+ * The centerline (chord, sampled arc, or spline path) is turned into thin
118
+ * band footprints and run through the same candidate machinery items use.
119
+ * `options.maxElevation` is the pointer-decided cap: aiming at the floor
120
+ * under a deck elects the floor, aiming at the deck top elects the deck.
121
+ *
122
+ * Persist rule: the items ambiguity rule (stacked candidates disagree)
123
+ * PLUS the elevated-host case — a winner sitting meaningfully above the
124
+ * level floor must be persisted even when unambiguous (a balcony deck with
125
+ * nothing underneath), or the commit loses the election entirely since
126
+ * fences run no per-frame election. A single default ground slab (its top
127
+ * within `SUPPORT_ELEVATION_EPSILON` of the floor) stays unpersisted so
128
+ * plain fences keep sitting at the level base. A capped-out election (all
129
+ * overlapping slabs above the aimed-at ground) also resolves to the floor
130
+ * via the same absent-host default. Pure; exported for tests.
131
+ */
132
+ export function resolveFenceSupportSlabPatch(fence, nodes, options) {
133
+ const parent = fence.parentId ? nodes[fence.parentId] : null;
134
+ if (parent?.type !== 'level')
135
+ return { supportSlabId: undefined };
136
+ const maxElevation = options?.maxElevation;
137
+ const band = Math.max(fence.thickness ?? DEFAULT_FENCE_THICKNESS, MIN_FENCE_SUPPORT_BAND);
138
+ const points = fenceCenterlinePoints(fence);
139
+ const candidateElevations = new Set();
140
+ let winner = null;
141
+ for (let i = 1; i < points.length; i++) {
142
+ const [ax, az] = points[i - 1];
143
+ const [bx, bz] = points[i];
144
+ const length = Math.hypot(bx - ax, bz - az);
145
+ if (length < 1e-6)
146
+ continue;
147
+ const position = [(ax + bx) / 2, 0, (az + bz) / 2];
148
+ const dimensions = [length, 1, band];
149
+ // getItemFootprint's rotation convention: local +X maps to
150
+ // (cos yRot, sin yRot) in XZ, so the segment angle aligns the band.
151
+ const rotation = [0, Math.atan2(bz - az, bx - ax), 0];
152
+ const candidates = spatialGridManager.getSupportCandidatesForFootprint(parent.id, position, dimensions, rotation);
153
+ for (const candidate of candidates)
154
+ candidateElevations.add(candidate.elevation);
155
+ const support = spatialGridManager.getSlabSupportForItem(parent.id, position, dimensions, rotation, maxElevation);
156
+ if (support.slabId && (!winner || support.elevation > winner.elevation)) {
157
+ winner = { slabId: support.slabId, elevation: support.elevation };
158
+ }
159
+ }
160
+ if (winner === null)
161
+ return { supportSlabId: undefined };
162
+ const persist = candidateElevations.size >= 2 || winner.elevation > SUPPORT_ELEVATION_EPSILON;
163
+ return { supportSlabId: persist ? winner.slabId : undefined };
164
+ }