@pascal-app/core 0.9.2 → 1.0.0-beta.2

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 (339) hide show
  1. package/README.md +17 -10
  2. package/dist/events/bus.d.ts +21 -2
  3. package/dist/events/bus.d.ts.map +1 -1
  4. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +17 -1
  5. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
  6. package/dist/hooks/spatial-grid/floor-placed-elevation.js +61 -8
  7. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +116 -66
  8. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  9. package/dist/hooks/spatial-grid/spatial-grid-manager.js +319 -441
  10. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +46 -1
  11. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  12. package/dist/hooks/spatial-grid/spatial-grid-sync.js +204 -12
  13. package/dist/hooks/spatial-grid/support-host-id.d.ts +3 -0
  14. package/dist/hooks/spatial-grid/support-host-id.d.ts.map +1 -0
  15. package/dist/hooks/spatial-grid/support-host-id.js +2 -0
  16. package/dist/hooks/spatial-grid/support-host-patch.d.ts +51 -0
  17. package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -0
  18. package/dist/hooks/spatial-grid/support-host-patch.js +164 -0
  19. package/dist/index.d.ts +23 -11
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +22 -10
  22. package/dist/lib/measurement-geometry.d.ts +4 -0
  23. package/dist/lib/measurement-geometry.d.ts.map +1 -1
  24. package/dist/lib/measurement-geometry.js +27 -10
  25. package/dist/lib/slab-polygon.d.ts.map +1 -1
  26. package/dist/lib/slab-polygon.js +33 -1
  27. package/dist/lib/space-detection.d.ts +15 -4
  28. package/dist/lib/space-detection.d.ts.map +1 -1
  29. package/dist/lib/space-detection.js +232 -91
  30. package/dist/lib/terrain-brush.d.ts +163 -0
  31. package/dist/lib/terrain-brush.d.ts.map +1 -0
  32. package/dist/lib/terrain-brush.js +359 -0
  33. package/dist/lib/terrain-codec.d.ts +51 -0
  34. package/dist/lib/terrain-codec.d.ts.map +1 -0
  35. package/dist/lib/terrain-codec.js +167 -0
  36. package/dist/lib/terrain-field.d.ts +150 -0
  37. package/dist/lib/terrain-field.d.ts.map +1 -0
  38. package/dist/lib/terrain-field.js +248 -0
  39. package/dist/lib/terrain-raycast.d.ts +44 -0
  40. package/dist/lib/terrain-raycast.d.ts.map +1 -0
  41. package/dist/lib/terrain-raycast.js +188 -0
  42. package/dist/lib/terrain-source.d.ts +60 -0
  43. package/dist/lib/terrain-source.d.ts.map +1 -0
  44. package/dist/lib/terrain-source.js +90 -0
  45. package/dist/lib/terrain-support.d.ts +85 -0
  46. package/dist/lib/terrain-support.d.ts.map +1 -0
  47. package/dist/lib/terrain-support.js +218 -0
  48. package/dist/lib/zone-quantities.d.ts +1 -1
  49. package/dist/lib/zone-quantities.d.ts.map +1 -1
  50. package/dist/lib/zone-quantities.js +311 -66
  51. package/dist/material-library.d.ts +9 -1
  52. package/dist/material-library.d.ts.map +1 -1
  53. package/dist/material-library.js +56 -4
  54. package/dist/registry/handles.d.ts +16 -1
  55. package/dist/registry/handles.d.ts.map +1 -1
  56. package/dist/registry/index.d.ts +1 -1
  57. package/dist/registry/index.d.ts.map +1 -1
  58. package/dist/registry/subtree.d.ts.map +1 -1
  59. package/dist/registry/subtree.js +5 -2
  60. package/dist/registry/types.d.ts +83 -1
  61. package/dist/registry/types.d.ts.map +1 -1
  62. package/dist/schema/index.d.ts +6 -3
  63. package/dist/schema/index.d.ts.map +1 -1
  64. package/dist/schema/index.js +6 -3
  65. package/dist/schema/nodes/cabinet.d.ts +2 -0
  66. package/dist/schema/nodes/cabinet.d.ts.map +1 -1
  67. package/dist/schema/nodes/cabinet.js +4 -2
  68. package/dist/schema/nodes/ceiling.d.ts +1 -1
  69. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  70. package/dist/schema/nodes/ceiling.js +7 -1
  71. package/dist/schema/nodes/column.d.ts +1 -0
  72. package/dist/schema/nodes/column.d.ts.map +1 -1
  73. package/dist/schema/nodes/column.js +2 -0
  74. package/dist/schema/nodes/construction-dimension.d.ts +195 -0
  75. package/dist/schema/nodes/construction-dimension.d.ts.map +1 -0
  76. package/dist/schema/nodes/construction-dimension.js +151 -0
  77. package/dist/schema/nodes/door.d.ts +29 -0
  78. package/dist/schema/nodes/door.d.ts.map +1 -1
  79. package/dist/schema/nodes/door.js +20 -0
  80. package/dist/schema/nodes/duct-terminal.d.ts +1 -0
  81. package/dist/schema/nodes/duct-terminal.d.ts.map +1 -1
  82. package/dist/schema/nodes/duct-terminal.js +2 -0
  83. package/dist/schema/nodes/fence.d.ts +2 -0
  84. package/dist/schema/nodes/fence.d.ts.map +1 -1
  85. package/dist/schema/nodes/fence.js +8 -0
  86. package/dist/schema/nodes/hvac-equipment.d.ts +1 -0
  87. package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -1
  88. package/dist/schema/nodes/hvac-equipment.js +2 -0
  89. package/dist/schema/nodes/item.d.ts +1 -0
  90. package/dist/schema/nodes/item.d.ts.map +1 -1
  91. package/dist/schema/nodes/item.js +13 -0
  92. package/dist/schema/nodes/level.d.ts +2 -1
  93. package/dist/schema/nodes/level.d.ts.map +1 -1
  94. package/dist/schema/nodes/level.js +12 -33
  95. package/dist/schema/nodes/shelf.d.ts +1 -0
  96. package/dist/schema/nodes/shelf.d.ts.map +1 -1
  97. package/dist/schema/nodes/shelf.js +2 -0
  98. package/dist/schema/nodes/site.d.ts +9 -0
  99. package/dist/schema/nodes/site.d.ts.map +1 -1
  100. package/dist/schema/nodes/site.js +8 -6
  101. package/dist/schema/nodes/slab.d.ts +5 -0
  102. package/dist/schema/nodes/slab.d.ts.map +1 -1
  103. package/dist/schema/nodes/slab.js +14 -2
  104. package/dist/schema/nodes/spawn.d.ts +1 -0
  105. package/dist/schema/nodes/spawn.d.ts.map +1 -1
  106. package/dist/schema/nodes/spawn.js +2 -0
  107. package/dist/schema/nodes/stair.d.ts +3 -1
  108. package/dist/schema/nodes/stair.d.ts.map +1 -1
  109. package/dist/schema/nodes/stair.js +8 -1
  110. package/dist/schema/nodes/structural-grid.d.ts +27 -0
  111. package/dist/schema/nodes/structural-grid.d.ts.map +1 -0
  112. package/dist/schema/nodes/structural-grid.js +17 -0
  113. package/dist/schema/nodes/wall.d.ts +5 -2
  114. package/dist/schema/nodes/wall.d.ts.map +1 -1
  115. package/dist/schema/nodes/wall.js +13 -4
  116. package/dist/schema/nodes/window.d.ts +29 -0
  117. package/dist/schema/nodes/window.d.ts.map +1 -1
  118. package/dist/schema/nodes/window.js +18 -0
  119. package/dist/schema/nodes/zone.d.ts +20 -0
  120. package/dist/schema/nodes/zone.d.ts.map +1 -1
  121. package/dist/schema/nodes/zone.js +15 -0
  122. package/dist/schema/terrain.d.ts +28 -0
  123. package/dist/schema/terrain.d.ts.map +1 -0
  124. package/dist/schema/terrain.js +30 -0
  125. package/dist/schema/types.d.ts +211 -3
  126. package/dist/schema/types.d.ts.map +1 -1
  127. package/dist/schema/types.js +4 -0
  128. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts +80 -0
  129. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.d.ts.map +1 -0
  130. package/dist/services/__fixtures__/wall-plane-top-boundary-repro.js +89 -0
  131. package/dist/services/index.d.ts +2 -1
  132. package/dist/services/index.d.ts.map +1 -1
  133. package/dist/services/index.js +2 -1
  134. package/dist/services/level-height.d.ts +8 -9
  135. package/dist/services/level-height.d.ts.map +1 -1
  136. package/dist/services/level-height.js +48 -16
  137. package/dist/services/storey.d.ts +103 -0
  138. package/dist/services/storey.d.ts.map +1 -0
  139. package/dist/services/storey.js +296 -0
  140. package/dist/store/actions/node-actions.d.ts.map +1 -1
  141. package/dist/store/actions/node-actions.js +47 -3
  142. package/dist/store/history-control.d.ts +20 -0
  143. package/dist/store/history-control.d.ts.map +1 -1
  144. package/dist/store/history-control.js +110 -13
  145. package/dist/store/use-live-terrain.d.ts +65 -0
  146. package/dist/store/use-live-terrain.d.ts.map +1 -0
  147. package/dist/store/use-live-terrain.js +31 -0
  148. package/dist/store/use-live-transforms.d.ts +8 -0
  149. package/dist/store/use-live-transforms.d.ts.map +1 -1
  150. package/dist/store/use-scene.d.ts +31 -2
  151. package/dist/store/use-scene.d.ts.map +1 -1
  152. package/dist/store/use-scene.js +638 -8
  153. package/dist/systems/elevator/elevator-opening-system.d.ts +1 -0
  154. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -1
  155. package/dist/systems/elevator/elevator-opening-system.js +27 -21
  156. package/dist/systems/elevator/elevator-service.d.ts +0 -2
  157. package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
  158. package/dist/systems/elevator/elevator-service.js +2 -24
  159. package/dist/systems/slab/slab-placement.d.ts +11 -0
  160. package/dist/systems/slab/slab-placement.d.ts.map +1 -0
  161. package/dist/systems/slab/slab-placement.js +14 -0
  162. package/dist/systems/slab/slab-support.d.ts +150 -0
  163. package/dist/systems/slab/slab-support.d.ts.map +1 -0
  164. package/dist/systems/slab/slab-support.js +575 -0
  165. package/dist/systems/stair/stair-opening-preview.d.ts +1060 -935
  166. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  167. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  168. package/dist/systems/stair/stair-opening-sync.js +18 -15
  169. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
  170. package/dist/systems/stair/stair-opening-system.js +32 -4
  171. package/dist/systems/stair/stair-rise.d.ts +22 -0
  172. package/dist/systems/stair/stair-rise.d.ts.map +1 -0
  173. package/dist/systems/stair/stair-rise.js +79 -0
  174. package/dist/systems/wall/wall-curve.d.ts +10 -0
  175. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  176. package/dist/systems/wall/wall-curve.js +1 -1
  177. package/dist/systems/wall/wall-mitering.js +6 -6
  178. package/dist/systems/wall/wall-top.d.ts +40 -0
  179. package/dist/systems/wall/wall-top.d.ts.map +1 -0
  180. package/dist/systems/wall/wall-top.js +46 -0
  181. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  182. package/dist/utils/clone-scene-graph.js +34 -3
  183. package/dist/utils/heal-scene-graph.d.ts +1 -1
  184. package/dist/utils/heal-scene-graph.d.ts.map +1 -1
  185. package/dist/utils/heal-scene-graph.js +25 -9
  186. package/dist/validation/validate-build-json.d.ts +2 -0
  187. package/dist/validation/validate-build-json.d.ts.map +1 -1
  188. package/dist/validation/validate-build-json.js +59 -3
  189. package/package.json +2 -2
  190. package/dist/hooks/scene-registry/scene-registry.test.d.ts +0 -2
  191. package/dist/hooks/scene-registry/scene-registry.test.d.ts.map +0 -1
  192. package/dist/hooks/scene-registry/scene-registry.test.js +0 -26
  193. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +0 -2
  194. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +0 -1
  195. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +0 -366
  196. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts +0 -2
  197. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts.map +0 -1
  198. package/dist/hooks/spatial-grid/wall-slab-overlap.test.js +0 -363
  199. package/dist/lib/measurement-geometry.test.d.ts +0 -2
  200. package/dist/lib/measurement-geometry.test.d.ts.map +0 -1
  201. package/dist/lib/measurement-geometry.test.js +0 -102
  202. package/dist/lib/slab-polygon.test.d.ts +0 -2
  203. package/dist/lib/slab-polygon.test.d.ts.map +0 -1
  204. package/dist/lib/slab-polygon.test.js +0 -635
  205. package/dist/lib/space-detection-pause.test.d.ts +0 -2
  206. package/dist/lib/space-detection-pause.test.d.ts.map +0 -1
  207. package/dist/lib/space-detection-pause.test.js +0 -42
  208. package/dist/lib/space-detection.test.d.ts +0 -2
  209. package/dist/lib/space-detection.test.d.ts.map +0 -1
  210. package/dist/lib/space-detection.test.js +0 -376
  211. package/dist/lib/zone-quantities.test.d.ts +0 -2
  212. package/dist/lib/zone-quantities.test.d.ts.map +0 -1
  213. package/dist/lib/zone-quantities.test.js +0 -82
  214. package/dist/registry/registry.test.d.ts +0 -2
  215. package/dist/registry/registry.test.d.ts.map +0 -1
  216. package/dist/registry/registry.test.js +0 -196
  217. package/dist/registry/relations-resolver.test.d.ts +0 -2
  218. package/dist/registry/relations-resolver.test.d.ts.map +0 -1
  219. package/dist/registry/relations-resolver.test.js +0 -183
  220. package/dist/registry/scene-api.test.d.ts +0 -2
  221. package/dist/registry/scene-api.test.d.ts.map +0 -1
  222. package/dist/registry/scene-api.test.js +0 -183
  223. package/dist/registry/subtree.test.d.ts +0 -2
  224. package/dist/registry/subtree.test.d.ts.map +0 -1
  225. package/dist/registry/subtree.test.js +0 -163
  226. package/dist/schema/asset-url.test.d.ts +0 -2
  227. package/dist/schema/asset-url.test.d.ts.map +0 -1
  228. package/dist/schema/asset-url.test.js +0 -134
  229. package/dist/schema/material.test.d.ts +0 -2
  230. package/dist/schema/material.test.d.ts.map +0 -1
  231. package/dist/schema/material.test.js +0 -43
  232. package/dist/schema/nodes/measurement.test.d.ts +0 -2
  233. package/dist/schema/nodes/measurement.test.d.ts.map +0 -1
  234. package/dist/schema/nodes/measurement.test.js +0 -139
  235. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts +0 -2
  236. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts.map +0 -1
  237. package/dist/schema/nodes/ridge-vent-defaults.test.js +0 -205
  238. package/dist/schema/nodes/roof-segment-shape.test.d.ts +0 -2
  239. package/dist/schema/nodes/roof-segment-shape.test.d.ts.map +0 -1
  240. package/dist/schema/nodes/roof-segment-shape.test.js +0 -175
  241. package/dist/schema/nodes/roof-segment-surface.test.d.ts +0 -2
  242. package/dist/schema/nodes/roof-segment-surface.test.d.ts.map +0 -1
  243. package/dist/schema/nodes/roof-segment-surface.test.js +0 -48
  244. package/dist/schema/nodes/roof-segment-trim.test.d.ts +0 -2
  245. package/dist/schema/nodes/roof-segment-trim.test.d.ts.map +0 -1
  246. package/dist/schema/nodes/roof-segment-trim.test.js +0 -107
  247. package/dist/schema/nodes/roof-segment-walls.test.d.ts +0 -2
  248. package/dist/schema/nodes/roof-segment-walls.test.d.ts.map +0 -1
  249. package/dist/schema/nodes/roof-segment-walls.test.js +0 -92
  250. package/dist/schema/nodes/wall.test.d.ts +0 -2
  251. package/dist/schema/nodes/wall.test.d.ts.map +0 -1
  252. package/dist/schema/nodes/wall.test.js +0 -208
  253. package/dist/services/alignment-anchors.test.d.ts +0 -2
  254. package/dist/services/alignment-anchors.test.d.ts.map +0 -1
  255. package/dist/services/alignment-anchors.test.js +0 -383
  256. package/dist/services/alignment.test.d.ts +0 -2
  257. package/dist/services/alignment.test.d.ts.map +0 -1
  258. package/dist/services/alignment.test.js +0 -92
  259. package/dist/services/drag-session.test.d.ts +0 -2
  260. package/dist/services/drag-session.test.d.ts.map +0 -1
  261. package/dist/services/drag-session.test.js +0 -219
  262. package/dist/services/hosting.test.d.ts +0 -2
  263. package/dist/services/hosting.test.d.ts.map +0 -1
  264. package/dist/services/hosting.test.js +0 -230
  265. package/dist/services/movement.test.d.ts +0 -2
  266. package/dist/services/movement.test.d.ts.map +0 -1
  267. package/dist/services/movement.test.js +0 -133
  268. package/dist/services/opening-guides.test.d.ts +0 -2
  269. package/dist/services/opening-guides.test.d.ts.map +0 -1
  270. package/dist/services/opening-guides.test.js +0 -208
  271. package/dist/services/port-connectivity.test.d.ts +0 -2
  272. package/dist/services/port-connectivity.test.d.ts.map +0 -1
  273. package/dist/services/port-connectivity.test.js +0 -297
  274. package/dist/services/riser-diagram.test.d.ts +0 -2
  275. package/dist/services/riser-diagram.test.d.ts.map +0 -1
  276. package/dist/services/riser-diagram.test.js +0 -66
  277. package/dist/services/single-undo-dance.test.d.ts +0 -2
  278. package/dist/services/single-undo-dance.test.d.ts.map +0 -1
  279. package/dist/services/single-undo-dance.test.js +0 -210
  280. package/dist/services/snap.test.d.ts +0 -2
  281. package/dist/services/snap.test.d.ts.map +0 -1
  282. package/dist/services/snap.test.js +0 -137
  283. package/dist/services/system-graph.test.d.ts +0 -2
  284. package/dist/services/system-graph.test.d.ts.map +0 -1
  285. package/dist/services/system-graph.test.js +0 -138
  286. package/dist/services/validate-dwv.test.d.ts +0 -2
  287. package/dist/services/validate-dwv.test.d.ts.map +0 -1
  288. package/dist/services/validate-dwv.test.js +0 -109
  289. package/dist/store/actions/node-mutation-sanitize.test.d.ts +0 -2
  290. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +0 -1
  291. package/dist/store/actions/node-mutation-sanitize.test.js +0 -147
  292. package/dist/store/actions/reparent.test.d.ts +0 -2
  293. package/dist/store/actions/reparent.test.d.ts.map +0 -1
  294. package/dist/store/actions/reparent.test.js +0 -212
  295. package/dist/store/actions/ridge-vent-update.test.d.ts +0 -2
  296. package/dist/store/actions/ridge-vent-update.test.d.ts.map +0 -1
  297. package/dist/store/actions/ridge-vent-update.test.js +0 -264
  298. package/dist/store/use-scene-dirty-tracking.test.d.ts +0 -2
  299. package/dist/store/use-scene-dirty-tracking.test.d.ts.map +0 -1
  300. package/dist/store/use-scene-dirty-tracking.test.js +0 -68
  301. package/dist/store/use-scene-elevator-migration.test.d.ts +0 -2
  302. package/dist/store/use-scene-elevator-migration.test.d.ts.map +0 -1
  303. package/dist/store/use-scene-elevator-migration.test.js +0 -109
  304. package/dist/store/use-scene-measurement-roundtrip.test.d.ts +0 -2
  305. package/dist/store/use-scene-measurement-roundtrip.test.d.ts.map +0 -1
  306. package/dist/store/use-scene-measurement-roundtrip.test.js +0 -97
  307. package/dist/store/use-scene-plugins.test.d.ts +0 -2
  308. package/dist/store/use-scene-plugins.test.d.ts.map +0 -1
  309. package/dist/store/use-scene-plugins.test.js +0 -51
  310. package/dist/store/use-scene-wall-slot-migration.test.d.ts +0 -2
  311. package/dist/store/use-scene-wall-slot-migration.test.d.ts.map +0 -1
  312. package/dist/store/use-scene-wall-slot-migration.test.js +0 -210
  313. package/dist/store/use-scene-window-migration.test.d.ts +0 -2
  314. package/dist/store/use-scene-window-migration.test.d.ts.map +0 -1
  315. package/dist/store/use-scene-window-migration.test.js +0 -85
  316. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +0 -2
  317. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +0 -1
  318. package/dist/systems/elevator/elevator-opening-sync.test.js +0 -146
  319. package/dist/systems/elevator/elevator-runtime.test.d.ts +0 -2
  320. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +0 -1
  321. package/dist/systems/elevator/elevator-runtime.test.js +0 -64
  322. package/dist/systems/fence/fence-spline.test.d.ts +0 -2
  323. package/dist/systems/fence/fence-spline.test.d.ts.map +0 -1
  324. package/dist/systems/fence/fence-spline.test.js +0 -136
  325. package/dist/systems/stair/stair-opening-preview.test.d.ts +0 -2
  326. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +0 -1
  327. package/dist/systems/stair/stair-opening-preview.test.js +0 -80
  328. package/dist/systems/stair/stair-opening-sync.test.d.ts +0 -2
  329. package/dist/systems/stair/stair-opening-sync.test.d.ts.map +0 -1
  330. package/dist/systems/stair/stair-opening-sync.test.js +0 -485
  331. package/dist/systems/wall/wall-mitering.test.d.ts +0 -2
  332. package/dist/systems/wall/wall-mitering.test.d.ts.map +0 -1
  333. package/dist/systems/wall/wall-mitering.test.js +0 -63
  334. package/dist/utils/clone-scene-graph.test.d.ts +0 -2
  335. package/dist/utils/clone-scene-graph.test.d.ts.map +0 -1
  336. package/dist/utils/clone-scene-graph.test.js +0 -62
  337. package/dist/utils/heal-scene-graph.test.d.ts +0 -2
  338. package/dist/utils/heal-scene-graph.test.d.ts.map +0 -1
  339. package/dist/utils/heal-scene-graph.test.js +0 -88
@@ -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":"AAEA,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,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,8 +1,12 @@
1
1
  import { getRenderableSlabPolygon } from '../../lib/slab-polygon';
2
+ import { isLevelAtSiteDatum, isLevelBaseConsumer } from '../../lib/terrain-support';
2
3
  import { nodeRegistry } from '../../registry';
4
+ import { getLevelBelow } from '../../services/storey';
5
+ import useLiveTerrain from '../../store/use-live-terrain';
3
6
  import useScene from '../../store/use-scene';
4
7
  import { getFloorPlacedFootprints } from './floor-placed-elevation';
5
8
  import { itemOverlapsPolygon, spatialGridManager, wallOverlapsPolygon, } from './spatial-grid-manager';
9
+ import { GROUND_SUPPORT_ID } from './support-host-id';
6
10
  export function resolveLevelId(node, nodes) {
7
11
  // If the node itself is a level
8
12
  if (node.type === 'level')
@@ -93,7 +97,7 @@ export function initSpatialGridSync() {
93
97
  // 2. Then subscribe to future changes
94
98
  const markDirty = (id) => store.getState().markDirty(id);
95
99
  // Subscribe to all changes
96
- const unsubscribe = store.subscribe((state, prevState) => {
100
+ const unsubscribeScene = store.subscribe((state, prevState) => {
97
101
  // Detect added nodes
98
102
  for (const [id, node] of Object.entries(state.nodes)) {
99
103
  if (!prevState.nodes[id]) {
@@ -102,6 +106,13 @@ export function initSpatialGridSync() {
102
106
  // When a slab is added, mark overlapping items/walls dirty
103
107
  if (node.type === 'slab') {
104
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);
105
116
  }
106
117
  }
107
118
  }
@@ -113,6 +124,12 @@ export function initSpatialGridSync() {
113
124
  // When a slab is removed, mark items/walls that were on it dirty (using current state)
114
125
  if (node.type === 'slab') {
115
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);
116
133
  }
117
134
  }
118
135
  }
@@ -136,23 +153,197 @@ export function initSpatialGridSync() {
136
153
  }
137
154
  }
138
155
  else if (node.type === 'slab' && prev.type === 'slab') {
139
- if (node.polygon !== prev.polygon ||
156
+ const supportChanged = node.polygon !== prev.polygon ||
140
157
  node.elevation !== prev.elevation ||
141
- node.holes !== prev.holes) {
158
+ node.holes !== prev.holes;
159
+ if (supportChanged) {
142
160
  const levelId = resolveLevelId(node, state.nodes);
143
161
  spatialGridManager.handleNodeUpdated(node, levelId);
144
- // Mark nodes overlapping old polygon and new polygon as dirty
145
- markNodesOverlappingSlab(prev, state.nodes, markDirty);
146
- 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));
147
188
  }
148
189
  }
149
190
  }
150
191
  });
151
- 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
+ };
152
204
  }
153
205
  function arraysEqual(a, b) {
154
206
  return a.length === b.length && a.every((v, i) => v === b[i]);
155
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
+ // A kind whose geometry builder resolved its own origin from the ground
307
+ // (`ctx.levelBaseAt`) has that ground baked into its meshes, so it has to
308
+ // rebuild even though nothing about the node changed. This is the
309
+ // invalidation half of the builder seam: without it a fence keeps the
310
+ // hillside it was built on and floats after the next stroke. Kinds that
311
+ // are `floorPlaced` need no entry here — the sweep below already covers
312
+ // them, through a mesh transform rather than a rebuild.
313
+ if (isLevelBaseConsumer(node.type) && isGrade(resolveLevelId(node, nodes))) {
314
+ markDirty(node.id);
315
+ }
316
+ continue;
317
+ }
318
+ if (floorPlaced.applies && !floorPlaced.applies(node))
319
+ continue;
320
+ // Items hosted on a shelf or table inherit Y from the parent group; only
321
+ // level-parented nodes read the ground. Mirrors the resolver's own gate.
322
+ const parentId = node.parentId;
323
+ const parent = parentId ? nodes[parentId] : null;
324
+ if (parent && parent.type !== 'level')
325
+ continue;
326
+ if (!isGrade(resolveLevelId(node, nodes)))
327
+ continue;
328
+ markDirty(node.id);
329
+ }
330
+ }
331
+ /**
332
+ * A slab on `slabLevelId` was created/deleted or changed shape/placement:
333
+ * the covering bound (slab underside) over the level BELOW moved, so that
334
+ * level's plane-bound walls and clamped ceilings must rebuild.
335
+ */
336
+ export function markCoveringDependentsBelow(slabLevelId, nodes, markDirty) {
337
+ const below = getLevelBelow(slabLevelId, nodes);
338
+ if (!below)
339
+ return;
340
+ for (const childId of below.children) {
341
+ const child = nodes[childId];
342
+ if (child?.type === 'wall' || child?.type === 'ceiling') {
343
+ markDirty(child.id);
344
+ }
345
+ }
346
+ }
156
347
  /**
157
348
  * Mark all floor items and walls that may be affected by a slab change as dirty.
158
349
  */
@@ -160,10 +351,11 @@ function markNodesOverlappingSlab(slab, nodes, markDirty) {
160
351
  if (slab.polygon.length < 3)
161
352
  return;
162
353
  const slabLevelId = resolveLevelId(slab, nodes);
163
- // Walls follow the slab's RENDERED footprint (band-adopted edges reach
164
- // the wall's outer face), so the dirty gate must test the same polygon
165
- // `getSlabElevationForWall` will re-evaluate — a stored polygon that
166
- // stops short of the wall body would otherwise never re-elevate it.
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.
167
359
  const levelWalls = [];
168
360
  const siblingSlabs = [];
169
361
  for (const node of Object.values(nodes)) {
@@ -213,7 +405,7 @@ function markNodesOverlappingSlab(slab, nodes, markDirty) {
213
405
  if (!position)
214
406
  continue;
215
407
  for (const footprint of getFloorPlacedFootprints(floorPlaced, node, { nodes })) {
216
- if (itemOverlapsPolygon(footprint.position ?? position, footprint.dimensions, footprint.rotation, slab.polygon, 0.01)) {
408
+ if (itemOverlapsPolygon(footprint.position ?? position, footprint.dimensions, footprint.rotation, renderedPolygon, 0.01)) {
217
409
  markDirty(node.id);
218
410
  break;
219
411
  }
@@ -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
+ }