@pascal-app/core 0.8.0 → 0.9.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 (471) hide show
  1. package/dist/events/bus.d.ts +67 -13
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
  4. package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
  5. package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
  6. package/dist/hooks/scene-registry/scene-registry.d.ts +14 -23
  7. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  8. package/dist/hooks/scene-registry/scene-registry.js +59 -26
  9. package/dist/hooks/scene-registry/scene-registry.test.d.ts +2 -0
  10. package/dist/hooks/scene-registry/scene-registry.test.d.ts.map +1 -0
  11. package/dist/hooks/scene-registry/scene-registry.test.js +26 -0
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
  13. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
  14. package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
  15. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
  16. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
  17. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +366 -0
  18. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +79 -11
  19. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  20. package/dist/hooks/spatial-grid/spatial-grid-manager.js +508 -141
  21. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
  22. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  23. package/dist/hooks/spatial-grid/spatial-grid-sync.js +109 -18
  24. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts +2 -0
  25. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts.map +1 -0
  26. package/dist/hooks/spatial-grid/wall-slab-overlap.test.js +363 -0
  27. package/dist/index.d.ts +36 -8
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +33 -6
  30. package/dist/lib/level-name.d.ts +4 -0
  31. package/dist/lib/level-name.d.ts.map +1 -0
  32. package/dist/lib/level-name.js +10 -0
  33. package/dist/lib/measurement-geometry.d.ts +19 -0
  34. package/dist/lib/measurement-geometry.d.ts.map +1 -0
  35. package/dist/lib/measurement-geometry.js +232 -0
  36. package/dist/lib/measurement-geometry.test.d.ts +2 -0
  37. package/dist/lib/measurement-geometry.test.d.ts.map +1 -0
  38. package/dist/lib/measurement-geometry.test.js +102 -0
  39. package/dist/lib/polygon-geometry.d.ts +0 -1
  40. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  41. package/dist/lib/polygon-geometry.js +0 -18
  42. package/dist/lib/polygon-relations.d.ts +10 -0
  43. package/dist/lib/polygon-relations.d.ts.map +1 -0
  44. package/dist/lib/polygon-relations.js +73 -0
  45. package/dist/lib/selection-proxy.d.ts +4 -0
  46. package/dist/lib/selection-proxy.d.ts.map +1 -0
  47. package/dist/lib/selection-proxy.js +12 -0
  48. package/dist/lib/slab-polygon.d.ts +31 -2
  49. package/dist/lib/slab-polygon.d.ts.map +1 -1
  50. package/dist/lib/slab-polygon.js +525 -31
  51. package/dist/lib/slab-polygon.test.d.ts +2 -0
  52. package/dist/lib/slab-polygon.test.d.ts.map +1 -0
  53. package/dist/lib/slab-polygon.test.js +635 -0
  54. package/dist/lib/slots.d.ts +14 -0
  55. package/dist/lib/slots.d.ts.map +1 -0
  56. package/dist/lib/slots.js +26 -0
  57. package/dist/lib/space-detection-pause.test.d.ts +2 -0
  58. package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
  59. package/dist/lib/space-detection-pause.test.js +42 -0
  60. package/dist/lib/space-detection.d.ts +54 -2
  61. package/dist/lib/space-detection.d.ts.map +1 -1
  62. package/dist/lib/space-detection.js +599 -124
  63. package/dist/lib/space-detection.test.d.ts +2 -0
  64. package/dist/lib/space-detection.test.d.ts.map +1 -0
  65. package/dist/lib/space-detection.test.js +376 -0
  66. package/dist/lib/wall-distance.d.ts +60 -0
  67. package/dist/lib/wall-distance.d.ts.map +1 -0
  68. package/dist/lib/wall-distance.js +85 -0
  69. package/dist/lib/zone-quantities.d.ts +21 -0
  70. package/dist/lib/zone-quantities.d.ts.map +1 -0
  71. package/dist/lib/zone-quantities.js +183 -0
  72. package/dist/lib/zone-quantities.test.d.ts +2 -0
  73. package/dist/lib/zone-quantities.test.d.ts.map +1 -0
  74. package/dist/lib/zone-quantities.test.js +82 -0
  75. package/dist/material-library.d.ts +20 -1
  76. package/dist/material-library.d.ts.map +1 -1
  77. package/dist/material-library.js +3737 -200
  78. package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
  79. package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
  80. package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
  81. package/dist/registry/handles.d.ts +394 -0
  82. package/dist/registry/handles.d.ts.map +1 -0
  83. package/dist/registry/handles.js +24 -0
  84. package/dist/registry/index.d.ts +7 -0
  85. package/dist/registry/index.d.ts.map +1 -0
  86. package/dist/registry/index.js +4 -0
  87. package/dist/registry/registry.d.ts +136 -0
  88. package/dist/registry/registry.d.ts.map +1 -0
  89. package/dist/registry/registry.js +282 -0
  90. package/dist/registry/registry.test.d.ts +2 -0
  91. package/dist/registry/registry.test.d.ts.map +1 -0
  92. package/dist/registry/registry.test.js +196 -0
  93. package/dist/registry/relations-resolver.d.ts +42 -0
  94. package/dist/registry/relations-resolver.d.ts.map +1 -0
  95. package/dist/registry/relations-resolver.js +77 -0
  96. package/dist/registry/relations-resolver.test.d.ts +2 -0
  97. package/dist/registry/relations-resolver.test.d.ts.map +1 -0
  98. package/dist/registry/relations-resolver.test.js +183 -0
  99. package/dist/registry/scene-api.d.ts +44 -0
  100. package/dist/registry/scene-api.d.ts.map +1 -0
  101. package/dist/registry/scene-api.js +111 -0
  102. package/dist/registry/scene-api.test.d.ts +2 -0
  103. package/dist/registry/scene-api.test.d.ts.map +1 -0
  104. package/dist/registry/scene-api.test.js +183 -0
  105. package/dist/registry/subtree.d.ts +67 -0
  106. package/dist/registry/subtree.d.ts.map +1 -0
  107. package/dist/registry/subtree.js +123 -0
  108. package/dist/registry/subtree.test.d.ts +2 -0
  109. package/dist/registry/subtree.test.d.ts.map +1 -0
  110. package/dist/registry/subtree.test.js +163 -0
  111. package/dist/registry/types.d.ts +2099 -0
  112. package/dist/registry/types.d.ts.map +1 -0
  113. package/dist/registry/types.js +1 -0
  114. package/dist/schema/index.d.ts +35 -4
  115. package/dist/schema/index.d.ts.map +1 -1
  116. package/dist/schema/index.js +32 -4
  117. package/dist/schema/material.d.ts +13 -2
  118. package/dist/schema/material.d.ts.map +1 -1
  119. package/dist/schema/material.js +17 -5
  120. package/dist/schema/material.test.d.ts +2 -0
  121. package/dist/schema/material.test.d.ts.map +1 -0
  122. package/dist/schema/material.test.js +43 -0
  123. package/dist/schema/nodes/box-vent.d.ts +74 -0
  124. package/dist/schema/nodes/box-vent.d.ts.map +1 -0
  125. package/dist/schema/nodes/box-vent.js +63 -0
  126. package/dist/schema/nodes/building.d.ts +1 -1
  127. package/dist/schema/nodes/building.d.ts.map +1 -1
  128. package/dist/schema/nodes/building.js +3 -2
  129. package/dist/schema/nodes/cabinet.d.ts +534 -0
  130. package/dist/schema/nodes/cabinet.d.ts.map +1 -0
  131. package/dist/schema/nodes/cabinet.js +137 -0
  132. package/dist/schema/nodes/ceiling.d.ts +5 -2
  133. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  134. package/dist/schema/nodes/ceiling.js +5 -1
  135. package/dist/schema/nodes/chimney.d.ts +158 -0
  136. package/dist/schema/nodes/chimney.d.ts.map +1 -0
  137. package/dist/schema/nodes/chimney.js +67 -0
  138. package/dist/schema/nodes/column.d.ts +600 -7
  139. package/dist/schema/nodes/column.d.ts.map +1 -1
  140. package/dist/schema/nodes/column.js +589 -6
  141. package/dist/schema/nodes/cupola.d.ts +66 -0
  142. package/dist/schema/nodes/cupola.d.ts.map +1 -0
  143. package/dist/schema/nodes/cupola.js +28 -0
  144. package/dist/schema/nodes/door.d.ts +13 -5
  145. package/dist/schema/nodes/door.d.ts.map +1 -1
  146. package/dist/schema/nodes/door.js +12 -0
  147. package/dist/schema/nodes/dormer.d.ts +228 -0
  148. package/dist/schema/nodes/dormer.d.ts.map +1 -0
  149. package/dist/schema/nodes/dormer.js +119 -0
  150. package/dist/schema/nodes/downspout.d.ts +75 -0
  151. package/dist/schema/nodes/downspout.d.ts.map +1 -0
  152. package/dist/schema/nodes/downspout.js +63 -0
  153. package/dist/schema/nodes/duct-fitting.d.ts +91 -0
  154. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
  155. package/dist/schema/nodes/duct-fitting.js +92 -0
  156. package/dist/schema/nodes/duct-segment.d.ts +61 -0
  157. package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
  158. package/dist/schema/nodes/duct-segment.js +73 -0
  159. package/dist/schema/nodes/duct-terminal.d.ts +57 -0
  160. package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
  161. package/dist/schema/nodes/duct-terminal.js +51 -0
  162. package/dist/schema/nodes/elevator.d.ts +106 -0
  163. package/dist/schema/nodes/elevator.d.ts.map +1 -0
  164. package/dist/schema/nodes/elevator.js +57 -0
  165. package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
  166. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
  167. package/dist/schema/nodes/eyebrow-vent.js +40 -0
  168. package/dist/schema/nodes/fence.d.ts +19 -2
  169. package/dist/schema/nodes/fence.d.ts.map +1 -1
  170. package/dist/schema/nodes/fence.js +27 -2
  171. package/dist/schema/nodes/gutter.d.ts +84 -0
  172. package/dist/schema/nodes/gutter.d.ts.map +1 -0
  173. package/dist/schema/nodes/gutter.js +76 -0
  174. package/dist/schema/nodes/hvac-equipment.d.ts +63 -0
  175. package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
  176. package/dist/schema/nodes/hvac-equipment.js +55 -0
  177. package/dist/schema/nodes/item.d.ts +12 -0
  178. package/dist/schema/nodes/item.d.ts.map +1 -1
  179. package/dist/schema/nodes/item.js +20 -0
  180. package/dist/schema/nodes/level.d.ts +1 -1
  181. package/dist/schema/nodes/level.d.ts.map +1 -1
  182. package/dist/schema/nodes/level.js +4 -0
  183. package/dist/schema/nodes/lineset.d.ts +42 -0
  184. package/dist/schema/nodes/lineset.d.ts.map +1 -0
  185. package/dist/schema/nodes/lineset.js +38 -0
  186. package/dist/schema/nodes/liquid-line.d.ts +34 -0
  187. package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
  188. package/dist/schema/nodes/liquid-line.js +24 -0
  189. package/dist/schema/nodes/measurement.d.ts +306 -0
  190. package/dist/schema/nodes/measurement.d.ts.map +1 -0
  191. package/dist/schema/nodes/measurement.js +87 -0
  192. package/dist/schema/nodes/measurement.test.d.ts +2 -0
  193. package/dist/schema/nodes/measurement.test.d.ts.map +1 -0
  194. package/dist/schema/nodes/measurement.test.js +139 -0
  195. package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
  196. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
  197. package/dist/schema/nodes/pipe-fitting.js +45 -0
  198. package/dist/schema/nodes/pipe-segment.d.ts +48 -0
  199. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
  200. package/dist/schema/nodes/pipe-segment.js +36 -0
  201. package/dist/schema/nodes/pipe-trap.d.ts +43 -0
  202. package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
  203. package/dist/schema/nodes/pipe-trap.js +36 -0
  204. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts +2 -0
  205. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts.map +1 -0
  206. package/dist/schema/nodes/ridge-vent-defaults.test.js +205 -0
  207. package/dist/schema/nodes/ridge-vent.d.ts +82 -0
  208. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
  209. package/dist/schema/nodes/ridge-vent.js +248 -0
  210. package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
  211. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
  212. package/dist/schema/nodes/roof-segment-shape.js +318 -0
  213. package/dist/schema/nodes/roof-segment-shape.test.d.ts +2 -0
  214. package/dist/schema/nodes/roof-segment-shape.test.d.ts.map +1 -0
  215. package/dist/schema/nodes/roof-segment-shape.test.js +175 -0
  216. package/dist/schema/nodes/roof-segment-surface.test.d.ts +2 -0
  217. package/dist/schema/nodes/roof-segment-surface.test.d.ts.map +1 -0
  218. package/dist/schema/nodes/roof-segment-surface.test.js +48 -0
  219. package/dist/schema/nodes/roof-segment-trim.test.d.ts +2 -0
  220. package/dist/schema/nodes/roof-segment-trim.test.d.ts.map +1 -0
  221. package/dist/schema/nodes/roof-segment-trim.test.js +107 -0
  222. package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
  223. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
  224. package/dist/schema/nodes/roof-segment-walls.js +330 -0
  225. package/dist/schema/nodes/roof-segment-walls.test.d.ts +2 -0
  226. package/dist/schema/nodes/roof-segment-walls.test.d.ts.map +1 -0
  227. package/dist/schema/nodes/roof-segment-walls.test.js +92 -0
  228. package/dist/schema/nodes/roof-segment.d.ts +273 -3
  229. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  230. package/dist/schema/nodes/roof-segment.js +539 -3
  231. package/dist/schema/nodes/roof.d.ts +8 -8
  232. package/dist/schema/nodes/roof.d.ts.map +1 -1
  233. package/dist/schema/nodes/roof.js +4 -16
  234. package/dist/schema/nodes/shelf.d.ts +106 -0
  235. package/dist/schema/nodes/shelf.d.ts.map +1 -0
  236. package/dist/schema/nodes/shelf.js +90 -0
  237. package/dist/schema/nodes/site.d.ts +1 -122
  238. package/dist/schema/nodes/site.d.ts.map +1 -1
  239. package/dist/schema/nodes/site.js +2 -6
  240. package/dist/schema/nodes/skylight.d.ts +279 -0
  241. package/dist/schema/nodes/skylight.d.ts.map +1 -0
  242. package/dist/schema/nodes/skylight.js +152 -0
  243. package/dist/schema/nodes/slab.d.ts +5 -2
  244. package/dist/schema/nodes/slab.d.ts.map +1 -1
  245. package/dist/schema/nodes/slab.js +5 -1
  246. package/dist/schema/nodes/solar-panel.d.ts +117 -0
  247. package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
  248. package/dist/schema/nodes/solar-panel.js +47 -0
  249. package/dist/schema/nodes/stair-segment.d.ts +4 -4
  250. package/dist/schema/nodes/stair.d.ts +13 -12
  251. package/dist/schema/nodes/stair.d.ts.map +1 -1
  252. package/dist/schema/nodes/stair.js +3 -0
  253. package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
  254. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  255. package/dist/schema/nodes/surface-hole-metadata.js +3 -1
  256. package/dist/schema/nodes/turbine-vent.d.ts +68 -0
  257. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
  258. package/dist/schema/nodes/turbine-vent.js +41 -0
  259. package/dist/schema/nodes/wall.d.ts +245 -6
  260. package/dist/schema/nodes/wall.d.ts.map +1 -1
  261. package/dist/schema/nodes/wall.js +257 -0
  262. package/dist/schema/nodes/wall.test.d.ts +2 -0
  263. package/dist/schema/nodes/wall.test.d.ts.map +1 -0
  264. package/dist/schema/nodes/wall.test.js +208 -0
  265. package/dist/schema/nodes/window.d.ts +11 -3
  266. package/dist/schema/nodes/window.d.ts.map +1 -1
  267. package/dist/schema/nodes/window.js +12 -0
  268. package/dist/schema/nodes/zone.d.ts +2 -0
  269. package/dist/schema/nodes/zone.d.ts.map +1 -1
  270. package/dist/schema/nodes/zone.js +6 -0
  271. package/dist/schema/scene-material.d.ts +41 -0
  272. package/dist/schema/scene-material.d.ts.map +1 -0
  273. package/dist/schema/scene-material.js +9 -0
  274. package/dist/schema/types.d.ts +2850 -655
  275. package/dist/schema/types.d.ts.map +1 -1
  276. package/dist/schema/types.js +49 -0
  277. package/dist/services/alignment-anchors.d.ts +100 -0
  278. package/dist/services/alignment-anchors.d.ts.map +1 -0
  279. package/dist/services/alignment-anchors.js +312 -0
  280. package/dist/services/alignment-anchors.test.d.ts +2 -0
  281. package/dist/services/alignment-anchors.test.d.ts.map +1 -0
  282. package/dist/services/alignment-anchors.test.js +383 -0
  283. package/dist/services/alignment.d.ts +131 -0
  284. package/dist/services/alignment.d.ts.map +1 -0
  285. package/dist/services/alignment.js +171 -0
  286. package/dist/services/alignment.test.d.ts +2 -0
  287. package/dist/services/alignment.test.d.ts.map +1 -0
  288. package/dist/services/alignment.test.js +92 -0
  289. package/dist/services/drag-session.d.ts +47 -0
  290. package/dist/services/drag-session.d.ts.map +1 -0
  291. package/dist/services/drag-session.js +100 -0
  292. package/dist/services/drag-session.test.d.ts +2 -0
  293. package/dist/services/drag-session.test.d.ts.map +1 -0
  294. package/dist/services/drag-session.test.js +219 -0
  295. package/dist/services/hosting.d.ts +88 -0
  296. package/dist/services/hosting.d.ts.map +1 -0
  297. package/dist/services/hosting.js +131 -0
  298. package/dist/services/hosting.test.d.ts +2 -0
  299. package/dist/services/hosting.test.d.ts.map +1 -0
  300. package/dist/services/hosting.test.js +230 -0
  301. package/dist/services/index.d.ts +13 -0
  302. package/dist/services/index.d.ts.map +1 -0
  303. package/dist/services/index.js +12 -0
  304. package/dist/services/level-height.d.ts +28 -0
  305. package/dist/services/level-height.d.ts.map +1 -0
  306. package/dist/services/level-height.js +62 -0
  307. package/dist/services/movement.d.ts +48 -0
  308. package/dist/services/movement.d.ts.map +1 -0
  309. package/dist/services/movement.js +65 -0
  310. package/dist/services/movement.test.d.ts +2 -0
  311. package/dist/services/movement.test.d.ts.map +1 -0
  312. package/dist/services/movement.test.js +133 -0
  313. package/dist/services/opening-guides.d.ts +141 -0
  314. package/dist/services/opening-guides.d.ts.map +1 -0
  315. package/dist/services/opening-guides.js +267 -0
  316. package/dist/services/opening-guides.test.d.ts +2 -0
  317. package/dist/services/opening-guides.test.d.ts.map +1 -0
  318. package/dist/services/opening-guides.test.js +208 -0
  319. package/dist/services/port-connectivity.d.ts +115 -0
  320. package/dist/services/port-connectivity.d.ts.map +1 -0
  321. package/dist/services/port-connectivity.js +300 -0
  322. package/dist/services/port-connectivity.test.d.ts +2 -0
  323. package/dist/services/port-connectivity.test.d.ts.map +1 -0
  324. package/dist/services/port-connectivity.test.js +297 -0
  325. package/dist/services/riser-diagram.d.ts +40 -0
  326. package/dist/services/riser-diagram.d.ts.map +1 -0
  327. package/dist/services/riser-diagram.js +110 -0
  328. package/dist/services/riser-diagram.test.d.ts +2 -0
  329. package/dist/services/riser-diagram.test.d.ts.map +1 -0
  330. package/dist/services/riser-diagram.test.js +66 -0
  331. package/dist/services/single-undo-dance.test.d.ts +2 -0
  332. package/dist/services/single-undo-dance.test.d.ts.map +1 -0
  333. package/dist/services/single-undo-dance.test.js +210 -0
  334. package/dist/services/snap.d.ts +80 -0
  335. package/dist/services/snap.d.ts.map +1 -0
  336. package/dist/services/snap.js +140 -0
  337. package/dist/services/snap.test.d.ts +2 -0
  338. package/dist/services/snap.test.d.ts.map +1 -0
  339. package/dist/services/snap.test.js +137 -0
  340. package/dist/services/system-graph.d.ts +30 -0
  341. package/dist/services/system-graph.d.ts.map +1 -0
  342. package/dist/services/system-graph.js +164 -0
  343. package/dist/services/system-graph.test.d.ts +2 -0
  344. package/dist/services/system-graph.test.d.ts.map +1 -0
  345. package/dist/services/system-graph.test.js +138 -0
  346. package/dist/services/validate-dwv.d.ts +32 -0
  347. package/dist/services/validate-dwv.d.ts.map +1 -0
  348. package/dist/services/validate-dwv.js +118 -0
  349. package/dist/services/validate-dwv.test.d.ts +2 -0
  350. package/dist/services/validate-dwv.test.d.ts.map +1 -0
  351. package/dist/services/validate-dwv.test.js +109 -0
  352. package/dist/solar-panel-presets.d.ts +17 -0
  353. package/dist/solar-panel-presets.d.ts.map +1 -0
  354. package/dist/solar-panel-presets.js +43 -0
  355. package/dist/store/actions/node-actions.d.ts +14 -2
  356. package/dist/store/actions/node-actions.d.ts.map +1 -1
  357. package/dist/store/actions/node-actions.js +585 -15
  358. package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
  359. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
  360. package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
  361. package/dist/store/actions/reparent.test.d.ts +2 -0
  362. package/dist/store/actions/reparent.test.d.ts.map +1 -0
  363. package/dist/store/actions/reparent.test.js +212 -0
  364. package/dist/store/actions/ridge-vent-update.test.d.ts +2 -0
  365. package/dist/store/actions/ridge-vent-update.test.d.ts.map +1 -0
  366. package/dist/store/actions/ridge-vent-update.test.js +264 -0
  367. package/dist/store/history-control.d.ts +11 -0
  368. package/dist/store/history-control.d.ts.map +1 -1
  369. package/dist/store/history-control.js +33 -0
  370. package/dist/store/use-interactive.d.ts +39 -0
  371. package/dist/store/use-interactive.d.ts.map +1 -1
  372. package/dist/store/use-interactive.js +75 -0
  373. package/dist/store/use-live-node-overrides.d.ts +22 -0
  374. package/dist/store/use-live-node-overrides.d.ts.map +1 -0
  375. package/dist/store/use-live-node-overrides.js +59 -0
  376. package/dist/store/use-scene-dirty-tracking.test.d.ts +2 -0
  377. package/dist/store/use-scene-dirty-tracking.test.d.ts.map +1 -0
  378. package/dist/store/use-scene-dirty-tracking.test.js +68 -0
  379. package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
  380. package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
  381. package/dist/store/use-scene-elevator-migration.test.js +109 -0
  382. package/dist/store/use-scene-measurement-roundtrip.test.d.ts +2 -0
  383. package/dist/store/use-scene-measurement-roundtrip.test.d.ts.map +1 -0
  384. package/dist/store/use-scene-measurement-roundtrip.test.js +97 -0
  385. package/dist/store/use-scene-plugins.test.d.ts +2 -0
  386. package/dist/store/use-scene-plugins.test.d.ts.map +1 -0
  387. package/dist/store/use-scene-plugins.test.js +51 -0
  388. package/dist/store/use-scene-wall-slot-migration.test.d.ts +2 -0
  389. package/dist/store/use-scene-wall-slot-migration.test.d.ts.map +1 -0
  390. package/dist/store/use-scene-wall-slot-migration.test.js +210 -0
  391. package/dist/store/use-scene-window-migration.test.d.ts +2 -0
  392. package/dist/store/use-scene-window-migration.test.d.ts.map +1 -0
  393. package/dist/store/use-scene-window-migration.test.js +85 -0
  394. package/dist/store/use-scene.d.ts +28 -2
  395. package/dist/store/use-scene.d.ts.map +1 -1
  396. package/dist/store/use-scene.js +546 -49
  397. package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
  398. package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
  399. package/dist/systems/elevator/elevator-dispatch.js +57 -0
  400. package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
  401. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
  402. package/dist/systems/elevator/elevator-geometry.js +50 -0
  403. package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
  404. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
  405. package/dist/systems/elevator/elevator-opening-sync.js +218 -0
  406. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
  407. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
  408. package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
  409. package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
  410. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
  411. package/dist/systems/elevator/elevator-opening-system.js +48 -0
  412. package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
  413. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
  414. package/dist/systems/elevator/elevator-runtime-system.js +8 -0
  415. package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
  416. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
  417. package/dist/systems/elevator/elevator-runtime.js +225 -0
  418. package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
  419. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
  420. package/dist/systems/elevator/elevator-runtime.test.js +64 -0
  421. package/dist/systems/elevator/elevator-service.d.ts +19 -0
  422. package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
  423. package/dist/systems/elevator/elevator-service.js +111 -0
  424. package/dist/systems/fence/fence-centerline.d.ts +12 -0
  425. package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
  426. package/dist/systems/fence/fence-centerline.js +37 -0
  427. package/dist/systems/fence/fence-spline.d.ts +39 -0
  428. package/dist/systems/fence/fence-spline.d.ts.map +1 -0
  429. package/dist/systems/fence/fence-spline.js +196 -0
  430. package/dist/systems/fence/fence-spline.test.d.ts +2 -0
  431. package/dist/systems/fence/fence-spline.test.d.ts.map +1 -0
  432. package/dist/systems/fence/fence-spline.test.js +136 -0
  433. package/dist/systems/stair/stair-footprint.d.ts +44 -0
  434. package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
  435. package/dist/systems/stair/stair-footprint.js +178 -0
  436. package/dist/systems/stair/stair-opening-preview.d.ts +2447 -0
  437. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
  438. package/dist/systems/stair/stair-opening-preview.js +93 -0
  439. package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
  440. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
  441. package/dist/systems/stair/stair-opening-preview.test.js +80 -0
  442. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  443. package/dist/systems/stair/stair-opening-sync.js +155 -147
  444. package/dist/systems/stair/stair-opening-sync.test.js +424 -2
  445. package/dist/systems/stair/stair-opening-system.d.ts +2 -0
  446. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
  447. package/dist/systems/stair/stair-opening-system.js +104 -0
  448. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  449. package/dist/systems/wall/wall-mitering.js +38 -2
  450. package/dist/systems/wall/wall-mitering.test.d.ts +2 -0
  451. package/dist/systems/wall/wall-mitering.test.d.ts.map +1 -0
  452. package/dist/systems/wall/wall-mitering.test.js +63 -0
  453. package/dist/systems/wall/wall-move.d.ts +48 -0
  454. package/dist/systems/wall/wall-move.d.ts.map +1 -0
  455. package/dist/systems/wall/wall-move.js +193 -0
  456. package/dist/utils/clone-scene-graph.d.ts +7 -4
  457. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  458. package/dist/utils/clone-scene-graph.js +29 -7
  459. package/dist/utils/clone-scene-graph.test.d.ts +2 -0
  460. package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
  461. package/dist/utils/clone-scene-graph.test.js +62 -0
  462. package/dist/utils/heal-scene-graph.d.ts +18 -0
  463. package/dist/utils/heal-scene-graph.d.ts.map +1 -0
  464. package/dist/utils/heal-scene-graph.js +90 -0
  465. package/dist/utils/heal-scene-graph.test.d.ts +2 -0
  466. package/dist/utils/heal-scene-graph.test.d.ts.map +1 -0
  467. package/dist/utils/heal-scene-graph.test.js +88 -0
  468. package/dist/validation/validate-build-json.d.ts +44 -0
  469. package/dist/validation/validate-build-json.d.ts.map +1 -0
  470. package/dist/validation/validate-build-json.js +256 -0
  471. package/package.json +11 -4
@@ -0,0 +1,383 @@
1
+ import { beforeEach, describe, expect, test } from 'bun:test';
2
+ import { z } from 'zod';
3
+ import { nodeRegistry, registerNode } from '../registry';
4
+ import { getElevatorShaftDepth, getElevatorShaftWallThickness, getElevatorShaftWidth, } from '../systems/elevator/elevator-geometry';
5
+ import { stairFootprintAABB } from '../systems/stair/stair-footprint';
6
+ import { collectAlignmentAnchors, footprintAABB, footprintAABBFrom, movingAlignmentAnchors, movingFootprintAnchors, polygonAnchors, wallSegmentAnchors, } from './alignment-anchors';
7
+ // Minimal floor-placed def whose footprint reads `dimensions` / `rotation`
8
+ // straight off the node, so tests can drive the AABB math directly.
9
+ function floorPlacedDef(kind, applies) {
10
+ return {
11
+ kind,
12
+ schemaVersion: 1,
13
+ schema: z.object({ type: z.literal(kind) }),
14
+ category: 'utility',
15
+ defaults: () => ({}),
16
+ capabilities: {
17
+ floorPlaced: {
18
+ footprint: (n) => ({
19
+ dimensions: n.dimensions ?? [1, 1, 1],
20
+ rotation: n.rotation ?? [0, 0, 0],
21
+ }),
22
+ ...(applies ? { applies } : {}),
23
+ },
24
+ },
25
+ renderer: { kind: 'parametric', module: async () => ({ default: () => null }) },
26
+ };
27
+ }
28
+ // Mirrors the real elevator/stair definitions, which expose their plan
29
+ // footprint via the `alignmentFootprint` capability rather than a hardcoded
30
+ // branch in the anchor bridge. The glue (shaft-outset box / stair AABB) is
31
+ // reproduced here from the same core helpers production uses.
32
+ function elevatorDef() {
33
+ return {
34
+ kind: 'elevator',
35
+ schemaVersion: 1,
36
+ schema: z.object({ type: z.literal('elevator') }),
37
+ category: 'structure',
38
+ defaults: () => ({}),
39
+ capabilities: {
40
+ alignmentFootprint: (n) => {
41
+ const e = n;
42
+ const wall = getElevatorShaftWallThickness(e);
43
+ return {
44
+ shape: 'box',
45
+ dimensions: [getElevatorShaftWidth(e) + wall * 2, 1, getElevatorShaftDepth(e) + wall * 2],
46
+ rotation: [0, e.rotation ?? 0, 0],
47
+ };
48
+ },
49
+ },
50
+ renderer: { kind: 'parametric', module: async () => ({ default: () => null }) },
51
+ };
52
+ }
53
+ function stairDef() {
54
+ return {
55
+ kind: 'stair',
56
+ schemaVersion: 1,
57
+ schema: z.object({ type: z.literal('stair') }),
58
+ category: 'structure',
59
+ defaults: () => ({}),
60
+ capabilities: {
61
+ alignmentFootprint: (n, nodes) => {
62
+ const aabb = stairFootprintAABB(n, nodes);
63
+ return aabb ? { shape: 'aabb', ...aabb } : null;
64
+ },
65
+ },
66
+ renderer: { kind: 'parametric', module: async () => ({ default: () => null }) },
67
+ };
68
+ }
69
+ function plainDef(kind) {
70
+ return {
71
+ kind,
72
+ schemaVersion: 1,
73
+ schema: z.object({ type: z.literal(kind) }),
74
+ category: 'utility',
75
+ defaults: () => ({}),
76
+ capabilities: {},
77
+ renderer: { kind: 'parametric', module: async () => ({ default: () => null }) },
78
+ };
79
+ }
80
+ const node = (over) => over;
81
+ describe('footprintAABBFrom', () => {
82
+ test('unrotated box is centred at position', () => {
83
+ const aabb = footprintAABBFrom([10, 0, 20], [2, 1, 4], 0);
84
+ expect(aabb).toEqual({ minX: 9, minZ: 18, maxX: 11, maxZ: 22 });
85
+ });
86
+ test('90° rotation swaps width and depth extents', () => {
87
+ const aabb = footprintAABBFrom([0, 0, 0], [2, 1, 4], Math.PI / 2);
88
+ expect(aabb.minX).toBeCloseTo(-2, 10);
89
+ expect(aabb.maxX).toBeCloseTo(2, 10);
90
+ expect(aabb.minZ).toBeCloseTo(-1, 10);
91
+ expect(aabb.maxZ).toBeCloseTo(1, 10);
92
+ });
93
+ });
94
+ describe('footprintAABB', () => {
95
+ beforeEach(() => nodeRegistry._reset());
96
+ test('reads dimensions + rotation from a floor-placed kind', () => {
97
+ registerNode(floorPlacedDef('box'));
98
+ const aabb = footprintAABB(node({ id: 'b1', type: 'box', position: [10, 0, 20], dimensions: [2, 1, 4] }));
99
+ expect(aabb).toEqual({ minX: 9, minZ: 18, maxX: 11, maxZ: 22 });
100
+ });
101
+ test('returns null for a kind without a footprint', () => {
102
+ registerNode(plainDef('wall'));
103
+ expect(footprintAABB(node({ id: 'w1', type: 'wall', position: [0, 0, 0] }))).toBeNull();
104
+ });
105
+ test('derives an elevator footprint from its OUTER SHAFT, not the cab', () => {
106
+ // Aligns to the visible shaft outline: cab 2×4 + 0.09 m wall each side →
107
+ // 2.18 × 4.18, centred at (10, 20). The cab corners alone would sit ~9 cm
108
+ // inside the drawn edge (past the 8 cm snap), so a guide never appeared.
109
+ // The footprint comes from the elevator's `alignmentFootprint` (box) cap.
110
+ registerNode(elevatorDef());
111
+ const aabb = footprintAABB(node({ id: 'e1', type: 'elevator', position: [10, 0, 20], width: 2, depth: 4, rotation: 0 }));
112
+ expect(aabb).toEqual({ minX: 8.91, minZ: 17.91, maxX: 11.09, maxZ: 22.09 });
113
+ });
114
+ test('returns null when the kind predicate excludes the node', () => {
115
+ registerNode(floorPlacedDef('lamp', (n) => !n.attached));
116
+ expect(footprintAABB(node({ id: 'l1', type: 'lamp', position: [0, 0, 0], attached: true }))).toBeNull();
117
+ expect(footprintAABB(node({ id: 'l2', type: 'lamp', position: [0, 0, 0], attached: false }))).not.toBeNull();
118
+ });
119
+ });
120
+ describe('movingFootprintAnchors', () => {
121
+ beforeEach(() => nodeRegistry._reset());
122
+ test('relocates the footprint corners around the proposed centre (edges only, no centre anchor)', () => {
123
+ registerNode(floorPlacedDef('box'));
124
+ const anchors = movingFootprintAnchors(node({ id: 'm', type: 'box', position: [0, 0, 0], dimensions: [2, 1, 4] }), 10, 20);
125
+ // 2×4 box centred at (10, 20): corners at x∈{9,11}, z∈{18,22}.
126
+ expect(anchors).toHaveLength(4);
127
+ expect(anchors.every((a) => a.kind === 'corner')).toBe(true);
128
+ expect(new Set(anchors.map((a) => a.x))).toEqual(new Set([9, 11]));
129
+ expect(new Set(anchors.map((a) => a.z))).toEqual(new Set([18, 22]));
130
+ });
131
+ test('rotationY override drives the AABB regardless of node rotation', () => {
132
+ registerNode(floorPlacedDef('box'));
133
+ const anchors = movingFootprintAnchors(node({
134
+ id: 'm',
135
+ type: 'box',
136
+ position: [0, 0, 0],
137
+ dimensions: [2, 1, 4],
138
+ rotation: [0, 0, 0],
139
+ }), 0, 0, Math.PI / 2);
140
+ const xs = anchors.map((a) => a.x);
141
+ // Rotated 90°, the 2×4 box spans ±2 in X (its depth) rather than ±1.
142
+ expect(Math.max(...xs)).toBeCloseTo(2, 10);
143
+ expect(Math.min(...xs)).toBeCloseTo(-2, 10);
144
+ });
145
+ test('returns empty for a footprintless kind', () => {
146
+ registerNode(plainDef('wall'));
147
+ expect(movingFootprintAnchors(node({ id: 'w', type: 'wall', position: [0, 0, 0] }), 1, 1)).toEqual([]);
148
+ });
149
+ });
150
+ describe('movingAlignmentAnchors', () => {
151
+ beforeEach(() => nodeRegistry._reset());
152
+ test('relocates a straight stair by its segment-chain footprint', () => {
153
+ registerNode(stairDef());
154
+ const nodes = {
155
+ st: node({
156
+ id: 'st',
157
+ type: 'stair',
158
+ position: [0, 0, 0],
159
+ rotation: 0,
160
+ stairType: 'straight',
161
+ width: 1,
162
+ children: ['seg'],
163
+ }),
164
+ seg: node({
165
+ id: 'seg',
166
+ type: 'stair-segment',
167
+ parentId: 'st',
168
+ width: 1,
169
+ length: 3,
170
+ height: 2.5,
171
+ attachmentSide: 'front',
172
+ }),
173
+ };
174
+ const anchors = movingAlignmentAnchors(nodes.st, nodes, 10, 20, 0);
175
+ expect(anchors).toHaveLength(4);
176
+ expect(new Set(anchors.map((a) => a.x))).toEqual(new Set([9.5, 10.5]));
177
+ expect(new Set(anchors.map((a) => a.z))).toEqual(new Set([20, 23]));
178
+ });
179
+ test('rotation override drives a moving straight stair footprint', () => {
180
+ registerNode(stairDef());
181
+ const nodes = {
182
+ st: node({
183
+ id: 'st',
184
+ type: 'stair',
185
+ position: [0, 0, 0],
186
+ rotation: 0,
187
+ stairType: 'straight',
188
+ width: 1,
189
+ children: ['seg'],
190
+ }),
191
+ seg: node({
192
+ id: 'seg',
193
+ type: 'stair-segment',
194
+ parentId: 'st',
195
+ width: 1,
196
+ length: 3,
197
+ height: 2.5,
198
+ attachmentSide: 'front',
199
+ }),
200
+ };
201
+ const anchors = movingAlignmentAnchors(nodes.st, nodes, 10, 20, Math.PI / 2);
202
+ const xs = anchors.map((a) => a.x);
203
+ const zs = anchors.map((a) => a.z);
204
+ expect(Math.min(...xs)).toBeCloseTo(10, 10);
205
+ expect(Math.max(...xs)).toBeCloseTo(13, 10);
206
+ expect(Math.min(...zs)).toBeCloseTo(19.5, 10);
207
+ expect(Math.max(...zs)).toBeCloseTo(20.5, 10);
208
+ });
209
+ });
210
+ describe('wallSegmentAnchors', () => {
211
+ test('returns both endpoints as corners and the chord midpoint as center', () => {
212
+ const anchors = wallSegmentAnchors('w', [0, 0], [4, 2]);
213
+ expect(anchors).toEqual([
214
+ { nodeId: 'w', kind: 'corner', x: 0, z: 0 },
215
+ { nodeId: 'w', kind: 'corner', x: 4, z: 2 },
216
+ { nodeId: 'w', kind: 'center', x: 2, z: 1 },
217
+ ]);
218
+ });
219
+ test('adds ±thickness/2 face corners on each endpoint when thickness is given', () => {
220
+ // Horizontal wall along +X: perpendicular is ±Z, so faces sit at z = ±0.1.
221
+ const anchors = wallSegmentAnchors('w', [0, 0], [4, 0], 0.2);
222
+ expect(anchors).toEqual([
223
+ { nodeId: 'w', kind: 'corner', x: 0, z: 0 },
224
+ { nodeId: 'w', kind: 'corner', x: 4, z: 0 },
225
+ { nodeId: 'w', kind: 'center', x: 2, z: 0 },
226
+ { nodeId: 'w', kind: 'corner', x: 0, z: 0.1 },
227
+ { nodeId: 'w', kind: 'corner', x: 0, z: -0.1 },
228
+ { nodeId: 'w', kind: 'corner', x: 4, z: 0.1 },
229
+ { nodeId: 'w', kind: 'corner', x: 4, z: -0.1 },
230
+ ]);
231
+ });
232
+ test('skips face corners for zero/degenerate input', () => {
233
+ expect(wallSegmentAnchors('w', [0, 0], [4, 0], 0)).toHaveLength(3);
234
+ expect(wallSegmentAnchors('w', [1, 1], [1, 1], 0.2)).toHaveLength(3);
235
+ });
236
+ });
237
+ describe('polygonAnchors', () => {
238
+ test('returns each vertex as a corner anchor', () => {
239
+ expect(polygonAnchors('s', [
240
+ [0, 0],
241
+ [2, 0],
242
+ [2, 3],
243
+ ])).toEqual([
244
+ { nodeId: 's', kind: 'corner', x: 0, z: 0 },
245
+ { nodeId: 's', kind: 'corner', x: 2, z: 0 },
246
+ { nodeId: 's', kind: 'corner', x: 2, z: 3 },
247
+ ]);
248
+ });
249
+ });
250
+ describe('collectAlignmentAnchors', () => {
251
+ beforeEach(() => nodeRegistry._reset());
252
+ test('unions footprint corners, segment anchors and polygon vertices, excluding the moving node', () => {
253
+ registerNode(floorPlacedDef('box'));
254
+ const nodes = {
255
+ moving: node({ id: 'moving', type: 'box', position: [0, 0, 0], dimensions: [1, 1, 1] }),
256
+ box: node({ id: 'box', type: 'box', position: [5, 0, 5], dimensions: [2, 1, 2] }),
257
+ wall: node({ id: 'wall', type: 'wall', start: [0, 0], end: [4, 0] }),
258
+ slab: node({
259
+ id: 'slab',
260
+ type: 'slab',
261
+ polygon: [
262
+ [0, 0],
263
+ [2, 0],
264
+ [2, 2],
265
+ ],
266
+ }),
267
+ };
268
+ const anchors = collectAlignmentAnchors(nodes, 'moving');
269
+ const ids = anchors.map((a) => a.nodeId);
270
+ expect(ids).not.toContain('moving');
271
+ expect(ids.filter((id) => id === 'box')).toHaveLength(4); // corner anchors
272
+ expect(ids.filter((id) => id === 'wall')).toHaveLength(7); // endpoints + midpoint + 4 face corners
273
+ expect(ids.filter((id) => id === 'slab')).toHaveLength(3); // polygon vertices
274
+ });
275
+ test('levelId filter keeps only nodes resolving to that level (incl. nested)', () => {
276
+ registerNode(floorPlacedDef('box'));
277
+ registerNode(elevatorDef());
278
+ const nodes = {
279
+ b: node({ id: 'b', type: 'building' }),
280
+ L1: node({ id: 'L1', type: 'level', parentId: 'b' }),
281
+ L2: node({ id: 'L2', type: 'level', parentId: 'b' }),
282
+ moving: node({ id: 'moving', type: 'box', parentId: 'L1', position: [0, 0, 0] }),
283
+ sameFloor: node({ id: 'sameFloor', type: 'box', parentId: 'L1', position: [5, 0, 5] }),
284
+ // Item resting on `sameFloor` — resolves to L1 through the parent chain.
285
+ nested: node({ id: 'nested', type: 'box', parentId: 'sameFloor', position: [5, 0, 5] }),
286
+ otherFloor: node({ id: 'otherFloor', type: 'box', parentId: 'L2', position: [5, 0, 5] }),
287
+ // Building-scoped (parented to the building, no level ancestor) — spans
288
+ // every floor, so it stays in the pool regardless of the active level.
289
+ elevator: node({
290
+ id: 'elevator',
291
+ type: 'elevator',
292
+ parentId: 'b',
293
+ position: [9, 0, 9],
294
+ width: 1.6,
295
+ depth: 1.6,
296
+ }),
297
+ };
298
+ const ids = collectAlignmentAnchors(nodes, 'moving', 'L1').map((a) => a.nodeId);
299
+ expect(ids.filter((id) => id === 'sameFloor')).toHaveLength(4);
300
+ expect(ids.filter((id) => id === 'nested')).toHaveLength(4);
301
+ expect(ids.filter((id) => id === 'elevator')).toHaveLength(4);
302
+ expect(ids).not.toContain('otherFloor');
303
+ });
304
+ test('straight stair contributes its segment-chain footprint corners', () => {
305
+ registerNode(stairDef());
306
+ const nodes = {
307
+ st: node({
308
+ id: 'st',
309
+ type: 'stair',
310
+ position: [0, 0, 0],
311
+ rotation: 0,
312
+ stairType: 'straight',
313
+ width: 1,
314
+ children: ['seg'],
315
+ }),
316
+ // Single 1×3 flight; origin at the run start, extending +Z by length.
317
+ seg: node({
318
+ id: 'seg',
319
+ type: 'stair-segment',
320
+ parentId: 'st',
321
+ width: 1,
322
+ length: 3,
323
+ height: 2.5,
324
+ attachmentSide: 'front',
325
+ }),
326
+ };
327
+ const anchors = collectAlignmentAnchors(nodes, '').filter((a) => a.nodeId === 'st');
328
+ expect(anchors).toHaveLength(4);
329
+ expect(anchors.every((a) => a.kind === 'corner')).toBe(true);
330
+ expect(new Set(anchors.map((a) => a.x))).toEqual(new Set([-0.5, 0.5]));
331
+ expect(new Set(anchors.map((a) => a.z))).toEqual(new Set([0, 3]));
332
+ });
333
+ test('curved stair contributes its sector bounding-box corners', () => {
334
+ registerNode(stairDef());
335
+ const nodes = {
336
+ cs: node({
337
+ id: 'cs',
338
+ type: 'stair',
339
+ position: [0, 0, 0],
340
+ rotation: 0,
341
+ stairType: 'curved',
342
+ width: 1,
343
+ innerRadius: 1,
344
+ sweepAngle: Math.PI / 2,
345
+ }),
346
+ };
347
+ const anchors = collectAlignmentAnchors(nodes, '').filter((a) => a.nodeId === 'cs');
348
+ expect(anchors).toHaveLength(4);
349
+ // outerRadius = inner(1) + width(1) = 2, sweep π/2 centred on +X. Outer rim
350
+ // reaches X=2 at the bisector; min X is the inner rim's ±π/4 ends (cos45·1);
351
+ // Z spans ±(outer·sin45).
352
+ const xs = anchors.map((a) => a.x);
353
+ const zs = anchors.map((a) => a.z);
354
+ expect(Math.max(...xs)).toBeCloseTo(2, 5);
355
+ expect(Math.min(...xs)).toBeCloseTo(Math.SQRT1_2, 5);
356
+ expect(Math.max(...zs)).toBeCloseTo(Math.SQRT2, 5);
357
+ expect(Math.min(...zs)).toBeCloseTo(-Math.SQRT2, 5);
358
+ });
359
+ test('spiral stair contributes a full-circle bounding box', () => {
360
+ registerNode(stairDef());
361
+ const nodes = {
362
+ sp: node({
363
+ id: 'sp',
364
+ type: 'stair',
365
+ position: [5, 0, 5],
366
+ rotation: 0,
367
+ stairType: 'spiral',
368
+ width: 1,
369
+ innerRadius: 0.5,
370
+ sweepAngle: Math.PI * 2,
371
+ }),
372
+ };
373
+ const anchors = collectAlignmentAnchors(nodes, '').filter((a) => a.nodeId === 'sp');
374
+ expect(anchors).toHaveLength(4);
375
+ // outerRadius = inner(0.5) + width(1) = 1.5, a full revolution about (5, 5).
376
+ const xs = anchors.map((a) => a.x);
377
+ const zs = anchors.map((a) => a.z);
378
+ expect(Math.max(...xs)).toBeCloseTo(6.5, 2);
379
+ expect(Math.min(...xs)).toBeCloseTo(3.5, 2);
380
+ expect(Math.max(...zs)).toBeCloseTo(6.5, 2);
381
+ expect(Math.min(...zs)).toBeCloseTo(3.5, 2);
382
+ });
383
+ });
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Pure alignment-guide resolver — no React, no DOM, no scene access.
3
+ *
4
+ * Given a moving object's anchor points at its proposed position and a
5
+ * pool of candidate anchors from nearby static objects, returns:
6
+ * - the best per-axis matches as `Guide` rendering primitives, and
7
+ * - an optional `{ dx, dz }` snap delta the caller can apply.
8
+ *
9
+ * Anchors are 2D points on the floor plane (XZ, in world meters). The
10
+ * resolver picks at most one match per axis: the smallest |Δx| match
11
+ * snaps X; the smallest |Δz| match snaps Z. This mirrors Figma's
12
+ * behaviour — guides appear along the matched axes, regardless of how
13
+ * many neighbours could have matched.
14
+ *
15
+ * Two guides max per call keeps the visual signal sharp at the cost of
16
+ * not surfacing every possible alignment at once. Multi-guide ("this
17
+ * lines up with three things") is intentionally out of scope for v1.
18
+ */
19
+ export type AnchorKind = 'corner' | 'edge-mid' | 'center';
20
+ export type AlignmentAnchor = {
21
+ /** Owning node id — informational; resolver does not use it. */
22
+ nodeId: string;
23
+ kind: AnchorKind;
24
+ x: number;
25
+ z: number;
26
+ };
27
+ export type AlignmentGuideAxis = 'x' | 'z';
28
+ /**
29
+ * Rendering primitive — a guide line on the floor plane.
30
+ *
31
+ * `axis === 'x'`: vertical guide. Both endpoints share `coord` as their X.
32
+ * `axis === 'z'`: horizontal guide. Both endpoints share `coord` as their Z.
33
+ *
34
+ * The line spans from the matched candidate anchor to the moving anchor
35
+ * after snap. Renderers extend visually beyond the endpoints if they want
36
+ * Figma-style "infinite line" feel.
37
+ */
38
+ export type AlignmentGuide = {
39
+ axis: AlignmentGuideAxis;
40
+ coord: number;
41
+ from: {
42
+ x: number;
43
+ z: number;
44
+ };
45
+ to: {
46
+ x: number;
47
+ z: number;
48
+ };
49
+ /** The matched candidate anchor — the fixed end the moving point aligns to. */
50
+ anchor: {
51
+ x: number;
52
+ z: number;
53
+ };
54
+ movingAnchorKind: AnchorKind;
55
+ candidateAnchorKind: AnchorKind;
56
+ candidateNodeId: string;
57
+ /** Perpendicular distance between the two anchors (used by the distance pill). */
58
+ distance: number;
59
+ };
60
+ export type ResolveAlignmentInput = {
61
+ /** Anchors of the moving node, positioned at the proposed (pre-snap) location. */
62
+ moving: readonly AlignmentAnchor[];
63
+ /** Anchors from every other candidate node the caller has already filtered. */
64
+ candidates: readonly AlignmentAnchor[];
65
+ /**
66
+ * Max |Δ| (meters) for an anchor pair to count as a match. Typically
67
+ * derived from a screen-pixel budget × current units-per-pixel so the
68
+ * snap feel is zoom-invariant.
69
+ */
70
+ threshold: number;
71
+ };
72
+ export type ResolveAlignmentResult = {
73
+ guides: AlignmentGuide[];
74
+ /**
75
+ * Delta the caller should add to the moving node's planar position so
76
+ * its anchors land on the matched axes. `null` when no axis matched.
77
+ */
78
+ snap: {
79
+ dx: number;
80
+ dz: number;
81
+ } | null;
82
+ };
83
+ export type BuildingPose = {
84
+ position: readonly [number, number, number];
85
+ rotationY: number;
86
+ };
87
+ export type ResolveAlignmentInBuildingResult = {
88
+ /** Guides in WORLD coordinates. Renderers must be in a world-space group. */
89
+ guides: AlignmentGuide[];
90
+ /** Snap delta in the BUILDING-LOCAL frame, ready to add to a local position. */
91
+ snap: {
92
+ dx: number;
93
+ dz: number;
94
+ } | null;
95
+ };
96
+ /**
97
+ * Resolve alignment in WORLD space while accepting BUILDING-LOCAL anchors.
98
+ *
99
+ * Why this exists: the floor-plan grid lives in world XZ (rendered outside
100
+ * the rotated scene group), so alignment must follow the same axes —
101
+ * otherwise rotating a building drags the alignment guides off the visible
102
+ * grid and onto the rotated wall's local axes (the bug the user hit). The
103
+ * resolver itself is frame-agnostic; this wrapper just transforms anchors
104
+ * to world, resolves, then rotates the snap delta back into building-local
105
+ * so callers can add it to a local position without further math.
106
+ *
107
+ * `pose === null` → resolve in the caller's frame as-is (no transform).
108
+ */
109
+ export declare function resolveAlignmentInBuildingWorld(input: {
110
+ moving: readonly AlignmentAnchor[];
111
+ candidates: readonly AlignmentAnchor[];
112
+ threshold: number;
113
+ pose: BuildingPose | null;
114
+ }): ResolveAlignmentInBuildingResult;
115
+ export declare function resolveAlignment(input: ResolveAlignmentInput): ResolveAlignmentResult;
116
+ /**
117
+ * Produces the 9 standard anchors for an axis-aligned bounding box on the
118
+ * floor plane: 4 corners, 4 edge midpoints, 1 center. Suitable for any
119
+ * floor-plan entity whose footprint can be expressed as a bbox.
120
+ *
121
+ * Caller is responsible for computing the bbox — the resolver doesn't
122
+ * care how (per-kind dimensions, SVG getBBox(), etc.).
123
+ */
124
+ export declare function bboxAnchors(nodeId: string, minX: number, minZ: number, maxX: number, maxZ: number): AlignmentAnchor[];
125
+ /**
126
+ * The 4 corner anchors of a bbox — edges only, no edge-midpoints or center.
127
+ * Used where alignment should lock to an object's edges (left/right/front/
128
+ * back), never its centreline.
129
+ */
130
+ export declare function bboxCornerAnchors(nodeId: string, minX: number, minZ: number, maxX: number, maxZ: number): AlignmentAnchor[];
131
+ //# sourceMappingURL=alignment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alignment.d.ts","sourceRoot":"","sources":["../../src/services/alignment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAA;AAEzD,MAAM,MAAM,eAAe,GAAG;IAC5B,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,UAAU,CAAA;IAChB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,GAAG,GAAG,GAAG,CAAA;AAE1C;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,kBAAkB,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9B,EAAE,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5B,+EAA+E;IAC/E,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChC,gBAAgB,EAAE,UAAU,CAAA;IAC5B,mBAAmB,EAAE,UAAU,CAAA;IAC/B,eAAe,EAAE,MAAM,CAAA;IACvB,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,kFAAkF;IAClF,MAAM,EAAE,SAAS,eAAe,EAAE,CAAA;IAClC,+EAA+E;IAC/E,UAAU,EAAE,SAAS,eAAe,EAAE,CAAA;IACtC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB;;;OAGG;IACH,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CACxC,CAAA;AAgCD,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC3C,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,gCAAgC,GAAG;IAC7C,6EAA6E;IAC7E,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,gFAAgF;IAChF,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CACxC,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,+BAA+B,CAAC,KAAK,EAAE;IACrD,MAAM,EAAE,SAAS,eAAe,EAAE,CAAA;IAClC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAA;IACtC,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,YAAY,GAAG,IAAI,CAAA;CAC1B,GAAG,gCAAgC,CAwBnC;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,CA4FrF;AAID;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,eAAe,EAAE,CAcnB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,eAAe,EAAE,CAOnB"}