@pascal-app/core 1.0.6 → 1.0.7

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 (940) hide show
  1. package/dist/agent-operations/__fixtures__/add-object-cases.d.ts +31 -0
  2. package/dist/agent-operations/__fixtures__/add-object-cases.d.ts.map +1 -1
  3. package/dist/agent-operations/__fixtures__/build-room-cases.d.ts.map +1 -1
  4. package/dist/agent-operations/__fixtures__/build-room-cases.js +27 -0
  5. package/dist/agent-operations/__fixtures__/cases.d.ts.map +1 -1
  6. package/dist/agent-operations/__fixtures__/cases.js +19 -11
  7. package/dist/agent-operations/__fixtures__/design-brief-cases.d.ts +25 -2
  8. package/dist/agent-operations/__fixtures__/design-brief-cases.d.ts.map +1 -1
  9. package/dist/agent-operations/__fixtures__/design-brief-cases.js +509 -66
  10. package/dist/agent-operations/__fixtures__/dressing-scenes.d.ts +30 -0
  11. package/dist/agent-operations/__fixtures__/dressing-scenes.d.ts.map +1 -0
  12. package/dist/agent-operations/__fixtures__/dressing-scenes.js +82 -0
  13. package/dist/agent-operations/__fixtures__/native-materials.d.ts +407 -9
  14. package/dist/agent-operations/__fixtures__/native-materials.d.ts.map +1 -1
  15. package/dist/agent-operations/__fixtures__/notation-cases.d.ts.map +1 -1
  16. package/dist/agent-operations/__fixtures__/notation-cases.js +14 -0
  17. package/dist/agent-operations/__fixtures__/roof-bearing-cases.d.ts +7253 -4053
  18. package/dist/agent-operations/__fixtures__/roof-bearing-cases.d.ts.map +1 -1
  19. package/dist/agent-operations/__fixtures__/room-cases.d.ts +1 -0
  20. package/dist/agent-operations/__fixtures__/room-cases.d.ts.map +1 -1
  21. package/dist/agent-operations/__fixtures__/room-cases.js +57 -1
  22. package/dist/agent-operations/__fixtures__/room-check-scenes.d.ts +51 -0
  23. package/dist/agent-operations/__fixtures__/room-check-scenes.d.ts.map +1 -0
  24. package/dist/agent-operations/__fixtures__/room-check-scenes.js +121 -0
  25. package/dist/agent-operations/__fixtures__/scene-check-cases.d.ts.map +1 -1
  26. package/dist/agent-operations/__fixtures__/scene-check-cases.js +152 -18
  27. package/dist/agent-operations/__fixtures__/search-assets-cases.d.ts.map +1 -1
  28. package/dist/agent-operations/__fixtures__/search-assets-cases.js +91 -0
  29. package/dist/agent-operations/__fixtures__/site-cases.d.ts +40 -1
  30. package/dist/agent-operations/__fixtures__/site-cases.d.ts.map +1 -1
  31. package/dist/agent-operations/__fixtures__/structure-cases.d.ts +3 -0
  32. package/dist/agent-operations/__fixtures__/structure-cases.d.ts.map +1 -1
  33. package/dist/agent-operations/__fixtures__/structure-cases.js +199 -18
  34. package/dist/agent-operations/__fixtures__/unit-cases.d.ts +6 -0
  35. package/dist/agent-operations/__fixtures__/unit-cases.d.ts.map +1 -0
  36. package/dist/agent-operations/__fixtures__/unit-cases.js +266 -0
  37. package/dist/agent-operations/__fixtures__/verify-scene-cases.d.ts +109 -73
  38. package/dist/agent-operations/__fixtures__/verify-scene-cases.d.ts.map +1 -1
  39. package/dist/agent-operations/add-column.d.ts.map +1 -1
  40. package/dist/agent-operations/add-column.js +32 -3
  41. package/dist/agent-operations/add-fence.d.ts.map +1 -1
  42. package/dist/agent-operations/add-fence.js +2 -1
  43. package/dist/agent-operations/add-level.d.ts.map +1 -1
  44. package/dist/agent-operations/add-level.js +4 -4
  45. package/dist/agent-operations/add-object.d.ts +32 -6
  46. package/dist/agent-operations/add-object.d.ts.map +1 -1
  47. package/dist/agent-operations/add-object.js +59 -12
  48. package/dist/agent-operations/add-wall.d.ts +3 -8
  49. package/dist/agent-operations/add-wall.d.ts.map +1 -1
  50. package/dist/agent-operations/add-wall.js +31 -8
  51. package/dist/agent-operations/apply-changes.d.ts.map +1 -1
  52. package/dist/agent-operations/apply-changes.js +2 -1
  53. package/dist/agent-operations/asset-catalog.d.ts +63 -0
  54. package/dist/agent-operations/asset-catalog.d.ts.map +1 -0
  55. package/dist/agent-operations/asset-catalog.js +182 -0
  56. package/dist/agent-operations/asset-views.d.ts +4 -2
  57. package/dist/agent-operations/asset-views.d.ts.map +1 -1
  58. package/dist/agent-operations/asset-views.js +1 -3
  59. package/dist/agent-operations/author-scene-checks.d.ts +38 -0
  60. package/dist/agent-operations/author-scene-checks.d.ts.map +1 -0
  61. package/dist/agent-operations/author-scene-checks.js +266 -0
  62. package/dist/agent-operations/author-scene.d.ts +54 -20
  63. package/dist/agent-operations/author-scene.d.ts.map +1 -1
  64. package/dist/agent-operations/author-scene.js +1164 -196
  65. package/dist/agent-operations/authored-scene-merge.d.ts.map +1 -1
  66. package/dist/agent-operations/authored-scene-merge.js +61 -3
  67. package/dist/agent-operations/batch.d.ts +1 -1
  68. package/dist/agent-operations/batch.d.ts.map +1 -1
  69. package/dist/agent-operations/batch.js +38 -39
  70. package/dist/agent-operations/ceiling-object-observations.d.ts.map +1 -1
  71. package/dist/agent-operations/ceiling-object-observations.js +5 -4
  72. package/dist/agent-operations/circulation-geometry.d.ts +14 -0
  73. package/dist/agent-operations/circulation-geometry.d.ts.map +1 -0
  74. package/dist/agent-operations/circulation-geometry.js +151 -0
  75. package/dist/agent-operations/circulation-graph.d.ts +72 -0
  76. package/dist/agent-operations/circulation-graph.d.ts.map +1 -0
  77. package/dist/agent-operations/circulation-graph.js +713 -0
  78. package/dist/agent-operations/circulation-non-spaces.d.ts +10 -0
  79. package/dist/agent-operations/circulation-non-spaces.d.ts.map +1 -0
  80. package/dist/agent-operations/circulation-non-spaces.js +88 -0
  81. package/dist/agent-operations/circulation-spaces.d.ts +5880 -0
  82. package/dist/agent-operations/circulation-spaces.d.ts.map +1 -0
  83. package/dist/agent-operations/circulation-spaces.js +368 -0
  84. package/dist/agent-operations/collections.d.ts.map +1 -1
  85. package/dist/agent-operations/collections.js +9 -7
  86. package/dist/agent-operations/corner-window.d.ts.map +1 -1
  87. package/dist/agent-operations/corner-window.js +2 -4
  88. package/dist/agent-operations/create-roof.d.ts.map +1 -1
  89. package/dist/agent-operations/create-roof.js +5 -7
  90. package/dist/agent-operations/create-room.d.ts +4 -1
  91. package/dist/agent-operations/create-room.d.ts.map +1 -1
  92. package/dist/agent-operations/create-room.js +7 -4
  93. package/dist/agent-operations/create-stair.js +2 -2
  94. package/dist/agent-operations/design-brief.d.ts +13 -7
  95. package/dist/agent-operations/design-brief.d.ts.map +1 -1
  96. package/dist/agent-operations/design-brief.js +172 -92
  97. package/dist/agent-operations/door-clearance.d.ts +68 -8
  98. package/dist/agent-operations/door-clearance.d.ts.map +1 -1
  99. package/dist/agent-operations/door-clearance.js +127 -20
  100. package/dist/agent-operations/dress-targets.d.ts +23 -0
  101. package/dist/agent-operations/dress-targets.d.ts.map +1 -0
  102. package/dist/agent-operations/dress-targets.js +34 -0
  103. package/dist/agent-operations/duplicate-node.d.ts +14 -0
  104. package/dist/agent-operations/duplicate-node.d.ts.map +1 -0
  105. package/dist/agent-operations/duplicate-node.js +173 -0
  106. package/dist/agent-operations/find-by-type.d.ts.map +1 -1
  107. package/dist/agent-operations/find-by-type.js +3 -0
  108. package/dist/agent-operations/furnish-room.d.ts +2 -0
  109. package/dist/agent-operations/furnish-room.d.ts.map +1 -1
  110. package/dist/agent-operations/furnish-room.js +15 -4
  111. package/dist/agent-operations/furniture-kind.d.ts +28 -1
  112. package/dist/agent-operations/furniture-kind.d.ts.map +1 -1
  113. package/dist/agent-operations/furniture-kind.js +105 -6
  114. package/dist/agent-operations/ground-reader.d.ts +10 -0
  115. package/dist/agent-operations/ground-reader.d.ts.map +1 -0
  116. package/dist/agent-operations/ground-reader.js +25 -0
  117. package/dist/agent-operations/index.d.ts +118 -12
  118. package/dist/agent-operations/index.d.ts.map +1 -1
  119. package/dist/agent-operations/index.js +26 -1
  120. package/dist/agent-operations/item-solids.d.ts +39 -0
  121. package/dist/agent-operations/item-solids.d.ts.map +1 -0
  122. package/dist/agent-operations/item-solids.js +128 -0
  123. package/dist/agent-operations/layout-clearance.d.ts +91 -11
  124. package/dist/agent-operations/layout-clearance.d.ts.map +1 -1
  125. package/dist/agent-operations/layout-clearance.js +280 -73
  126. package/dist/agent-operations/level-height-fill.d.ts.map +1 -1
  127. package/dist/agent-operations/level-height-fill.js +7 -1
  128. package/dist/agent-operations/level-reads.d.ts +5 -0
  129. package/dist/agent-operations/level-reads.d.ts.map +1 -1
  130. package/dist/agent-operations/level-reads.js +38 -2
  131. package/dist/agent-operations/list-levels.d.ts.map +1 -1
  132. package/dist/agent-operations/list-levels.js +2 -4
  133. package/dist/agent-operations/move-node.d.ts +12 -0
  134. package/dist/agent-operations/move-node.d.ts.map +1 -0
  135. package/dist/agent-operations/move-node.js +28 -0
  136. package/dist/agent-operations/move-plan.d.ts +28 -0
  137. package/dist/agent-operations/move-plan.d.ts.map +1 -0
  138. package/dist/agent-operations/move-plan.js +257 -0
  139. package/dist/agent-operations/node-patch.d.ts.map +1 -1
  140. package/dist/agent-operations/node-patch.js +15 -0
  141. package/dist/agent-operations/paint.d.ts +3 -1
  142. package/dist/agent-operations/paint.d.ts.map +1 -1
  143. package/dist/agent-operations/paint.js +14 -1
  144. package/dist/agent-operations/photo-elevation.d.ts.map +1 -1
  145. package/dist/agent-operations/photo-elevation.js +2 -1
  146. package/dist/agent-operations/place-in-room.d.ts.map +1 -1
  147. package/dist/agent-operations/place-in-room.js +1 -0
  148. package/dist/agent-operations/place-items.d.ts.map +1 -1
  149. package/dist/agent-operations/place-items.js +3 -4
  150. package/dist/agent-operations/plan-clearance.d.ts +28 -0
  151. package/dist/agent-operations/plan-clearance.d.ts.map +1 -0
  152. package/dist/agent-operations/plan-clearance.js +47 -0
  153. package/dist/agent-operations/plan-geometry.d.ts +2 -0
  154. package/dist/agent-operations/plan-geometry.d.ts.map +1 -1
  155. package/dist/agent-operations/plan-geometry.js +10 -0
  156. package/dist/agent-operations/project-files.d.ts +10 -0
  157. package/dist/agent-operations/project-files.d.ts.map +1 -1
  158. package/dist/agent-operations/reachability.d.ts +41 -0
  159. package/dist/agent-operations/reachability.d.ts.map +1 -0
  160. package/dist/agent-operations/reachability.js +199 -0
  161. package/dist/agent-operations/room-checks.d.ts +60 -0
  162. package/dist/agent-operations/room-checks.d.ts.map +1 -0
  163. package/dist/agent-operations/room-checks.js +226 -0
  164. package/dist/agent-operations/room-faces.d.ts +30 -0
  165. package/dist/agent-operations/room-faces.d.ts.map +1 -0
  166. package/dist/agent-operations/room-faces.js +31 -0
  167. package/dist/agent-operations/room-findings.d.ts +5 -2
  168. package/dist/agent-operations/room-findings.d.ts.map +1 -1
  169. package/dist/agent-operations/room-findings.js +30 -0
  170. package/dist/agent-operations/room-finishes.d.ts +87 -0
  171. package/dist/agent-operations/room-finishes.d.ts.map +1 -0
  172. package/dist/agent-operations/room-finishes.js +193 -0
  173. package/dist/agent-operations/room-floor-script.d.ts +3 -3
  174. package/dist/agent-operations/room-floor-script.js +3 -3
  175. package/dist/agent-operations/room-readings.d.ts +90 -0
  176. package/dist/agent-operations/room-readings.d.ts.map +1 -0
  177. package/dist/agent-operations/room-readings.js +369 -0
  178. package/dist/agent-operations/room-tile.d.ts +15 -0
  179. package/dist/agent-operations/room-tile.d.ts.map +1 -0
  180. package/dist/agent-operations/room-tile.js +30 -0
  181. package/dist/agent-operations/room-wet.d.ts +55 -0
  182. package/dist/agent-operations/room-wet.d.ts.map +1 -0
  183. package/dist/agent-operations/room-wet.js +203 -0
  184. package/dist/agent-operations/scene-check-policy.d.ts +5 -0
  185. package/dist/agent-operations/scene-check-policy.d.ts.map +1 -0
  186. package/dist/agent-operations/scene-check-policy.js +24 -0
  187. package/dist/agent-operations/scene-checkpoint.d.ts +10 -0
  188. package/dist/agent-operations/scene-checkpoint.d.ts.map +1 -1
  189. package/dist/agent-operations/scene-checkpoint.js +31 -0
  190. package/dist/agent-operations/scene-issue-groups.d.ts +17 -0
  191. package/dist/agent-operations/scene-issue-groups.d.ts.map +1 -0
  192. package/dist/agent-operations/scene-issue-groups.js +71 -0
  193. package/dist/agent-operations/scene-materials.d.ts +23 -0
  194. package/dist/agent-operations/scene-materials.d.ts.map +1 -1
  195. package/dist/agent-operations/scene-materials.js +41 -0
  196. package/dist/agent-operations/scene-queries.d.ts +1667 -866
  197. package/dist/agent-operations/scene-queries.d.ts.map +1 -1
  198. package/dist/agent-operations/scene-queries.js +3 -3
  199. package/dist/agent-operations/scene-review.d.ts.map +1 -1
  200. package/dist/agent-operations/scene-review.js +3 -2
  201. package/dist/agent-operations/scene-view.d.ts.map +1 -1
  202. package/dist/agent-operations/scene-view.js +91 -21
  203. package/dist/agent-operations/search-assets.d.ts +10 -6
  204. package/dist/agent-operations/search-assets.d.ts.map +1 -1
  205. package/dist/agent-operations/search-assets.js +37 -22
  206. package/dist/agent-operations/set-checkpoint.d.ts.map +1 -1
  207. package/dist/agent-operations/set-checkpoint.js +1 -0
  208. package/dist/agent-operations/set-ground-level.d.ts +1 -1
  209. package/dist/agent-operations/set-ground-level.d.ts.map +1 -1
  210. package/dist/agent-operations/set-ground-level.js +3 -14
  211. package/dist/agent-operations/site.js +3 -2
  212. package/dist/agent-operations/slab-surface-script.d.ts +25 -0
  213. package/dist/agent-operations/slab-surface-script.d.ts.map +1 -0
  214. package/dist/agent-operations/slab-surface-script.js +60 -0
  215. package/dist/agent-operations/stair-plan-footprint.d.ts +9 -0
  216. package/dist/agent-operations/stair-plan-footprint.d.ts.map +1 -0
  217. package/dist/agent-operations/stair-plan-footprint.js +50 -0
  218. package/dist/agent-operations/types.d.ts +10 -5
  219. package/dist/agent-operations/types.d.ts.map +1 -1
  220. package/dist/agent-operations/units.d.ts +26 -0
  221. package/dist/agent-operations/units.d.ts.map +1 -0
  222. package/dist/agent-operations/units.js +110 -0
  223. package/dist/agent-operations/unreachable-rooms-check.d.ts +17 -0
  224. package/dist/agent-operations/unreachable-rooms-check.d.ts.map +1 -0
  225. package/dist/agent-operations/unreachable-rooms-check.js +35 -0
  226. package/dist/agent-operations/usage-checks.d.ts +2 -0
  227. package/dist/agent-operations/usage-checks.d.ts.map +1 -0
  228. package/dist/agent-operations/usage-checks.js +12 -0
  229. package/dist/agent-operations/verify-scene.d.ts +8 -11
  230. package/dist/agent-operations/verify-scene.d.ts.map +1 -1
  231. package/dist/agent-operations/verify-scene.js +55 -11
  232. package/dist/agent-operations/wall-face-defaults.d.ts +44 -0
  233. package/dist/agent-operations/wall-face-defaults.d.ts.map +1 -0
  234. package/dist/agent-operations/wall-face-defaults.js +229 -0
  235. package/dist/agent-operations/wall-joins.d.ts +119 -0
  236. package/dist/agent-operations/wall-joins.d.ts.map +1 -0
  237. package/dist/agent-operations/wall-joins.js +308 -0
  238. package/dist/agent-operations/wall-object-observations.d.ts.map +1 -1
  239. package/dist/agent-operations/wall-object-observations.js +4 -6
  240. package/dist/agent-operations/wall-skirting.d.ts.map +1 -1
  241. package/dist/agent-operations/wall-skirting.js +2 -0
  242. package/dist/agent-operations/wall-type.d.ts +49 -0
  243. package/dist/agent-operations/wall-type.d.ts.map +1 -0
  244. package/dist/agent-operations/wall-type.js +31 -0
  245. package/dist/agent-operations/zone-coverage.d.ts +85 -0
  246. package/dist/agent-operations/zone-coverage.d.ts.map +1 -0
  247. package/dist/agent-operations/zone-coverage.js +208 -0
  248. package/dist/agent-tools/add-object.d.ts +31 -0
  249. package/dist/agent-tools/add-object.d.ts.map +1 -1
  250. package/dist/agent-tools/add-object.js +44 -7
  251. package/dist/agent-tools/assets.d.ts +4 -0
  252. package/dist/agent-tools/assets.d.ts.map +1 -1
  253. package/dist/agent-tools/assets.js +11 -1
  254. package/dist/agent-tools/author-scene.d.ts +810 -15
  255. package/dist/agent-tools/author-scene.d.ts.map +1 -1
  256. package/dist/agent-tools/author-scene.js +275 -34
  257. package/dist/agent-tools/batch.d.ts +1 -1
  258. package/dist/agent-tools/batch.d.ts.map +1 -1
  259. package/dist/agent-tools/batch.js +5 -1
  260. package/dist/agent-tools/checkpoints.js +1 -1
  261. package/dist/agent-tools/columns.d.ts +14 -0
  262. package/dist/agent-tools/columns.d.ts.map +1 -1
  263. package/dist/agent-tools/columns.js +14 -2
  264. package/dist/agent-tools/contract.d.ts +10 -0
  265. package/dist/agent-tools/contract.d.ts.map +1 -1
  266. package/dist/agent-tools/contract.js +5 -1
  267. package/dist/agent-tools/corner-window.d.ts +1 -0
  268. package/dist/agent-tools/corner-window.d.ts.map +1 -1
  269. package/dist/agent-tools/corner-window.js +1 -1
  270. package/dist/agent-tools/create-room.d.ts +85 -0
  271. package/dist/agent-tools/create-room.d.ts.map +1 -1
  272. package/dist/agent-tools/create-room.js +8 -1
  273. package/dist/agent-tools/design-brief.d.ts +513 -7
  274. package/dist/agent-tools/design-brief.d.ts.map +1 -1
  275. package/dist/agent-tools/design-brief.js +192 -14
  276. package/dist/agent-tools/designs.js +1 -1
  277. package/dist/agent-tools/find-by-type.js +1 -1
  278. package/dist/agent-tools/index.d.ts +658 -10
  279. package/dist/agent-tools/index.d.ts.map +1 -1
  280. package/dist/agent-tools/index.js +9 -1
  281. package/dist/agent-tools/levels.js +4 -4
  282. package/dist/agent-tools/move-duplicate.d.ts +65 -0
  283. package/dist/agent-tools/move-duplicate.d.ts.map +1 -0
  284. package/dist/agent-tools/move-duplicate.js +68 -0
  285. package/dist/agent-tools/paint.d.ts +6 -0
  286. package/dist/agent-tools/paint.d.ts.map +1 -1
  287. package/dist/agent-tools/paint.js +7 -3
  288. package/dist/agent-tools/photo-elevation.js +1 -1
  289. package/dist/agent-tools/project-files.d.ts +2 -2
  290. package/dist/agent-tools/project-files.d.ts.map +1 -1
  291. package/dist/agent-tools/project-files.js +1 -1
  292. package/dist/agent-tools/refusal.d.ts +9 -1
  293. package/dist/agent-tools/refusal.d.ts.map +1 -1
  294. package/dist/agent-tools/refusal.js +5 -1
  295. package/dist/agent-tools/roofs.js +1 -1
  296. package/dist/agent-tools/room-structure.d.ts +60 -0
  297. package/dist/agent-tools/room-structure.d.ts.map +1 -1
  298. package/dist/agent-tools/room-structure.js +20 -1
  299. package/dist/agent-tools/search-materials.d.ts +1 -0
  300. package/dist/agent-tools/search-materials.d.ts.map +1 -1
  301. package/dist/agent-tools/site.js +1 -1
  302. package/dist/agent-tools/stairs.js +1 -1
  303. package/dist/agent-tools/units.d.ts +84 -0
  304. package/dist/agent-tools/units.d.ts.map +1 -0
  305. package/dist/agent-tools/units.js +54 -0
  306. package/dist/agent-tools/verify-scene-usage.d.ts +44 -0
  307. package/dist/agent-tools/verify-scene-usage.d.ts.map +1 -0
  308. package/dist/agent-tools/verify-scene-usage.js +146 -0
  309. package/dist/agent-tools/verify-scene.d.ts +12 -1
  310. package/dist/agent-tools/verify-scene.d.ts.map +1 -1
  311. package/dist/agent-tools/verify-scene.js +183 -3
  312. package/dist/agent-tools/view-scene.d.ts +3 -0
  313. package/dist/agent-tools/view-scene.d.ts.map +1 -1
  314. package/dist/agent-tools/view-scene.js +4 -1
  315. package/dist/agent-tools/wall-openings.d.ts +121 -2
  316. package/dist/agent-tools/wall-openings.d.ts.map +1 -1
  317. package/dist/agent-tools/wall-openings.js +123 -18
  318. package/dist/agent-tools/walls.d.ts +30 -0
  319. package/dist/agent-tools/walls.d.ts.map +1 -1
  320. package/dist/agent-tools/walls.js +54 -2
  321. package/dist/building/__fixtures__/wall-opening-cases.d.ts +340 -195
  322. package/dist/building/__fixtures__/wall-opening-cases.d.ts.map +1 -1
  323. package/dist/building/__fixtures__/wall-opening-cases.js +13 -0
  324. package/dist/building/code-profile.d.ts +231 -0
  325. package/dist/building/code-profile.d.ts.map +1 -0
  326. package/dist/building/code-profile.js +208 -0
  327. package/dist/building/corner-window.d.ts +2 -2
  328. package/dist/building/corner-window.d.ts.map +1 -1
  329. package/dist/building/corner-window.js +11 -2
  330. package/dist/building/door-types.d.ts +1 -0
  331. package/dist/building/door-types.d.ts.map +1 -1
  332. package/dist/building/door-types.js +2 -0
  333. package/dist/building/floor-item-fit.d.ts.map +1 -1
  334. package/dist/building/floor-item-fit.js +2 -4
  335. package/dist/building/ground-level.d.ts +1 -3
  336. package/dist/building/ground-level.d.ts.map +1 -1
  337. package/dist/building/ground-level.js +3 -5
  338. package/dist/building/index.d.ts +2 -0
  339. package/dist/building/index.d.ts.map +1 -1
  340. package/dist/building/index.js +2 -0
  341. package/dist/building/level-duplication.d.ts +5951 -4351
  342. package/dist/building/level-duplication.d.ts.map +1 -1
  343. package/dist/building/level-insertion.d.ts +1 -1
  344. package/dist/building/level-wall-copy.d.ts +24 -0
  345. package/dist/building/level-wall-copy.d.ts.map +1 -0
  346. package/dist/building/level-wall-copy.js +53 -0
  347. package/dist/building/merge-windows.d.ts.map +1 -1
  348. package/dist/building/merge-windows.js +1 -0
  349. package/dist/building/opening-style-presets.d.ts +2 -0
  350. package/dist/building/opening-style-presets.d.ts.map +1 -1
  351. package/dist/building/opening-style-presets.js +26 -5
  352. package/dist/building/room-use.d.ts +26 -0
  353. package/dist/building/room-use.d.ts.map +1 -0
  354. package/dist/building/room-use.js +102 -0
  355. package/dist/building/wall-openings.d.ts +115 -7
  356. package/dist/building/wall-openings.d.ts.map +1 -1
  357. package/dist/building/wall-openings.js +121 -29
  358. package/dist/building/window-types.d.ts +1 -0
  359. package/dist/building/window-types.d.ts.map +1 -1
  360. package/dist/building/window-types.js +3 -0
  361. package/dist/coherence/__fixtures__/enclosure-scenes.d.ts +84 -0
  362. package/dist/coherence/__fixtures__/enclosure-scenes.d.ts.map +1 -0
  363. package/dist/coherence/__fixtures__/enclosure-scenes.js +44 -0
  364. package/dist/commands/structure/attached-walls.d.ts +16 -0
  365. package/dist/commands/structure/attached-walls.d.ts.map +1 -0
  366. package/dist/commands/structure/attached-walls.js +53 -0
  367. package/dist/commands/structure/index.d.ts +3 -0
  368. package/dist/commands/structure/index.d.ts.map +1 -1
  369. package/dist/commands/structure/index.js +3 -0
  370. package/dist/commands/structure/move-separators.d.ts +62 -0
  371. package/dist/commands/structure/move-separators.d.ts.map +1 -0
  372. package/dist/commands/structure/move-separators.js +346 -0
  373. package/dist/commands/structure/set-floor-foundation.d.ts +5 -0
  374. package/dist/commands/structure/set-floor-foundation.d.ts.map +1 -1
  375. package/dist/commands/structure/set-wall-geometry.d.ts.map +1 -1
  376. package/dist/commands/structure/set-wall-geometry.js +44 -11
  377. package/dist/commands/structure/set-zone-intent.d.ts +26 -0
  378. package/dist/commands/structure/set-zone-intent.d.ts.map +1 -1
  379. package/dist/commands/structure/set-zone-intent.js +107 -2
  380. package/dist/commands/structure/shared.d.ts +888 -88
  381. package/dist/commands/structure/shared.d.ts.map +1 -1
  382. package/dist/commands/structure/wall-overlap.d.ts +45 -0
  383. package/dist/commands/structure/wall-overlap.d.ts.map +1 -0
  384. package/dist/commands/structure/wall-overlap.js +289 -0
  385. package/dist/commands/structure/zone-wall-merge.d.ts +3326 -2370
  386. package/dist/commands/structure/zone-wall-merge.d.ts.map +1 -1
  387. package/dist/contracts/reference-discovery.d.ts +2 -2
  388. package/dist/contracts/reference-discovery.d.ts.map +1 -1
  389. package/dist/contracts/reference-discovery.js +5 -3
  390. package/dist/contracts/reference-inventory.d.ts.map +1 -1
  391. package/dist/contracts/reference-inventory.js +75 -24
  392. package/dist/events/bus.d.ts +15 -2
  393. package/dist/events/bus.d.ts.map +1 -1
  394. package/dist/hooks/spatial-grid/built-in-floor-placed.d.ts +40 -0
  395. package/dist/hooks/spatial-grid/built-in-floor-placed.d.ts.map +1 -0
  396. package/dist/hooks/spatial-grid/built-in-floor-placed.js +220 -0
  397. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
  398. package/dist/hooks/spatial-grid/floor-placed-elevation.js +13 -7
  399. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +56 -1
  400. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  401. package/dist/hooks/spatial-grid/spatial-grid-manager.js +177 -27
  402. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +1631 -831
  403. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  404. package/dist/hooks/spatial-grid/spatial-grid-sync.js +24 -1
  405. package/dist/hooks/spatial-grid/support-host-patch.js +10 -10
  406. package/dist/i18n/format.d.ts +41 -0
  407. package/dist/i18n/format.d.ts.map +1 -0
  408. package/dist/i18n/format.js +124 -0
  409. package/dist/i18n/index.d.ts +5 -0
  410. package/dist/i18n/index.d.ts.map +1 -0
  411. package/dist/i18n/index.js +3 -0
  412. package/dist/i18n/registry.d.ts +78 -0
  413. package/dist/i18n/registry.d.ts.map +1 -0
  414. package/dist/i18n/registry.js +252 -0
  415. package/dist/i18n/use-translate.d.ts +16 -0
  416. package/dist/i18n/use-translate.d.ts.map +1 -0
  417. package/dist/i18n/use-translate.js +23 -0
  418. package/dist/index.d.ts +32 -16
  419. package/dist/index.d.ts.map +1 -1
  420. package/dist/index.js +31 -15
  421. package/dist/lib/__fixtures__/mezzanine.d.ts +6840 -4169
  422. package/dist/lib/__fixtures__/mezzanine.d.ts.map +1 -1
  423. package/dist/lib/absorb-wall-separators.d.ts +2128 -1328
  424. package/dist/lib/absorb-wall-separators.d.ts.map +1 -1
  425. package/dist/lib/beam.d.ts +137 -0
  426. package/dist/lib/beam.d.ts.map +1 -0
  427. package/dist/lib/beam.js +297 -0
  428. package/dist/lib/below-grade-footprints.d.ts +13 -5
  429. package/dist/lib/below-grade-footprints.d.ts.map +1 -1
  430. package/dist/lib/below-grade-footprints.js +53 -10
  431. package/dist/lib/build-up.d.ts +89 -0
  432. package/dist/lib/build-up.d.ts.map +1 -0
  433. package/dist/lib/build-up.js +216 -0
  434. package/dist/lib/corner-pair.d.ts +205 -4
  435. package/dist/lib/corner-pair.d.ts.map +1 -1
  436. package/dist/lib/deck-boards.d.ts +80 -0
  437. package/dist/lib/deck-boards.d.ts.map +1 -0
  438. package/dist/lib/deck-boards.js +184 -0
  439. package/dist/lib/deck-framing.d.ts +150 -0
  440. package/dist/lib/deck-framing.d.ts.map +1 -0
  441. package/dist/lib/deck-framing.js +515 -0
  442. package/dist/lib/deepest-drawn.d.ts +3 -6
  443. package/dist/lib/deepest-drawn.d.ts.map +1 -1
  444. package/dist/lib/deepest-drawn.js +4 -8
  445. package/dist/lib/door-opening.d.ts +19 -0
  446. package/dist/lib/door-opening.d.ts.map +1 -0
  447. package/dist/lib/door-opening.js +27 -0
  448. package/dist/lib/door-operation.d.ts +2 -0
  449. package/dist/lib/door-operation.d.ts.map +1 -1
  450. package/dist/lib/door-operation.js +4 -0
  451. package/dist/lib/exterior-wall-loops.d.ts.map +1 -1
  452. package/dist/lib/exterior-wall-loops.js +2 -1
  453. package/dist/lib/finish-script.d.ts +5 -5
  454. package/dist/lib/finish-script.d.ts.map +1 -1
  455. package/dist/lib/finish-script.js +12 -5
  456. package/dist/lib/floor-footprint-name.d.ts +17 -0
  457. package/dist/lib/floor-footprint-name.d.ts.map +1 -1
  458. package/dist/lib/floor-footprint-name.js +17 -4
  459. package/dist/lib/floor-intent-changes.d.ts.map +1 -1
  460. package/dist/lib/floor-intent-changes.js +60 -35
  461. package/dist/lib/floor-opening-footprints.d.ts +1 -1
  462. package/dist/lib/floor-opening-footprints.d.ts.map +1 -1
  463. package/dist/lib/floor-opening-footprints.js +10 -4
  464. package/dist/lib/floor-plate-paint.d.ts +2 -0
  465. package/dist/lib/floor-plate-paint.d.ts.map +1 -1
  466. package/dist/lib/floor-plate-paint.js +12 -0
  467. package/dist/lib/floor-plates.d.ts +250 -0
  468. package/dist/lib/floor-plates.d.ts.map +1 -1
  469. package/dist/lib/floor-plates.js +43 -4
  470. package/dist/lib/floor-script.d.ts +35 -6
  471. package/dist/lib/floor-script.d.ts.map +1 -1
  472. package/dist/lib/floor-script.js +96 -16
  473. package/dist/lib/foundation-construction.d.ts +4 -13
  474. package/dist/lib/foundation-construction.d.ts.map +1 -1
  475. package/dist/lib/foundation-construction.js +4 -14
  476. package/dist/lib/foundation-standard.d.ts +2 -3
  477. package/dist/lib/foundation-standard.d.ts.map +1 -1
  478. package/dist/lib/foundation-standard.js +2 -3
  479. package/dist/lib/foundation-wall.d.ts +1 -2
  480. package/dist/lib/foundation-wall.d.ts.map +1 -1
  481. package/dist/lib/foundation-wall.js +1 -2
  482. package/dist/lib/framing-post.d.ts +91 -0
  483. package/dist/lib/framing-post.d.ts.map +1 -0
  484. package/dist/lib/framing-post.js +174 -0
  485. package/dist/lib/geometry-script-node.d.ts +11 -0
  486. package/dist/lib/geometry-script-node.d.ts.map +1 -1
  487. package/dist/lib/geometry-script-node.js +35 -0
  488. package/dist/lib/grade-standing.d.ts +16 -2
  489. package/dist/lib/grade-standing.d.ts.map +1 -1
  490. package/dist/lib/grade-standing.js +129 -9
  491. package/dist/lib/ground-seat.d.ts +2 -22
  492. package/dist/lib/ground-seat.d.ts.map +1 -1
  493. package/dist/lib/ground-seat.js +2 -17
  494. package/dist/lib/ground-window.d.ts +2 -1
  495. package/dist/lib/ground-window.d.ts.map +1 -1
  496. package/dist/lib/ground-window.js +5 -2
  497. package/dist/lib/level-footprints.d.ts +2 -0
  498. package/dist/lib/level-footprints.d.ts.map +1 -1
  499. package/dist/lib/level-footprints.js +7 -0
  500. package/dist/lib/opening-casing.d.ts +67 -0
  501. package/dist/lib/opening-casing.d.ts.map +1 -0
  502. package/dist/lib/opening-casing.js +131 -0
  503. package/dist/lib/opening-exterior-face.d.ts +6 -0
  504. package/dist/lib/opening-exterior-face.d.ts.map +1 -0
  505. package/dist/lib/opening-exterior-face.js +15 -0
  506. package/dist/lib/opening-floor-datum.d.ts +11 -5
  507. package/dist/lib/opening-floor-datum.d.ts.map +1 -1
  508. package/dist/lib/opening-floor-datum.js +38 -6
  509. package/dist/lib/opening-frame-depth.d.ts +18 -0
  510. package/dist/lib/opening-frame-depth.d.ts.map +1 -0
  511. package/dist/lib/opening-frame-depth.js +22 -0
  512. package/dist/lib/outdoor-room.d.ts +32 -0
  513. package/dist/lib/outdoor-room.d.ts.map +1 -0
  514. package/dist/lib/outdoor-room.js +66 -0
  515. package/dist/lib/plate-surface.d.ts +8 -0
  516. package/dist/lib/plate-surface.d.ts.map +1 -1
  517. package/dist/lib/plate-surface.js +53 -3
  518. package/dist/lib/porch-framing-sync.d.ts +46 -0
  519. package/dist/lib/porch-framing-sync.d.ts.map +1 -0
  520. package/dist/lib/porch-framing-sync.js +206 -0
  521. package/dist/lib/porch-framing.d.ts +44 -0
  522. package/dist/lib/porch-framing.d.ts.map +1 -0
  523. package/dist/lib/porch-framing.js +159 -0
  524. package/dist/lib/railing-rooms.d.ts +13 -0
  525. package/dist/lib/railing-rooms.d.ts.map +1 -0
  526. package/dist/lib/railing-rooms.js +37 -0
  527. package/dist/lib/roof-eave.d.ts +135 -0
  528. package/dist/lib/roof-eave.d.ts.map +1 -0
  529. package/dist/lib/roof-eave.js +186 -0
  530. package/dist/lib/room-floor-plate.d.ts +3 -1
  531. package/dist/lib/room-floor-plate.d.ts.map +1 -1
  532. package/dist/lib/room-floor-plate.js +7 -0
  533. package/dist/lib/space-detection.d.ts +13 -0
  534. package/dist/lib/space-detection.d.ts.map +1 -1
  535. package/dist/lib/space-detection.js +112 -24
  536. package/dist/lib/structure-kernel.d.ts +22 -9
  537. package/dist/lib/structure-kernel.d.ts.map +1 -1
  538. package/dist/lib/structure-kernel.js +146 -28
  539. package/dist/lib/structure-reconcile.d.ts +1 -1
  540. package/dist/lib/structure-reconcile.d.ts.map +1 -1
  541. package/dist/lib/structure-reconcile.js +7 -4
  542. package/dist/lib/surface-intent.d.ts +10 -0
  543. package/dist/lib/surface-intent.d.ts.map +1 -0
  544. package/dist/lib/surface-intent.js +18 -0
  545. package/dist/lib/terrain-source.d.ts.map +1 -1
  546. package/dist/lib/terrain-source.js +2 -1
  547. package/dist/lib/wall-face-script.d.ts.map +1 -1
  548. package/dist/lib/wall-face-script.js +5 -2
  549. package/dist/material-kinds.d.ts +1 -1
  550. package/dist/material-kinds.d.ts.map +1 -1
  551. package/dist/material-kinds.js +1 -0
  552. package/dist/material-library.d.ts +15 -0
  553. package/dist/material-library.d.ts.map +1 -1
  554. package/dist/material-library.js +458 -59
  555. package/dist/node-slots/door.d.ts +3 -4
  556. package/dist/node-slots/door.d.ts.map +1 -1
  557. package/dist/node-slots/door.js +4 -3
  558. package/dist/node-slots/wall.d.ts.map +1 -1
  559. package/dist/node-slots/wall.js +21 -3
  560. package/dist/node-slots/window.d.ts +2 -2
  561. package/dist/node-slots/window.d.ts.map +1 -1
  562. package/dist/node-slots/window.js +6 -1
  563. package/dist/procedural-items/design.d.ts +8 -0
  564. package/dist/procedural-items/design.d.ts.map +1 -1
  565. package/dist/procedural-items/design.js +169 -21
  566. package/dist/procedural-items/motion-controller.d.ts +2 -0
  567. package/dist/procedural-items/motion-controller.d.ts.map +1 -1
  568. package/dist/procedural-items/motion-controller.js +2 -1
  569. package/dist/registry/index.d.ts +2 -2
  570. package/dist/registry/index.d.ts.map +1 -1
  571. package/dist/registry/index.js +1 -1
  572. package/dist/registry/mechanism.d.ts +4 -1
  573. package/dist/registry/mechanism.d.ts.map +1 -1
  574. package/dist/registry/mechanism.js +10 -0
  575. package/dist/registry/types.d.ts +65 -23
  576. package/dist/registry/types.d.ts.map +1 -1
  577. package/dist/runtime/__fixtures__/live-scene.d.ts +14 -0
  578. package/dist/runtime/__fixtures__/live-scene.d.ts.map +1 -0
  579. package/dist/runtime/__fixtures__/live-scene.js +64 -0
  580. package/dist/runtime/apply.d.ts +42 -0
  581. package/dist/runtime/apply.d.ts.map +1 -0
  582. package/dist/runtime/apply.js +274 -0
  583. package/dist/runtime/dependencies.d.ts +97 -0
  584. package/dist/runtime/dependencies.d.ts.map +1 -0
  585. package/dist/runtime/dependencies.js +42 -0
  586. package/dist/runtime/ids.d.ts +11 -0
  587. package/dist/runtime/ids.d.ts.map +1 -0
  588. package/dist/runtime/ids.js +80 -0
  589. package/dist/runtime/index.d.ts +14 -0
  590. package/dist/runtime/index.d.ts.map +1 -0
  591. package/dist/runtime/index.js +11 -0
  592. package/dist/runtime/node-queries.d.ts +16 -0
  593. package/dist/runtime/node-queries.d.ts.map +1 -0
  594. package/dist/runtime/node-queries.js +30 -0
  595. package/dist/runtime/patch.d.ts +26 -0
  596. package/dist/runtime/patch.d.ts.map +1 -0
  597. package/dist/runtime/patch.js +165 -0
  598. package/dist/runtime/reader.d.ts +104 -0
  599. package/dist/runtime/reader.d.ts.map +1 -0
  600. package/dist/runtime/reader.js +455 -0
  601. package/dist/runtime/scene-runtime.d.ts +112 -0
  602. package/dist/runtime/scene-runtime.d.ts.map +1 -0
  603. package/dist/runtime/scene-runtime.js +302 -0
  604. package/dist/schema/__fixtures__/node-fixtures.d.ts.map +1 -1
  605. package/dist/schema/__fixtures__/node-fixtures.js +1 -0
  606. package/dist/schema/assembly.d.ts +3 -0
  607. package/dist/schema/assembly.d.ts.map +1 -1
  608. package/dist/schema/assembly.js +13 -0
  609. package/dist/schema/asset-url.d.ts +37 -2
  610. package/dist/schema/asset-url.d.ts.map +1 -1
  611. package/dist/schema/asset-url.js +80 -11
  612. package/dist/schema/base.d.ts +8 -0
  613. package/dist/schema/base.d.ts.map +1 -1
  614. package/dist/schema/base.js +19 -1
  615. package/dist/schema/build-up.d.ts +247 -0
  616. package/dist/schema/build-up.d.ts.map +1 -0
  617. package/dist/schema/build-up.js +83 -0
  618. package/dist/schema/geometry-source.d.ts +80 -0
  619. package/dist/schema/geometry-source.d.ts.map +1 -1
  620. package/dist/schema/geometry-source.js +38 -0
  621. package/dist/schema/index.d.ts +12 -7
  622. package/dist/schema/index.d.ts.map +1 -1
  623. package/dist/schema/index.js +12 -6
  624. package/dist/schema/material.d.ts +5 -0
  625. package/dist/schema/material.d.ts.map +1 -1
  626. package/dist/schema/material.js +6 -0
  627. package/dist/schema/nodes/beam.d.ts +264 -0
  628. package/dist/schema/nodes/beam.d.ts.map +1 -0
  629. package/dist/schema/nodes/beam.js +139 -0
  630. package/dist/schema/nodes/box-vent.d.ts +5 -0
  631. package/dist/schema/nodes/box-vent.d.ts.map +1 -1
  632. package/dist/schema/nodes/cabinet.d.ts +10 -0
  633. package/dist/schema/nodes/cabinet.d.ts.map +1 -1
  634. package/dist/schema/nodes/ceiling.d.ts +48 -0
  635. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  636. package/dist/schema/nodes/ceiling.js +5 -0
  637. package/dist/schema/nodes/chimney.d.ts +10 -0
  638. package/dist/schema/nodes/chimney.d.ts.map +1 -1
  639. package/dist/schema/nodes/column.d.ts +42 -0
  640. package/dist/schema/nodes/column.d.ts.map +1 -1
  641. package/dist/schema/nodes/column.js +11 -1
  642. package/dist/schema/nodes/cupola.d.ts +5 -0
  643. package/dist/schema/nodes/cupola.d.ts.map +1 -1
  644. package/dist/schema/nodes/door.d.ts +54 -3
  645. package/dist/schema/nodes/door.d.ts.map +1 -1
  646. package/dist/schema/nodes/door.js +41 -7
  647. package/dist/schema/nodes/dormer.d.ts +20 -0
  648. package/dist/schema/nodes/dormer.d.ts.map +1 -1
  649. package/dist/schema/nodes/downspout.d.ts +5 -0
  650. package/dist/schema/nodes/downspout.d.ts.map +1 -1
  651. package/dist/schema/nodes/elevator.d.ts +5 -0
  652. package/dist/schema/nodes/elevator.d.ts.map +1 -1
  653. package/dist/schema/nodes/eyebrow-vent.d.ts +5 -0
  654. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -1
  655. package/dist/schema/nodes/fence.d.ts +5 -0
  656. package/dist/schema/nodes/fence.d.ts.map +1 -1
  657. package/dist/schema/nodes/guide.d.ts +14 -0
  658. package/dist/schema/nodes/guide.d.ts.map +1 -1
  659. package/dist/schema/nodes/guide.js +2 -0
  660. package/dist/schema/nodes/gutter.d.ts +5 -0
  661. package/dist/schema/nodes/gutter.d.ts.map +1 -1
  662. package/dist/schema/nodes/item.d.ts +71 -0
  663. package/dist/schema/nodes/item.d.ts.map +1 -1
  664. package/dist/schema/nodes/item.js +5 -1
  665. package/dist/schema/nodes/level.d.ts +1 -1
  666. package/dist/schema/nodes/level.d.ts.map +1 -1
  667. package/dist/schema/nodes/opening-types.d.ts +13 -0
  668. package/dist/schema/nodes/opening-types.d.ts.map +1 -1
  669. package/dist/schema/nodes/opening-types.js +8 -1
  670. package/dist/schema/nodes/railing-wall.d.ts +112 -0
  671. package/dist/schema/nodes/railing-wall.d.ts.map +1 -0
  672. package/dist/schema/nodes/railing-wall.js +160 -0
  673. package/dist/schema/nodes/ridge-vent.d.ts +5 -0
  674. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
  675. package/dist/schema/nodes/roof-segment.d.ts +224 -1
  676. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  677. package/dist/schema/nodes/roof-segment.js +57 -10
  678. package/dist/schema/nodes/roof-types.d.ts +11 -0
  679. package/dist/schema/nodes/roof-types.d.ts.map +1 -1
  680. package/dist/schema/nodes/roof-types.js +6 -0
  681. package/dist/schema/nodes/roof.d.ts +60 -0
  682. package/dist/schema/nodes/roof.d.ts.map +1 -1
  683. package/dist/schema/nodes/roof.js +4 -0
  684. package/dist/schema/nodes/shelf.d.ts +5 -0
  685. package/dist/schema/nodes/shelf.d.ts.map +1 -1
  686. package/dist/schema/nodes/site.d.ts +80 -0
  687. package/dist/schema/nodes/site.d.ts.map +1 -1
  688. package/dist/schema/nodes/site.js +7 -0
  689. package/dist/schema/nodes/skylight.d.ts +10 -0
  690. package/dist/schema/nodes/skylight.d.ts.map +1 -1
  691. package/dist/schema/nodes/slab.d.ts +137 -0
  692. package/dist/schema/nodes/slab.d.ts.map +1 -1
  693. package/dist/schema/nodes/slab.js +3 -0
  694. package/dist/schema/nodes/solar-panel.d.ts +10 -0
  695. package/dist/schema/nodes/solar-panel.d.ts.map +1 -1
  696. package/dist/schema/nodes/stair-segment.d.ts +5 -0
  697. package/dist/schema/nodes/stair-segment.d.ts.map +1 -1
  698. package/dist/schema/nodes/stair-structure.d.ts +69 -0
  699. package/dist/schema/nodes/stair-structure.d.ts.map +1 -0
  700. package/dist/schema/nodes/stair-structure.js +39 -0
  701. package/dist/schema/nodes/stair.d.ts +46 -0
  702. package/dist/schema/nodes/stair.d.ts.map +1 -1
  703. package/dist/schema/nodes/stair.js +3 -0
  704. package/dist/schema/nodes/turbine-vent.d.ts +5 -0
  705. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -1
  706. package/dist/schema/nodes/wall.d.ts +135 -2
  707. package/dist/schema/nodes/wall.d.ts.map +1 -1
  708. package/dist/schema/nodes/wall.js +42 -6
  709. package/dist/schema/nodes/window.d.ts +66 -1
  710. package/dist/schema/nodes/window.d.ts.map +1 -1
  711. package/dist/schema/nodes/window.js +50 -5
  712. package/dist/schema/nodes/zone.d.ts +68 -0
  713. package/dist/schema/nodes/zone.d.ts.map +1 -1
  714. package/dist/schema/nodes/zone.js +32 -0
  715. package/dist/schema/scene-material.d.ts +5 -0
  716. package/dist/schema/scene-material.d.ts.map +1 -1
  717. package/dist/schema/types.d.ts +1079 -4
  718. package/dist/schema/types.d.ts.map +1 -1
  719. package/dist/schema/types.js +2 -0
  720. package/dist/schema/wall-type.d.ts +95 -0
  721. package/dist/schema/wall-type.d.ts.map +1 -0
  722. package/dist/schema/wall-type.js +32 -0
  723. package/dist/services/roof-bearing.d.ts.map +1 -1
  724. package/dist/services/roof-bearing.js +6 -3
  725. package/dist/services/roof-level.d.ts.map +1 -1
  726. package/dist/services/roof-level.js +4 -3
  727. package/dist/services/roof-move.d.ts.map +1 -1
  728. package/dist/services/roof-move.js +13 -7
  729. package/dist/services/storey.d.ts.map +1 -1
  730. package/dist/services/storey.js +13 -8
  731. package/dist/spatial/usage-field/beds.d.ts +51 -0
  732. package/dist/spatial/usage-field/beds.d.ts.map +1 -0
  733. package/dist/spatial/usage-field/beds.js +346 -0
  734. package/dist/spatial/usage-field/blocked.d.ts +28 -0
  735. package/dist/spatial/usage-field/blocked.d.ts.map +1 -0
  736. package/dist/spatial/usage-field/blocked.js +185 -0
  737. package/dist/spatial/usage-field/boxes.d.ts +17 -0
  738. package/dist/spatial/usage-field/boxes.d.ts.map +1 -0
  739. package/dist/spatial/usage-field/boxes.js +82 -0
  740. package/dist/spatial/usage-field/catalogue-shapes.d.ts +3 -0
  741. package/dist/spatial/usage-field/catalogue-shapes.d.ts.map +1 -0
  742. package/dist/spatial/usage-field/catalogue-shapes.js +257 -0
  743. package/dist/spatial/usage-field/catalogue.d.ts +6 -0
  744. package/dist/spatial/usage-field/catalogue.d.ts.map +1 -0
  745. package/dist/spatial/usage-field/catalogue.js +11 -0
  746. package/dist/spatial/usage-field/clash.d.ts +40 -0
  747. package/dist/spatial/usage-field/clash.d.ts.map +1 -0
  748. package/dist/spatial/usage-field/clash.js +187 -0
  749. package/dist/spatial/usage-field/door-stairs.d.ts +18 -0
  750. package/dist/spatial/usage-field/door-stairs.d.ts.map +1 -0
  751. package/dist/spatial/usage-field/door-stairs.js +129 -0
  752. package/dist/spatial/usage-field/entry-way.d.ts +14 -0
  753. package/dist/spatial/usage-field/entry-way.d.ts.map +1 -0
  754. package/dist/spatial/usage-field/entry-way.js +219 -0
  755. package/dist/spatial/usage-field/fences.d.ts +18 -0
  756. package/dist/spatial/usage-field/fences.d.ts.map +1 -0
  757. package/dist/spatial/usage-field/fences.js +253 -0
  758. package/dist/spatial/usage-field/field.d.ts +44 -0
  759. package/dist/spatial/usage-field/field.d.ts.map +1 -0
  760. package/dist/spatial/usage-field/field.js +145 -0
  761. package/dist/spatial/usage-field/findings.d.ts +76 -0
  762. package/dist/spatial/usage-field/findings.d.ts.map +1 -0
  763. package/dist/spatial/usage-field/findings.js +252 -0
  764. package/dist/spatial/usage-field/fixtures.d.ts +18 -0
  765. package/dist/spatial/usage-field/fixtures.d.ts.map +1 -0
  766. package/dist/spatial/usage-field/fixtures.js +160 -0
  767. package/dist/spatial/usage-field/flag.d.ts +10 -0
  768. package/dist/spatial/usage-field/flag.d.ts.map +1 -0
  769. package/dist/spatial/usage-field/flag.js +12 -0
  770. package/dist/spatial/usage-field/floors.d.ts +13 -0
  771. package/dist/spatial/usage-field/floors.d.ts.map +1 -0
  772. package/dist/spatial/usage-field/floors.js +36 -0
  773. package/dist/spatial/usage-field/index.d.ts +7 -0
  774. package/dist/spatial/usage-field/index.d.ts.map +1 -0
  775. package/dist/spatial/usage-field/index.js +5 -0
  776. package/dist/spatial/usage-field/items.d.ts +38 -0
  777. package/dist/spatial/usage-field/items.d.ts.map +1 -0
  778. package/dist/spatial/usage-field/items.js +261 -0
  779. package/dist/spatial/usage-field/labels.d.ts +8 -0
  780. package/dist/spatial/usage-field/labels.d.ts.map +1 -0
  781. package/dist/spatial/usage-field/labels.js +14 -0
  782. package/dist/spatial/usage-field/level-field.d.ts +47 -0
  783. package/dist/spatial/usage-field/level-field.d.ts.map +1 -0
  784. package/dist/spatial/usage-field/level-field.js +96 -0
  785. package/dist/spatial/usage-field/measure.d.ts +324 -0
  786. package/dist/spatial/usage-field/measure.d.ts.map +1 -0
  787. package/dist/spatial/usage-field/measure.js +458 -0
  788. package/dist/spatial/usage-field/obstacles.d.ts +63 -0
  789. package/dist/spatial/usage-field/obstacles.d.ts.map +1 -0
  790. package/dist/spatial/usage-field/obstacles.js +215 -0
  791. package/dist/spatial/usage-field/openings.d.ts +12 -0
  792. package/dist/spatial/usage-field/openings.d.ts.map +1 -0
  793. package/dist/spatial/usage-field/openings.js +162 -0
  794. package/dist/spatial/usage-field/path.d.ts +48 -0
  795. package/dist/spatial/usage-field/path.d.ts.map +1 -0
  796. package/dist/spatial/usage-field/path.js +354 -0
  797. package/dist/spatial/usage-field/pieces.d.ts +27 -0
  798. package/dist/spatial/usage-field/pieces.d.ts.map +1 -0
  799. package/dist/spatial/usage-field/pieces.js +89 -0
  800. package/dist/spatial/usage-field/shapes.d.ts +18 -0
  801. package/dist/spatial/usage-field/shapes.d.ts.map +1 -0
  802. package/dist/spatial/usage-field/shapes.js +92 -0
  803. package/dist/spatial/usage-field/stairs.d.ts +23 -0
  804. package/dist/spatial/usage-field/stairs.d.ts.map +1 -0
  805. package/dist/spatial/usage-field/stairs.js +163 -0
  806. package/dist/spatial/usage-field/support.d.ts +24 -0
  807. package/dist/spatial/usage-field/support.d.ts.map +1 -0
  808. package/dist/spatial/usage-field/support.js +46 -0
  809. package/dist/spatial/usage-field/swings.d.ts +846 -0
  810. package/dist/spatial/usage-field/swings.d.ts.map +1 -0
  811. package/dist/spatial/usage-field/swings.js +427 -0
  812. package/dist/spatial/usage-field/types.d.ts +65 -0
  813. package/dist/spatial/usage-field/types.d.ts.map +1 -0
  814. package/dist/spatial/usage-field/types.js +1 -0
  815. package/dist/spatial/usage-field/walls.d.ts +1057 -0
  816. package/dist/spatial/usage-field/walls.d.ts.map +1 -0
  817. package/dist/spatial/usage-field/walls.js +166 -0
  818. package/dist/spatial/usage-field/way-in.d.ts +26 -0
  819. package/dist/spatial/usage-field/way-in.d.ts.map +1 -0
  820. package/dist/spatial/usage-field/way-in.js +113 -0
  821. package/dist/store/actions/node-actions.d.ts +2124 -1322
  822. package/dist/store/actions/node-actions.d.ts.map +1 -1
  823. package/dist/store/actions/node-actions.js +42 -16
  824. package/dist/store/derived-node-guard.d.ts +10 -2
  825. package/dist/store/derived-node-guard.d.ts.map +1 -1
  826. package/dist/store/derived-node-guard.js +31 -11
  827. package/dist/store/document-settling.d.ts +9 -0
  828. package/dist/store/document-settling.d.ts.map +1 -0
  829. package/dist/store/document-settling.js +22 -0
  830. package/dist/store/history-control.d.ts +19 -10
  831. package/dist/store/history-control.d.ts.map +1 -1
  832. package/dist/store/history-control.js +176 -127
  833. package/dist/store/history-drafts.d.ts.map +1 -1
  834. package/dist/store/history-drafts.js +21 -17
  835. package/dist/store/live-previews.d.ts +9 -0
  836. package/dist/store/live-previews.d.ts.map +1 -0
  837. package/dist/store/live-previews.js +19 -0
  838. package/dist/store/scene-annotations.d.ts +6751 -5151
  839. package/dist/store/scene-annotations.d.ts.map +1 -1
  840. package/dist/store/scene-environment.d.ts +33 -0
  841. package/dist/store/scene-environment.d.ts.map +1 -0
  842. package/dist/store/scene-environment.js +54 -0
  843. package/dist/store/scene-hydration.d.ts.map +1 -1
  844. package/dist/store/scene-hydration.js +41 -30
  845. package/dist/store/use-interactive.d.ts +9 -3
  846. package/dist/store/use-interactive.d.ts.map +1 -1
  847. package/dist/store/use-interactive.js +43 -11
  848. package/dist/store/use-live-node-overrides.d.ts.map +1 -1
  849. package/dist/store/use-live-node-overrides.js +3 -0
  850. package/dist/store/use-scene.d.ts +25 -0
  851. package/dist/store/use-scene.d.ts.map +1 -1
  852. package/dist/store/use-scene.js +408 -336
  853. package/dist/systems/document-reconcile.d.ts +40 -0
  854. package/dist/systems/document-reconcile.d.ts.map +1 -0
  855. package/dist/systems/document-reconcile.js +159 -0
  856. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -1
  857. package/dist/systems/elevator/elevator-opening-system.js +9 -24
  858. package/dist/systems/fence/guard-layout.d.ts +69 -0
  859. package/dist/systems/fence/guard-layout.d.ts.map +1 -0
  860. package/dist/systems/fence/guard-layout.js +194 -0
  861. package/dist/systems/owned-floor-openings.d.ts +13 -1
  862. package/dist/systems/owned-floor-openings.d.ts.map +1 -1
  863. package/dist/systems/owned-floor-openings.js +58 -2
  864. package/dist/systems/reconcile-owned-floor-openings.d.ts +3 -1
  865. package/dist/systems/reconcile-owned-floor-openings.d.ts.map +1 -1
  866. package/dist/systems/reconcile-owned-floor-openings.js +4 -4
  867. package/dist/systems/roof/roof-elevation-system.d.ts.map +1 -1
  868. package/dist/systems/roof/roof-elevation-system.js +8 -26
  869. package/dist/systems/roof/roof-elevation.d.ts +16 -1
  870. package/dist/systems/roof/roof-elevation.d.ts.map +1 -1
  871. package/dist/systems/roof/roof-elevation.js +85 -4
  872. package/dist/systems/roof/roof-footprint.d.ts +42 -1
  873. package/dist/systems/roof/roof-footprint.d.ts.map +1 -1
  874. package/dist/systems/roof/roof-footprint.js +160 -4
  875. package/dist/systems/slab/__fixtures__/floor-step.d.ts +3163 -1934
  876. package/dist/systems/slab/__fixtures__/floor-step.d.ts.map +1 -1
  877. package/dist/systems/slab/__fixtures__/raised-room.d.ts +2495 -1249
  878. package/dist/systems/slab/__fixtures__/raised-room.d.ts.map +1 -1
  879. package/dist/systems/stair/stair-clearance.d.ts +7 -0
  880. package/dist/systems/stair/stair-clearance.d.ts.map +1 -1
  881. package/dist/systems/stair/stair-clearance.js +31 -11
  882. package/dist/systems/stair/stair-construction.d.ts +8 -3
  883. package/dist/systems/stair/stair-construction.d.ts.map +1 -1
  884. package/dist/systems/stair/stair-construction.js +55 -4
  885. package/dist/systems/stair/stair-opening-preview.d.ts +889 -89
  886. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  887. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
  888. package/dist/systems/stair/stair-opening-system.js +18 -34
  889. package/dist/systems/stair/stair-presets.d.ts +36 -0
  890. package/dist/systems/stair/stair-presets.d.ts.map +1 -1
  891. package/dist/systems/stair/stair-rise-query.d.ts.map +1 -1
  892. package/dist/systems/stair/stair-rise-query.js +3 -2
  893. package/dist/systems/stair/stair-rise.d.ts.map +1 -1
  894. package/dist/systems/stair/stair-rise.js +6 -0
  895. package/dist/systems/stair/stair-sizing.d.ts +42 -11
  896. package/dist/systems/stair/stair-sizing.d.ts.map +1 -1
  897. package/dist/systems/stair/stair-sizing.js +10 -1
  898. package/dist/systems/stair/stair-structure.d.ts +277 -0
  899. package/dist/systems/stair/stair-structure.d.ts.map +1 -0
  900. package/dist/systems/stair/stair-structure.js +496 -0
  901. package/dist/systems/wall/wall-assembly.d.ts +84 -6
  902. package/dist/systems/wall/wall-assembly.d.ts.map +1 -1
  903. package/dist/systems/wall/wall-assembly.js +204 -5
  904. package/dist/systems/wall/wall-finish.d.ts +38 -2
  905. package/dist/systems/wall/wall-finish.d.ts.map +1 -1
  906. package/dist/systems/wall/wall-finish.js +70 -2
  907. package/dist/systems/wall/wall-footprint.js +2 -2
  908. package/dist/systems/wall/wall-merge.d.ts.map +1 -1
  909. package/dist/systems/wall/wall-merge.js +28 -7
  910. package/dist/systems/wall/wall-mitering.d.ts +1 -0
  911. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  912. package/dist/systems/wall/wall-mitering.js +152 -52
  913. package/dist/systems/wall/wall-move.d.ts +23 -6
  914. package/dist/systems/wall/wall-move.d.ts.map +1 -1
  915. package/dist/systems/wall/wall-move.js +65 -0
  916. package/dist/systems/wall/wall-top.d.ts +2 -2
  917. package/dist/systems/wall/wall-top.d.ts.map +1 -1
  918. package/dist/systems/wall/wall-top.js +6 -1
  919. package/dist/systems/wall/wall-topology.d.ts +32 -1
  920. package/dist/systems/wall/wall-topology.d.ts.map +1 -1
  921. package/dist/systems/wall/wall-topology.js +118 -10
  922. package/dist/systems/wall/wall-trim-profiles.d.ts +12 -0
  923. package/dist/systems/wall/wall-trim-profiles.d.ts.map +1 -0
  924. package/dist/systems/wall/wall-trim-profiles.js +191 -0
  925. package/dist/utils/ensure-scene-openings.d.ts +6523 -4923
  926. package/dist/utils/ensure-scene-openings.d.ts.map +1 -1
  927. package/dist/utils/floor-piece-adoption.d.ts +5 -0
  928. package/dist/utils/floor-piece-adoption.d.ts.map +1 -1
  929. package/dist/utils/floor-plate-migration.d.ts +1930 -1130
  930. package/dist/utils/floor-plate-migration.d.ts.map +1 -1
  931. package/dist/utils/owned-floor-opening-migration.d.ts +888 -88
  932. package/dist/utils/owned-floor-opening-migration.d.ts.map +1 -1
  933. package/dist/utils/reconcile-structure-on-load.d.ts +1930 -1130
  934. package/dist/utils/reconcile-structure-on-load.d.ts.map +1 -1
  935. package/dist/utils/structure-id.d.ts.map +1 -1
  936. package/dist/utils/structure-id.js +4 -0
  937. package/package.json +22 -5
  938. package/dist/lib/generic-foundation.d.ts +0 -40
  939. package/dist/lib/generic-foundation.d.ts.map +0 -1
  940. package/dist/lib/generic-foundation.js +0 -200
@@ -1,32 +1,88 @@
1
1
  import { refuse } from '../agent-tools/refusal.js';
2
- import { doorTypeFields } from '../building/door-types.js';
2
+ import { doorTypeFields, NEW_DOOR_FRAME_THICKNESS } from '../building/door-types.js';
3
3
  import { DOOR_STYLES, doorStyleLook, getWindowStyleOverrides, WINDOW_STYLES, } from '../building/opening-style-presets.js';
4
- import { windowTakesStyle, windowTypeFields } from '../building/window-types.js';
4
+ import { doorFacing, resolveWallOpeningCeiling } from '../building/wall-openings.js';
5
+ import { NEW_WINDOW_FRAME_THICKNESS, windowTakesStyle, windowTypeFields, } from '../building/window-types.js';
6
+ import { cutFloorOpening } from '../commands/structure/floor-opening.js';
7
+ import { doorPositionForFacing, getDoorOpeningSize } from '../lib/door-opening.js';
5
8
  import { floorRoomFaces } from '../lib/floor-room-faces.js';
6
9
  import { matchScriptSlotsToLibrary, requireMeasured, scriptedSize, scriptSource, } from '../lib/geometry-script-node.js';
10
+ import { exposedIntervals } from '../lib/level-footprints.js';
11
+ import { area, containsPoint, difference, intersection, union, } from '../lib/polygon-boolean.js';
12
+ import { roundFloorElevation } from '../lib/room-floor-feasibility.js';
7
13
  import { detectSpacesForLevel } from '../lib/space-detection.js';
8
14
  import { reconcileSceneStructure } from '../lib/structure-reconcile.js';
9
15
  import { wallFaceScriptInputs } from '../lib/wall-face-script.js';
16
+ import { nodeSlotDeclarations } from '../node-slots/index.js';
10
17
  import { nearestLibraryColorRef } from '../procedural-items/library-colors.js';
11
- import { ColumnNode, DoorNode, LevelNode, SlabNode, WallNode, WindowNode, ZoneNode, } from '../schema/index.js';
18
+ import { ColumnNode, CurtainWallConfig, DoorNode, FenceNode, getActiveRoofHeight, getCurtainWallConfig, getSegmentSlopeFrame, LevelNode, RoofNode, RoofSegmentNode, SlabNode, UnitNode, WallNode, WindowNode, ZoneNode, } from '../schema/index.js';
12
19
  import { DoorType, WindowType } from '../schema/nodes/opening-types.js';
13
- import { resolveCeilingHeight } from '../services/level-height.js';
20
+ import { MIN_SLAB_THICKNESS } from '../schema/nodes/slab.js';
21
+ import { DEFAULT_LEVEL_HEIGHT, resolveCeilingHeight } from '../services/level-height.js';
22
+ import { planRoofLevel } from '../services/roof-level.js';
14
23
  import { CEILING_DRAW_OFFSET } from '../services/storey.js';
24
+ import { resolveRoofElevation } from '../systems/roof/roof-elevation.js';
25
+ import { stairArrivalOpening } from '../systems/stair/stair-footprint.js';
26
+ import { getMaxWallCurveOffset, getWallCenterlineSegments, getWallCurveLength, getWallSurfacePolygon, isCurvedWall, normalizeWallCurveOffset, } from '../systems/wall/wall-curve.js';
27
+ import { getWallPlanFootprint, getWallThickness } from '../systems/wall/wall-footprint.js';
15
28
  import { getWallLocalFaceZ } from '../systems/wall/wall-frame.js';
29
+ import { calculateLevelMiters } from '../systems/wall/wall-mitering.js';
16
30
  import { roomFaceCover } from '../systems/wall/wall-room-sides.js';
17
31
  import { migrateRoomZones } from '../utils/room-zone-migration.js';
18
32
  import { addObject } from './add-object.js';
33
+ import { applySceneChanges } from './apply-changes.js';
19
34
  import { authoredSceneChanges } from './authored-scene-merge.js';
35
+ import { createStair } from './create-stair.js';
20
36
  import { paint } from './paint.js';
21
37
  import { pointInPolygon, projectWorldPointToWallLocalX } from './plan-geometry.js';
22
- import { resolveInlinePaintMaterial } from './scene-materials.js';
38
+ import { exactColorMaterial, exactScriptSlots, MATTE } from './scene-materials.js';
39
+ import { queryNodes } from './scene-queries.js';
23
40
  import { setWallSkirting } from './wall-skirting.js';
41
+ const BOOKKEEPING = ['authorSceneBase', 'authorSceneWritten'];
42
+ /** A value as live collaboration and storage keep it: no undefined, no non-finite number. */
43
+ const plain = (value) => JSON.parse(JSON.stringify(value));
44
+ /** A node without the conversion's bookkeeping: what a person edits and a revision merges. */
45
+ function withoutBookkeeping(node) {
46
+ const metadata = node.metadata;
47
+ if (!(metadata && BOOKKEEPING.some((key) => key in metadata)))
48
+ return node;
49
+ const kept = { ...metadata };
50
+ for (const key of BOOKKEEPING)
51
+ delete kept[key];
52
+ return { ...node, metadata: kept };
53
+ }
54
+ /** A node the person deleted since the last conversion: present in the baseline, never read. */
55
+ const DELETED = { type: 'deleted' };
56
+ /**
57
+ * The last conversion of `building`, read back from its levels and nodes: its seed, its source-key
58
+ * identities, and the baseline of every node it wrote (a deleted one marked so).
59
+ */
60
+ function authoredBaseline(scene, building) {
61
+ const state = building.metadata?.authorSceneState;
62
+ if (!(state?.version === 2 && state.buildingId === building.id && typeof state.seed === 'string'))
63
+ return undefined;
64
+ const ids = {};
65
+ const nodes = {};
66
+ // The building lists what it holds itself (its units); each level, what stands on it.
67
+ for (const holder of [building, ...queryNodes(scene, { type: 'level', parentId: building.id })]) {
68
+ const written = holder.metadata?.authorSceneWritten;
69
+ if (!written)
70
+ continue;
71
+ Object.assign(ids, written.keys);
72
+ for (const id of written.ids) {
73
+ const node = scene[id];
74
+ nodes[id] = node?.metadata?.authorSceneBase ?? DELETED;
75
+ }
76
+ }
77
+ return { seed: state.seed, ids, nodes };
78
+ }
24
79
  const JOIN_REACH = 0.3;
80
+ /** The deepest floor between storeys: a wall stopping this far under the floor above carries it. */
81
+ const FLOOR_DEPTH = 0.5;
25
82
  const THIN_SLAB = 0.02;
26
83
  const MANY_SLABS = 20;
27
84
  const MANY_REGIONS = 24;
28
85
  const HEX = /^#[0-9a-f]{6}$/i;
29
- const GLASS = 'library:preset-glass';
30
86
  /**
31
87
  * Explicit payload keys retain their identities across revisions; derived nodes use the
32
88
  * conversion's retained seed. A changed payload never remints every existing wall or item.
@@ -52,6 +108,17 @@ function payloadSeed(payload) {
52
108
  }
53
109
  const dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
54
110
  const metres = (value) => `${value.toFixed(2)} m`;
111
+ /** The value given most often, the first one on a tie; undefined for none. */
112
+ const mostCommon = (values) => {
113
+ const counts = new Map();
114
+ for (const value of values)
115
+ counts.set(value, (counts.get(value) ?? 0) + 1);
116
+ let best;
117
+ for (const [value, count] of counts)
118
+ if (!best || count > counts.get(best))
119
+ best = value;
120
+ return best;
121
+ };
55
122
  /**
56
123
  * Where a wall end meets another wall's centerline: the projection onto that line, as long as it
57
124
  * lands on the wall or within reach past its ends. The line, not the segment, so a partition
@@ -65,6 +132,37 @@ function nearestOnSegment(p, a, b) {
65
132
  const q = [a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1])];
66
133
  return { point: q, distance: dist(p, q) };
67
134
  }
135
+ /**
136
+ * The bend Pascal stores (`curveOffset`) for an arc from start to end through a plan point: the
137
+ * point's distance from the straight line, positive when it lies to the right looking from start
138
+ * to end (away from the wall's left normal).
139
+ */
140
+ function bendThrough(start, end, through) {
141
+ const [dx, dz] = [end[0] - start[0], end[1] - start[1]];
142
+ return ((through[0] - start[0]) * dz - (through[1] - start[1]) * dx) / (Math.hypot(dx, dz) || 1);
143
+ }
144
+ /** The same on a curved wall: the nearest point of its arc, so a partition meets the curve. */
145
+ function nearestOnWall(p, wall) {
146
+ if (!wall.spec.through)
147
+ return nearestOnSegment(p, wall.start, wall.end);
148
+ const arc = {
149
+ start: wall.start,
150
+ end: wall.end,
151
+ curveOffset: bendThrough(wall.start, wall.end, wall.spec.through),
152
+ };
153
+ let best = { point: [p[0], p[1]], distance: Number.POSITIVE_INFINITY };
154
+ for (const [a, b] of getWallCenterlineSegments(arc, 64)) {
155
+ const [dx, dz] = [b.x - a.x, b.y - a.y];
156
+ const t = ((p[0] - a.x) * dx + (p[1] - a.y) * dz) / (dx * dx + dz * dz || 1);
157
+ const q = [
158
+ a.x + Math.max(0, Math.min(1, t)) * dx,
159
+ a.y + Math.max(0, Math.min(1, t)) * dz,
160
+ ];
161
+ if (dist(p, q) < best.distance)
162
+ best = { point: q, distance: dist(p, q) };
163
+ }
164
+ return best;
165
+ }
68
166
  const cross = (o, a, b) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
69
167
  /** Whether two segments cross each other's interior (touching ends and shared edges do not). */
70
168
  function segmentsCross(a, b, c, d) {
@@ -83,6 +181,8 @@ function polygonsOverlap(a, b) {
83
181
  }
84
182
  /** A wall's footprint, pulled in by a few millimetres so a slab meeting its face is not on it. */
85
183
  function wallFootprint(wall) {
184
+ if (wall.curveOffset)
185
+ return getWallSurfacePolygon({ ...wall, thickness: wall.thickness - 0.01 }).map(({ x, y }) => [x, y]);
86
186
  const inset = 0.005;
87
187
  const [dx, dz] = [wall.end[0] - wall.start[0], wall.end[1] - wall.start[1]];
88
188
  const len = Math.hypot(dx, dz) || 1;
@@ -106,7 +206,31 @@ function toLocal(frame, world) {
106
206
  const [c, s] = [Math.cos(frame.yaw), Math.sin(frame.yaw)];
107
207
  return [c * dx - s * dz, dy, s * dx + c * dz];
108
208
  }
109
- const MATTE = { roughness: 0.9, metalness: 0 };
209
+ /**
210
+ * A payload light's beam in its host's frame (so it turns with the host), or none for a point
211
+ * light. A target on the light itself points it down.
212
+ */
213
+ function lightSpot(light, at, host) {
214
+ if (!light.direction && !light.target && light.angle === undefined)
215
+ return undefined;
216
+ let [x, y, z] = light.target
217
+ ? [light.target[0] - at[0], light.target[1] - at[1], light.target[2] - at[2]]
218
+ : (light.direction ?? [0, -1, 0]);
219
+ let length = Math.hypot(x, y, z);
220
+ if (length < 1e-6)
221
+ [x, y, z, length] = [0, -1, 0, 1];
222
+ const [c, s] = [Math.cos(host.yaw), Math.sin(host.yaw)];
223
+ const round = (v) => Math.round(v * 1e4) / 1e4;
224
+ return {
225
+ direction: [
226
+ round((c * x - s * z) / length),
227
+ round(y / length),
228
+ round((s * x + c * z) / length),
229
+ ],
230
+ angle: light.angle ?? 60,
231
+ softness: light.softness ?? 0.4,
232
+ };
233
+ }
110
234
  /**
111
235
  * The finish a payload value writes. A library or scene ref passes through; a hex colour is kept
112
236
  * as an exact native material, one scene material per distinct colour and surface across the
@@ -127,25 +251,13 @@ function finishPool(context, say) {
127
251
  return { ref: value };
128
252
  if (!HEX.test(value))
129
253
  return { error: `${value} is no colour: give #rrggbb or library:<id>.` };
130
- const hex = value.toLowerCase();
131
254
  if (!materials) {
132
255
  if (!snapped)
133
256
  say('this host keeps no scene materials: colours written as the nearest library colour');
134
257
  snapped = true;
135
- return { ref: nearestLibraryColorRef(hex).ref };
258
+ return { ref: nearestLibraryColorRef(value).ref };
136
259
  }
137
- const resolved = resolveInlinePaintMaterial(materials, {
138
- properties: {
139
- color: hex,
140
- roughness: surface.roughness,
141
- metalness: surface.metalness,
142
- opacity: 1,
143
- transparent: false,
144
- side: 'front',
145
- },
146
- }, surface.roughness === MATTE.roughness && surface.metalness === MATTE.metalness
147
- ? hex
148
- : `${hex}, roughness ${+surface.roughness.toFixed(2)}, metalness ${+surface.metalness.toFixed(2)}`);
260
+ const resolved = exactColorMaterial(materials, value, surface);
149
261
  if (resolved.created) {
150
262
  materials[resolved.sceneMaterial.id] = resolved.sceneMaterial;
151
263
  created.push(resolved.sceneMaterial);
@@ -166,8 +278,13 @@ export default function build({ THREE }) {
166
278
  }`;
167
279
  /** Candidate modules, compiled by the host before room derivation decides which points are used. */
168
280
  export function authorSceneCeilingPoints(payload) {
169
- return payload.levels.flatMap((level, index) => level.lights.flatMap((light, i) => light.fixture ? [] : [{ id: `light/${index}/${i}`, code: CEILING_POINT_CODE }]));
281
+ return payload.levels.flatMap((level, index) => [
282
+ ...level.lights.flatMap((light, i) => light.fixture ? [] : [{ id: `light/${index}/${i}`, code: CEILING_POINT_CODE }]),
283
+ // A room left with no light gets one at its seed.
284
+ ...level.rooms.map((_, i) => ({ id: roomLightId(index, i), code: CEILING_POINT_CODE })),
285
+ ]);
170
286
  }
287
+ const roomLightId = (level, room) => `light/${level}/room/${room}`;
171
288
  /**
172
289
  * The rooms whose floor finish is a module, by their zone once the write made it: the host reads
173
290
  * each room's floor (`floorScriptInputs`), compiles the module for it and writes it with
@@ -181,12 +298,27 @@ export function authorSceneFloorScripts(payload, report) {
181
298
  return room.floorCode && zoneId ? [{ roomId, zoneId, code: room.floorCode }] : [];
182
299
  }));
183
300
  }
301
+ /**
302
+ * The slabs whose surface finish is a module, by their node once the write made them: the host
303
+ * reads each slab's surface (`slabSurfaceScriptInputs`), compiles the module for it and writes it
304
+ * with `setSlabSurfaceScript`, as it does a room's floor module.
305
+ */
306
+ export function authorSceneSlabScripts(payload, report) {
307
+ return payload.levels.flatMap((level, index) => (level.slabs ?? []).flatMap((slab, i) => {
308
+ const slabId = `slab/${index}/${i}`;
309
+ const nodeId = report.ids[slabId];
310
+ return slab.surfaceCode && nodeId
311
+ ? [{ slabId, nodeId, name: slab.name ?? slabId, code: slab.surfaceCode }]
312
+ : [];
313
+ }));
314
+ }
184
315
  /**
185
316
  * Which faces the payload's face modules build on, and why the others cannot be: a wall's own
186
- * `faceLeft` (face a) and `faceRight` (face b), then each room's `wallCode` on the faces its floor
187
- * meets (the spans of the room the zone was derived from, the faces `setWallSkirting` runs along),
188
- * where a wall's own module wins. A face with nothing to build on (a curved or curtain wall) or
189
- * that no room looks into is a `no_face` defect.
317
+ * `faceLeft` (face a) and `faceRight` (face b), its `faceOutside` on the faces no room of its level
318
+ * looks into, then each room's `wallCode` on the faces its floor meets (the spans of the room the
319
+ * zone was derived from, the faces `setWallSkirting` runs along), where a wall's own module wins.
320
+ * A face with nothing to build on (a curved or curtain wall), a left or right face no room looks
321
+ * into, or an outside module on a wall with rooms on both faces is a `no_face` defect.
190
322
  */
191
323
  function resolveFaceScripts(payload, ids, nodes) {
192
324
  const scripts = [];
@@ -225,6 +357,27 @@ function resolveFaceScripts(payload, ids, nodes) {
225
357
  else
226
358
  add(spec.id, face, nodeId, module.code, module.toHeight);
227
359
  }
360
+ const outside = spec.faceOutside;
361
+ if (!outside)
362
+ continue;
363
+ if (!wallFaceScriptInputs(nodes, nodeId, 'a', outside.toHeight)) {
364
+ defects.push({
365
+ id: `${spec.id}/outside`,
366
+ code: 'no_face',
367
+ message: `Wall ${spec.id} ${flat}: its outside face has nothing a script can build on and keeps its paint.`,
368
+ });
369
+ continue;
370
+ }
371
+ const cover = roomFaceCover(nodes, nodeId);
372
+ const faces = ['a', 'b'].filter((face) => cover[face] === 'none');
373
+ if (!faces.length)
374
+ defects.push({
375
+ id: `${spec.id}/outside`,
376
+ code: 'no_face',
377
+ message: `Rooms look into both faces of wall ${spec.id}: it has no outside face, so its faceOutside was not written.`,
378
+ });
379
+ for (const face of faces)
380
+ add(spec.id, face, nodeId, outside.code, outside.toHeight);
228
381
  }
229
382
  const byRoom = new Map();
230
383
  for (const [i, room] of level.rooms.entries()) {
@@ -232,7 +385,10 @@ function resolveFaceScripts(payload, ids, nodes) {
232
385
  const zone = nodes[ids[roomId] ?? ''];
233
386
  if (!room.wallCode || zone?.type !== 'zone')
234
387
  continue;
235
- const boundaries = Object.values(nodes).filter((n) => n.parentId === zone.parentId && (n.type === 'wall' || n.type === 'separator'));
388
+ const boundaries = queryNodes(nodes, {
389
+ type: ['wall', 'separator'],
390
+ parentId: zone.parentId ?? null,
391
+ });
236
392
  const zoneWalls = [...zone.boundaryWallIds].sort().join();
237
393
  const derived = floorRoomFaces(boundaries).find((r) => [...new Set(r.spans.filter((s) => s.kind === 'wall').map((s) => s.boundaryId))]
238
394
  .sort()
@@ -347,6 +503,8 @@ export function authorScene(scene, input, context) {
347
503
  ['slab', level.slabs],
348
504
  ['column', level.columns],
349
505
  ['light', level.lights],
506
+ ['stair', level.stairs],
507
+ ['void', level.voids],
350
508
  ])
351
509
  parts?.forEach((_, i) => {
352
510
  claimSourceKey(`${kind}/${index}/${i}`);
@@ -360,6 +518,10 @@ export function authorScene(scene, input, context) {
360
518
  for (const item of level.items ?? [])
361
519
  claimSourceKey(item.id);
362
520
  });
521
+ for (const item of payload.roof?.items ?? [])
522
+ claimSourceKey(item.id);
523
+ for (const unit of payload.units)
524
+ claimSourceKey(`unit/${unit.name}`);
363
525
  const buildings = Object.values(scene).filter((n) => n.type === 'building');
364
526
  const selected = input.buildingId ?? (context.activeLevelId ? scene[context.activeLevelId]?.parentId : undefined);
365
527
  const building = selected
@@ -370,14 +532,7 @@ export function authorScene(scene, input, context) {
370
532
  if (!building)
371
533
  refuse('no_building', 'The scene has no building to put the levels in.');
372
534
  const existingLevels = Object.values(scene).filter((n) => n.type === 'level' && n.parentId === building.id);
373
- const saved = building.metadata?.authorSceneState;
374
- const previous = saved?.version === 1 &&
375
- saved.buildingId === building.id &&
376
- saved.nodes &&
377
- saved.ids &&
378
- typeof saved.seed === 'string'
379
- ? saved
380
- : undefined;
535
+ const previous = authoredBaseline(scene, building);
381
536
  const converted = Boolean(previous) || existingLevels.some((l) => l.metadata?.authorScene);
382
537
  if (converted && !input.replace)
383
538
  refuse('conversion_exists', 'This scene already holds a conversion. Pass replace: true to write over it.');
@@ -392,11 +547,16 @@ export function authorScene(scene, input, context) {
392
547
  walls: 0,
393
548
  doors: 0,
394
549
  windows: 0,
550
+ passages: 0,
395
551
  rooms: 0,
396
552
  roomsNamed: 0,
397
553
  slabs: 0,
398
554
  columns: 0,
399
555
  items: 0,
556
+ stairs: 0,
557
+ voids: 0,
558
+ roofSegments: 0,
559
+ units: 0,
400
560
  lights: 0,
401
561
  floorScripts: 0,
402
562
  faceScripts: 0,
@@ -421,23 +581,7 @@ export function authorScene(scene, input, context) {
421
581
  }
422
582
  return got.ref;
423
583
  };
424
- const exactSlots = (compiled, id) => {
425
- const slots = matchScriptSlotsToLibrary(compiled.manifest);
426
- for (const slot of compiled.manifest.slots) {
427
- // Glass keeps the glass add_object writes, drawn see-through and letting light pass. A
428
- // scene material carries no emission, so an emissive slot keeps the module's own material
429
- // (the library match leaves it alone) and a bulb stays lit.
430
- if (!slot.color || slot.emissive || slots[slot.id] === GLASS)
431
- continue;
432
- const ref = finishRef(slot.color, id, {
433
- roughness: slot.roughness ?? MATTE.roughness,
434
- metalness: slot.metalness ?? MATTE.metalness,
435
- });
436
- if (ref)
437
- slots[slot.id] = ref;
438
- }
439
- return slots;
440
- };
584
+ const exactSlots = (compiled, id) => exactScriptSlots(compiled.manifest, matchScriptSlotsToLibrary(compiled.manifest), (hex, surface) => finishRef(hex, id, surface));
441
585
  const paintContext = (levelId) => ({
442
586
  ...context,
443
587
  activeLevelId: levelId,
@@ -452,11 +596,20 @@ export function authorScene(scene, input, context) {
452
596
  }
453
597
  const scaffoldIds = new Set(Object.keys(draft));
454
598
  const levelNodes = [];
599
+ const levelWalls = [];
455
600
  const itemNodeIds = new Map();
456
601
  const itemFrames = new Map();
457
602
  const skirtings = [];
458
603
  let untypedWindows = 0;
604
+ /** By upper level index: the storey under it and the height of the tallest wall carrying it. */
605
+ const floorsAbove = new Map();
459
606
  payload.levels.forEach((level, index) => {
607
+ // A storey is as tall as the climb to the next level's floor; the top one, as its walls.
608
+ const above = payload.levels[index + 1];
609
+ const walled = Math.max(0, ...(level.walls ?? []).map((w) => w.height)) || DEFAULT_LEVEL_HEIGHT;
610
+ if (above && above.elevation <= level.elevation)
611
+ say(`"${above.name}" (elevation ${metres(above.elevation)}) is not above "${level.name}" (${metres(level.elevation)}): "${level.name}" is as tall as its walls, ${metres(walled)}`);
612
+ const storey = above && above.elevation > level.elevation ? above.elevation - level.elevation : 0;
460
613
  const levelNode = LevelNode.parse({
461
614
  object: 'node',
462
615
  type: 'level',
@@ -466,6 +619,7 @@ export function authorScene(scene, input, context) {
466
619
  metadata: { authorScene: { elevation: level.elevation } },
467
620
  name: level.name,
468
621
  level: index,
622
+ height: storey || walled,
469
623
  children: [],
470
624
  });
471
625
  draft[levelNode.id] = levelNode;
@@ -476,11 +630,26 @@ export function authorScene(scene, input, context) {
476
630
  levelNode.children.push(node.id);
477
631
  };
478
632
  // ---------- walls, joined ----------
633
+ // Walls keep their height; the floor above is built between the storeys, from the tallest wall
634
+ // carrying it up to its surface. A wall reaching into that floor stops under it, following the
635
+ // storey as a drawn wall does.
636
+ const reaches = (height) => storey > 0 && height > storey - MIN_SLAB_THICKNESS;
479
637
  const walls = (level.walls ?? []).map((w) => ({
480
638
  spec: w,
481
639
  start: [...w.start],
482
640
  end: [...w.end],
641
+ height: reaches(w.height) ? storey : w.height,
483
642
  }));
643
+ const carrying = walls
644
+ .map((w) => w.spec.height)
645
+ .filter((height) => storey > 0 && !reaches(height) && height >= storey - FLOOR_DEPTH);
646
+ if (carrying.length)
647
+ floorsAbove.set(index + 1, { below: level.name, wallHeight: Math.max(...carrying), storey });
648
+ const stopped = [
649
+ ...new Set(walls.filter((w) => w.height !== w.spec.height).map((w) => w.spec.height)),
650
+ ];
651
+ if (stopped.length)
652
+ say(`walls of "${level.name}" given ${stopped.map(metres).join(', ')} reach the floor of "${above.name}", ${metres(storey)} up: they stop under it, following the storey`);
484
653
  for (const w of walls) {
485
654
  for (const end of ['start', 'end']) {
486
655
  const pt = w[end];
@@ -488,7 +657,7 @@ export function authorScene(scene, input, context) {
488
657
  for (const o of walls) {
489
658
  if (o === w)
490
659
  continue;
491
- const near = nearestOnSegment(pt, o.start, o.end);
660
+ const near = nearestOnWall(pt, o);
492
661
  if (near.distance < JOIN_REACH && (!best || near.distance < best.distance))
493
662
  best = near;
494
663
  }
@@ -508,6 +677,13 @@ export function authorScene(scene, input, context) {
508
677
  defect(w.spec.id, 'wall_too_short', 'The wall is shorter than 5 cm after joining.');
509
678
  continue;
510
679
  }
680
+ const bend = w.spec.through ? bendThrough(w.start, w.end, w.spec.through) : 0;
681
+ const curve = bend ? normalizeWallCurveOffset(w, bend) : 0;
682
+ if (w.spec.through && !curve)
683
+ say(`wall ${w.spec.id}: its through point is on the straight line: written straight`);
684
+ else if (Math.abs(bend) > getMaxWallCurveOffset(w) + 1e-9)
685
+ say(`wall ${w.spec.id}: its through point stands ${metres(Math.abs(bend))} off the straight line, more than half its ${metres(dist(w.start, w.end))} span: written as a half circle, ${metres(Math.abs(curve))} out`);
686
+ const { curtain } = w.spec;
511
687
  const node = WallNode.parse({
512
688
  object: 'node',
513
689
  type: 'wall',
@@ -518,8 +694,20 @@ export function authorScene(scene, input, context) {
518
694
  children: [],
519
695
  start: w.start,
520
696
  end: w.end,
521
- height: w.spec.height,
697
+ ...(w.height === w.spec.height ? { height: w.spec.height } : {}),
522
698
  thickness: w.spec.thickness,
699
+ ...(curve ? { curveOffset: curve } : {}),
700
+ ...(curtain
701
+ ? {
702
+ wallType: 'curtain',
703
+ curtainWall: CurtainWallConfig.parse({
704
+ columns: {
705
+ layout: 'maximum-spacing',
706
+ spacing: Math.min(100, Math.max(0.2, curtain.spacing)),
707
+ },
708
+ }),
709
+ }
710
+ : {}),
523
711
  });
524
712
  place(node);
525
713
  wallNodes.set(w.spec.id, { ...w, node });
@@ -532,13 +720,20 @@ export function authorScene(scene, input, context) {
532
720
  });
533
721
  report.ids[w.spec.id] = node.id;
534
722
  report.walls++;
535
- // ---------- its openings ----------
536
- const length = dist(w.start, w.end);
723
+ // ---------- its openings: along the arc on a curved wall, as Pascal places them ----------
724
+ const length = curve ? getWallCurveLength(node) : dist(w.start, w.end);
537
725
  (w.spec.openings ?? []).forEach((o, i) => {
538
726
  const id = `${w.spec.id}/${i}`;
727
+ const passage = o.kind === 'passage';
728
+ const kind = passage ? 'door' : o.kind;
729
+ if (passage) {
730
+ const leaf = ['type', 'style', 'leaves', 'hinge', 'swing', 'code'].filter((field) => o[field] !== undefined);
731
+ if (leaf.length)
732
+ say(`passage ${id}: it has no leaf, its ${leaf.join(', ')} ignored`);
733
+ }
539
734
  // A module supplies geometry; the declared type still describes how the opening works.
540
735
  let compiled;
541
- if (o.code) {
736
+ if (o.code && !passage) {
542
737
  compiled = input.compiled[id];
543
738
  if (!compiled) {
544
739
  defect(id, 'compile_failed', input.compileErrors?.[id] ?? 'The module did not compile.');
@@ -560,24 +755,54 @@ export function authorScene(scene, input, context) {
560
755
  say(`${o.kind} ${id}: style ignored, its module builds it`);
561
756
  }
562
757
  const [width, height] = compiled ? scriptedSize(compiled.manifest) : [o.width, o.height];
563
- if (width > length + 0.01) {
758
+ const size = o.kind === 'door' && !compiled
759
+ ? getDoorOpeningSize({
760
+ ...o,
761
+ width,
762
+ height,
763
+ doorType: o.type && isDoorType(o.type) ? o.type : 'hinged',
764
+ })
765
+ : { width, height, leafOffsetX: 0 };
766
+ if (size.width > length + 0.01) {
564
767
  defect(id, 'opening_wider_than_wall', `${o.kind} ${width.toFixed(2)} m wide on a ${length.toFixed(2)} m wall.`);
565
768
  return;
566
769
  }
567
- const x = Math.min(Math.max(o.along, width / 2), length - width / 2);
568
- if (Math.abs(x - o.along) > 0.01)
569
- say(`${o.kind} ${id}: moved ${Math.abs(x - o.along).toFixed(2)} m along the wall to fit inside it`);
770
+ if (o.kind === 'door') {
771
+ const wallHeight = resolveWallOpeningCeiling(node, draft) -
772
+ (node.wallType === 'curtain' ? getCurtainWallConfig(node).perimeterWidth : 0);
773
+ if (o.sill + size.height > wallHeight + 1e-6) {
774
+ defect(id, 'wall_too_low', `Wall ${w.spec.id} is ${metres(wallHeight)} high, too low for a ${metres(size.height)} door at sill ${metres(o.sill)}.`);
775
+ return;
776
+ }
777
+ }
778
+ // A door or window is flat, set on the arc at its centre: its ends leave the curve.
779
+ if (curve) {
780
+ const chord = dist(w.start, w.end);
781
+ const radius = (chord * chord) / (8 * Math.abs(curve)) + Math.abs(curve) / 2;
782
+ const standoff = radius - Math.sqrt(Math.max(0, radius ** 2 - (size.width / 2) ** 2));
783
+ const half = w.spec.thickness / 2;
784
+ if (standoff > half)
785
+ warn(id, 'flat_on_arc', `The ${metres(width)} ${o.kind} is flat on wall ${w.spec.id}, an arc of radius ${metres(radius)}: its ends stand ${metres(standoff)} off the curve, past the wall's faces. Set narrower ones side by side, at most ${metres(2 * Math.sqrt(radius ** 2 - (radius - half) ** 2))} wide.`);
786
+ }
787
+ const centre = Math.min(Math.max(o.along, size.width / 2), length - size.width / 2);
788
+ const x = centre + size.leafOffsetX;
789
+ if (Math.abs(centre - o.along) > 0.01)
790
+ say(`${o.kind} ${id}: moved ${Math.abs(centre - o.along).toFixed(2)} m along the wall to fit inside it`);
570
791
  const common = {
571
792
  object: 'node',
572
- id: mint(o.kind, id),
793
+ id: mint(kind, id),
573
794
  parentId: node.id,
574
795
  visible: true,
575
796
  metadata: { authorScene: { id, color: o.color ?? null } },
576
797
  wallId: node.id,
798
+ casingFaceA: true,
799
+ casingFaceB: true,
577
800
  width,
578
801
  height,
579
802
  rotation: [0, 0, 0],
580
803
  ...(o.name ? { name: o.name } : {}),
804
+ ...(o.shape ? { openingShape: o.shape } : {}),
805
+ ...(o.archHeight === undefined ? {} : { archHeight: Math.min(o.archHeight, height) }),
581
806
  ...(compiled
582
807
  ? {
583
808
  source: scriptSource(compiled, { category: o.kind }),
@@ -586,7 +811,17 @@ export function authorScene(scene, input, context) {
586
811
  : {}),
587
812
  };
588
813
  let opening;
589
- if (o.kind === 'door') {
814
+ if (passage) {
815
+ opening = DoorNode.parse({
816
+ ...common,
817
+ type: 'door',
818
+ frameThickness: NEW_DOOR_FRAME_THICKNESS,
819
+ position: [x, o.sill + height / 2, 0],
820
+ openingKind: 'opening',
821
+ });
822
+ report.passages++;
823
+ }
824
+ else if (o.kind === 'door') {
590
825
  const type = o.type;
591
826
  if (type && !isDoorType(type))
592
827
  defect(id, 'type_mismatch', `${type} is a window type; the door is written hinged.`);
@@ -600,12 +835,19 @@ export function authorScene(scene, input, context) {
600
835
  opening = DoorNode.parse({
601
836
  ...common,
602
837
  type: 'door',
603
- position: [x, height / 2, 0],
838
+ frameThickness: NEW_DOOR_FRAME_THICKNESS,
839
+ position: [x, o.sill + height / 2, 0],
604
840
  ...(type && isDoorType(type) ? doorTypeFields(type) : {}),
605
841
  ...(style && isDoorStyle(style) ? doorStyleLook(style) : {}),
606
842
  ...(o.leaves ? { leafCount: o.leaves } : {}),
843
+ sidelightLeft: o.sidelightLeft,
844
+ sidelightRight: o.sidelightRight,
845
+ transomHeight: o.transomHeight,
846
+ slidingStyle: o.slidingStyle,
847
+ ...(o.slideDirection ? { slideDirection: o.slideDirection } : {}),
607
848
  hingesSide,
608
849
  handleSide,
850
+ handleStyle: o.handleStyle,
609
851
  ...(o.swing ? { swingDirection: o.swing === 'back' ? 'outward' : 'inward' } : {}),
610
852
  });
611
853
  report.doors++;
@@ -626,13 +868,35 @@ export function authorScene(scene, input, context) {
626
868
  opening = WindowNode.parse({
627
869
  ...common,
628
870
  type: 'window',
871
+ hingesSide: o.hingesSide ?? (o.hinge === 'end' ? 'right' : 'left'),
872
+ sashGrid: !windowTakesStyle(windowType ?? 'fixed') && (o.columns || o.rows) ? true : o.sashGrid,
873
+ windowHandle: o.windowHandle,
874
+ sillInterior: o.sillInterior,
875
+ shutters: o.shutters,
876
+ shutterWidth: o.shutterWidth,
877
+ tiltTurnMode: o.tiltTurnMode,
878
+ sillOverhang: o.sillOverhang,
879
+ sillInteriorDepth: o.sillInteriorDepth,
880
+ sillInteriorThickness: o.sillInteriorThickness,
881
+ sillInteriorOverhang: o.sillInteriorOverhang,
882
+ shutterOpen: o.shutterOpen,
883
+ shutterSides: o.shutterSides,
884
+ rollerShutter: o.rollerShutter,
885
+ rollerShutterBoxHeight: o.rollerShutterBoxHeight,
886
+ rollerShutterOpen: o.rollerShutterOpen,
887
+ frameThickness: NEW_WINDOW_FRAME_THICKNESS,
629
888
  position: [x, o.sill + height / 2, 0],
630
889
  ...(windowType ? windowTypeFields(windowType) : {}),
890
+ ...(o.openingDirection === undefined ? {} : { openingDirection: o.openingDirection }),
631
891
  ...(style && isWindowStyle(style) && takesStyle ? getWindowStyleOverrides(style) : {}),
892
+ ...(o.columns
893
+ ? { columnRatios: Array.from({ length: o.columns }, () => 1 / o.columns) }
894
+ : {}),
895
+ ...(o.rows ? { rowRatios: Array.from({ length: o.rows }, () => 1 / o.rows) } : {}),
632
896
  });
633
897
  report.windows++;
634
898
  }
635
- const panel = o.kind === 'door' ? finishRef(o.color, id) : undefined;
899
+ const panel = kind === 'door' ? finishRef(o.color, id) : undefined;
636
900
  const frame = finishRef(o.frameColor ?? o.color, id);
637
901
  if (panel || frame)
638
902
  opening = {
@@ -658,10 +922,14 @@ export function authorScene(scene, input, context) {
658
922
  const polygon = s.polygon;
659
923
  // A slab over a wall's footprint, higher than half the wall: a cap, which would lift the
660
924
  // wall onto it. Pascal caps its walls itself.
661
- const lifted = [...wallNodes.values()].find((w) => s.top > w.spec.height / 2 &&
662
- polygonsOverlap(polygon, wallFootprint({ ...w, thickness: w.spec.thickness })));
925
+ const lifted = [...wallNodes.values()].find((w) => s.top > w.height / 2 &&
926
+ polygonsOverlap(polygon, wallFootprint({
927
+ ...w,
928
+ thickness: w.spec.thickness,
929
+ curveOffset: w.node.curveOffset,
930
+ })));
663
931
  if (lifted) {
664
- defect(id, 'slab_over_wall', `${label} overlaps wall ${lifted.spec.id} with its top at ${metres(s.top)}, above half the wall's ${metres(lifted.spec.height)}: it would lift the wall onto it. Pascal builds a wall's cap itself; fold it into the wall.`);
932
+ defect(id, 'slab_over_wall', `${label} overlaps wall ${lifted.spec.id} with its top at ${metres(s.top)}, above half the wall's ${metres(lifted.height)}: it would lift the wall onto it. Pascal builds a wall's cap itself; fold it into the wall.`);
665
933
  return;
666
934
  }
667
935
  if (s.thickness < THIN_SLAB)
@@ -679,35 +947,527 @@ export function authorScene(scene, input, context) {
679
947
  elevation: s.top,
680
948
  thickness: s.thickness,
681
949
  ...(finish ? { slots: { surface: finish } } : {}),
682
- ...(s.railing?.length
683
- ? { railing: s.railing.map((r) => ({ start: r.start, end: r.end })) }
684
- : {}),
685
950
  });
686
951
  place(node);
687
952
  report.ids[id] = node.id;
688
953
  report.slabs++;
954
+ (s.railing ?? []).forEach((r, k) => {
955
+ const railId = `${id}/railing/${k}`;
956
+ let fence = FenceNode.parse({
957
+ object: 'node',
958
+ id: mint('fence', railId),
959
+ parentId: levelNode.id,
960
+ visible: true,
961
+ metadata: { authorScene: { id: railId } },
962
+ name: 'Railing',
963
+ start: r.start,
964
+ end: r.end,
965
+ height: r.height,
966
+ style: 'rail',
967
+ baseStyle: 'floating',
968
+ baseHeight: 0,
969
+ postCap: 'none',
970
+ supportSlabId: node.id,
971
+ });
972
+ const railFinish = finishRef(r.color, railId);
973
+ if (railFinish)
974
+ fence = {
975
+ ...fence,
976
+ slots: Object.fromEntries((nodeSlotDeclarations(fence) ?? []).map((slot) => [slot.slotId, railFinish])),
977
+ };
978
+ place(fence);
979
+ });
689
980
  });
690
981
  (level.columns ?? []).forEach((c, i) => {
691
- const node = ColumnNode.parse({
982
+ const id = `column/${index}/${i}`;
983
+ let compiled;
984
+ if (c.code) {
985
+ compiled = input.compiled[id];
986
+ if (!compiled) {
987
+ defect(id, 'compile_failed', input.compileErrors?.[id] ?? 'The module did not compile.');
988
+ return;
989
+ }
990
+ if (compiled.mount !== 'floor') {
991
+ defect(id, 'wrong_mount', `A column's module uses mount 'floor', not '${compiled.mount}'.`);
992
+ return;
993
+ }
994
+ try {
995
+ requireMeasured(compiled, { label: c.name ?? id, tool: 'author_scene' });
996
+ }
997
+ catch (error) {
998
+ const e = error;
999
+ defect(id, e.code ?? 'unmeasurable_object', e.message ?? String(error));
1000
+ return;
1001
+ }
1002
+ if (c.color)
1003
+ say(`column ${id}: built by its module, which keeps its own colours; color ignored`);
1004
+ }
1005
+ const size = compiled && scriptedSize(compiled.manifest);
1006
+ let node = ColumnNode.parse({
692
1007
  object: 'node',
693
1008
  type: 'column',
694
- id: mint('column', `column/${index}/${i}`),
1009
+ id: mint('column', id),
695
1010
  parentId: levelNode.id,
696
1011
  visible: true,
697
- metadata: { authorScene: { color: c.color ?? null } },
1012
+ metadata: { authorScene: { id } },
698
1013
  ...(c.name ? { name: c.name } : {}),
699
1014
  position: [c.position[0], 0, c.position[1]],
700
- crossSection: 'square',
701
- width: c.width,
702
- depth: c.depth,
703
- height: c.height,
704
- baseHeight: 0,
705
- capitalHeight: 0,
1015
+ rotation: (c.rotation * Math.PI) / 180,
1016
+ ...(compiled && size
1017
+ ? {
1018
+ source: scriptSource(compiled, { category: 'column' }),
1019
+ slots: exactSlots(compiled, id),
1020
+ width: size[0],
1021
+ height: size[1],
1022
+ depth: size[2],
1023
+ }
1024
+ : {
1025
+ crossSection: c.shape,
1026
+ ...(c.shape === 'round' ? { radius: c.width / 2 } : {}),
1027
+ width: c.width,
1028
+ depth: c.shape === 'round' ? c.width : (c.depth ?? c.width),
1029
+ height: c.height,
1030
+ baseHeight: 0,
1031
+ capitalHeight: 0,
1032
+ }),
706
1033
  });
1034
+ const finish = !compiled && finishRef(c.color, id);
1035
+ if (finish)
1036
+ node = {
1037
+ ...node,
1038
+ slots: Object.fromEntries((nodeSlotDeclarations(node) ?? []).map((slot) => [slot.slotId, finish])),
1039
+ };
707
1040
  place(node);
708
- report.ids[`column/${index}/${i}`] = node.id;
1041
+ report.ids[id] = node.id;
709
1042
  report.columns++;
710
1043
  });
1044
+ levelWalls.push({ walls, wallNodes });
1045
+ });
1046
+ report.levels = levelNodes.length;
1047
+ if (untypedWindows)
1048
+ say(`${untypedWindows} window${untypedWindows === 1 ? ' has' : 's have'} no type: written fixed (cannot open); give type`);
1049
+ // ---------- which face of each wall looks into a room: read from the plan, as the editor does ----------
1050
+ for (const levelNode of levelNodes) {
1051
+ const boundaries = Object.values(draft).filter((n) => n.parentId === levelNode.id && (n.type === 'wall' || n.type === 'separator'));
1052
+ for (const { wallId, ...sides } of detectSpacesForLevel(levelNode.id, boundaries).wallUpdates)
1053
+ draft[wallId] = { ...draft[wallId], ...sides };
1054
+ }
1055
+ // ---------- rooms: Pascal's own derivation, then the payload's names and floors ----------
1056
+ const { nodes: withZones, createdZoneIds } = migrateRoomZones(draft);
1057
+ const zones = createdZoneIds.map((id) => ZoneNode.parse(withZones[id]));
1058
+ for (const zone of zones) {
1059
+ draft[zone.id] = zone;
1060
+ const level = draft[zone.parentId];
1061
+ draft[zone.parentId] = { ...level, children: [...level.children, zone.id] };
1062
+ report.rooms++;
1063
+ }
1064
+ const joined = new Map();
1065
+ const roomIds = new Map();
1066
+ payload.levels.forEach((level, index) => {
1067
+ const levelNode = levelNodes[index];
1068
+ for (const [i, room] of level.rooms.entries()) {
1069
+ const id = `room/${index}/${i}`;
1070
+ roomIds.set(room, id);
1071
+ const zone = zones.find((z) => z.parentId === levelNode.id && pointInPolygon(room.seed, z.polygon));
1072
+ // Several seeds in one enclosure name one open-plan room, in the order given.
1073
+ const sharing = zone && joined.get(zone.id);
1074
+ if (sharing) {
1075
+ sharing.push(room);
1076
+ continue;
1077
+ }
1078
+ if (!zone) {
1079
+ defect(id, 'room_not_enclosed', `No closed room around (${room.seed[0]}, ${room.seed[1]}) "${room.name}": its walls do not meet.`);
1080
+ continue;
1081
+ }
1082
+ if (room.floorRegions.length > MANY_REGIONS)
1083
+ warn(id, 'many_regions', `"${room.name}" has ${room.floorRegions.length} floor regions: regions are fixed polygons that do not follow a resize; use one floor finish.`);
1084
+ const finish = finishRef(room.floor, id);
1085
+ const regions = room.floorRegions.map((r, k) => ({
1086
+ id: `region_${k}`,
1087
+ polygon: r.polygon,
1088
+ finish: finishRef(r.finish, id) ?? r.finish,
1089
+ }));
1090
+ draft[zone.id] = ZoneNode.parse({
1091
+ ...zone,
1092
+ name: room.name,
1093
+ spaceRole: 'room',
1094
+ ...(finish || regions.length
1095
+ ? {
1096
+ floor: {
1097
+ ...zone.floor,
1098
+ ...(finish ? { finish } : {}),
1099
+ ...(regions.length ? { regions } : {}),
1100
+ },
1101
+ }
1102
+ : {}),
1103
+ metadata: { ...zone.metadata, authorScene: { seed: room.seed } },
1104
+ });
1105
+ joined.set(zone.id, [room]);
1106
+ report.ids[id] = zone.id;
1107
+ report.roomsNamed++;
1108
+ }
1109
+ });
1110
+ for (const [zoneId, rooms] of joined) {
1111
+ if (rooms.length < 2)
1112
+ continue;
1113
+ const [first, ...rest] = rooms.map((room) => room.name);
1114
+ // "Kitchen" joins as "kitchen"; a name that is not a capitalised word ("WC") stays as given.
1115
+ const names = [
1116
+ first,
1117
+ ...rest.map((name) => name.replace(/^([A-Z])(?=[a-z])/, (c) => c.toLowerCase())),
1118
+ ];
1119
+ const name = `${names.slice(0, -1).join(', ')} and ${names.at(-1)}`;
1120
+ draft[zoneId] = { ...draft[zoneId], name };
1121
+ report.roomsNamed += rest.length;
1122
+ const floors = rooms
1123
+ .slice(1)
1124
+ .filter((room) => room.floor || room.floorCode || room.floorRegions.length);
1125
+ say(`${rooms.map((room) => `"${room.name}"`).join(', ')} are one enclosure, no wall between them: one open-plan room, "${name}"${floors.length ? `, with the floor of "${first}"` : ''}`);
1126
+ }
1127
+ // ---------- units: one per distinct name, under the building, holding the rooms that name it ----------
1128
+ const units = new Map();
1129
+ for (const unit of payload.units) {
1130
+ const color = unit.color && HEX.test(unit.color) ? unit.color : undefined;
1131
+ if (unit.color && !color)
1132
+ defect(`unit/${unit.name}`, 'invalid_color', `"${unit.color}" is not a #rrggbb colour.`);
1133
+ units.set(unit.name, { kind: unit.kind, color, zones: [] });
1134
+ }
1135
+ for (const [zoneId, rooms] of joined) {
1136
+ const names = [...new Set(rooms.flatMap((room) => (room.unit ? [room.unit] : [])))];
1137
+ // One enclosure is one room, so it is in one unit: seeds naming two want a wall between them.
1138
+ if (names.length > 1) {
1139
+ defect(roomIds.get(rooms[0]), 'unit_split_room', `${rooms.map((room) => `"${room.name}"`).join(', ')} are one open-plan room but name different units (${names.join(', ')}): the room is in none. Put a wall between them, or one unit on all of them.`);
1140
+ continue;
1141
+ }
1142
+ if (!names[0])
1143
+ continue;
1144
+ const entry = units.get(names[0]) ?? { kind: 'apartment', zones: [] };
1145
+ entry.zones.push(zoneId);
1146
+ units.set(names[0], entry);
1147
+ }
1148
+ for (const [name, { kind, color, zones: members }] of units) {
1149
+ const unit = UnitNode.parse({
1150
+ id: mint('unit', `unit/${name}`),
1151
+ parentId: building.id,
1152
+ name,
1153
+ kind,
1154
+ ...(color ? { color } : {}),
1155
+ members,
1156
+ });
1157
+ draft[unit.id] = unit;
1158
+ report.ids[`unit/${name}`] = unit.id;
1159
+ report.units++;
1160
+ if (!members.length)
1161
+ warn(`unit/${name}`, 'unit_no_rooms', `No room names unit "${name}".`);
1162
+ }
1163
+ // ---------- floors and ceilings, as the editor derives them on load ----------
1164
+ const levelIds = levelNodes.map((l) => l.id);
1165
+ let written = {
1166
+ ...reconcileSceneStructure({ nodes: draft, levelIds, mintId: (kind) => mint(kind) }).nodes,
1167
+ };
1168
+ for (const opening of Object.values(written)) {
1169
+ const wall = written[opening.parentId ?? ''];
1170
+ if (opening.type !== 'door' || wall?.type !== 'wall' || scaffoldIds.has(opening.id))
1171
+ continue;
1172
+ const facing = doorFacing(wall);
1173
+ written[opening.id] = {
1174
+ ...opening,
1175
+ ...facing,
1176
+ position: doorPositionForFacing(opening, facing.rotation),
1177
+ };
1178
+ }
1179
+ // A room's plate and ceiling are named by its room, whose id follows its walls: a revision keeps
1180
+ // an unchanged room's, and a room it adds brings its own with it (never another room's renamed).
1181
+ const derivedIds = new Map();
1182
+ for (const node of Object.values(written)) {
1183
+ const zones = node.type === 'ceiling' && node.zoneId
1184
+ ? [node.zoneId]
1185
+ : node.type === 'slab' && node.plateRole === 'base'
1186
+ ? [...(node.zoneIds ?? [])].sort()
1187
+ : [];
1188
+ if (zones.length && !scaffoldIds.has(node.id))
1189
+ derivedIds.set(node.id, mint(node.type, `${node.type}/${zones.join('+')}`));
1190
+ }
1191
+ if (derivedIds.size) {
1192
+ let json = JSON.stringify(written);
1193
+ for (const [from, to] of derivedIds)
1194
+ json = json.replaceAll(`"${from}"`, `"${to}"`);
1195
+ written = JSON.parse(json);
1196
+ }
1197
+ // ---------- the floor between storeys: each upper plate keeps its top and reaches down to the walls ----------
1198
+ for (const [index, { below, wallHeight, storey }] of floorsAbove) {
1199
+ const plates = (levelId) => Object.values(written).filter((node) => node.type === 'slab' && node.parentId === levelId && node.plateRole === 'base');
1200
+ // The carrying walls stand on the floor below; their tops, in this level's frame, are its underside.
1201
+ const lower = plates(levelNodes[index - 1].id);
1202
+ const underside = (lower.length ? Math.max(...lower.map((p) => p.elevation)) : 0) + wallHeight;
1203
+ // Seen from outside, the band continues the cladding under it: the colour most of those walls
1204
+ // wear outside paints the plate's edge and sides.
1205
+ const outsides = (payload.levels[index - 1].walls ?? []).flatMap((w) => w.colorOutside ? [w.colorOutside] : []);
1206
+ const cladding = mostCommon(outsides);
1207
+ const bandRef = cladding && finishRef(cladding, `level/${index}/floor`);
1208
+ const depths = new Set();
1209
+ for (const plate of plates(levelNodes[index].id)) {
1210
+ const thickness = roundFloorElevation(plate.elevation - (underside - storey));
1211
+ written[plate.id] = {
1212
+ ...plate,
1213
+ thickness,
1214
+ ...(bandRef ? { slots: { ...plate.slots, edge: bandRef, side: bandRef } } : {}),
1215
+ };
1216
+ depths.add(thickness);
1217
+ }
1218
+ if (depths.size)
1219
+ say(`"${payload.levels[index].name}" stands on a ${[...depths].map(metres).join(', ')} floor over the ${metres(wallHeight)} walls of "${below}": its plate reaches down to their tops`);
1220
+ }
1221
+ /**
1222
+ * The room a plan point of a level is in, its ceiling, and the height in that level's frame that
1223
+ * ceiling hangs things from. Under an opening in the ceiling (a void, a stairwell) it is the
1224
+ * ceiling above, the room's double height.
1225
+ */
1226
+ const ceilingAt = (index, x, z) => {
1227
+ const levelId = levelNodes[index].id;
1228
+ const room = Object.values(written).find((node) => node.type === 'zone' &&
1229
+ node.spaceRole === 'room' &&
1230
+ node.parentId === levelId &&
1231
+ pointInPolygon([x, z], node.polygon) &&
1232
+ !node.holes.some((hole) => pointInPolygon([x, z], hole)));
1233
+ const ceiling = room &&
1234
+ Object.values(written).find((node) => node.type === 'ceiling' && node.zoneId === room.id && node.parentId === levelId);
1235
+ if (!room || !ceiling)
1236
+ return undefined;
1237
+ if (levelNodes[index + 1] && ceiling.holes.some((hole) => pointInPolygon([x, z], hole))) {
1238
+ const above = ceilingAt(index + 1, x, z);
1239
+ const climb = payload.levels[index + 1].elevation - payload.levels[index].elevation;
1240
+ if (above)
1241
+ return { ...above, height: above.height + climb };
1242
+ }
1243
+ return { room, ceiling, height: resolveCeilingHeight(ceiling, written) - CEILING_DRAW_OFFSET };
1244
+ };
1245
+ // ---------- stairs, as create_stair builds them: up to the next level, opening its floor ----------
1246
+ let stairsWritten = false;
1247
+ payload.levels.forEach((level, index) => {
1248
+ const levelNode = levelNodes[index];
1249
+ const upper = levelNodes[index + 1];
1250
+ (level.stairs ?? []).forEach((stair, i) => {
1251
+ const id = `stair/${index}/${i}`;
1252
+ if (!upper) {
1253
+ defect(id, 'no_level_above', `A stair on "${level.name}", the top level, has nothing to rise to: give the level it reaches after this one in levels.`);
1254
+ return;
1255
+ }
1256
+ try {
1257
+ const outcome = createStair(written, {
1258
+ x: stair.start[0],
1259
+ z: stair.start[1],
1260
+ rotation: stair.rotation,
1261
+ layout: stair.layout,
1262
+ width: stair.width,
1263
+ ...(stair.turn ? { turn: stair.turn } : {}),
1264
+ ...(stair.length === undefined ? {} : { length: stair.length }),
1265
+ ...(stair.steps === undefined ? {} : { steps: stair.steps }),
1266
+ ...(stair.lowerSteps === undefined ? {} : { lowerSteps: stair.lowerSteps }),
1267
+ ...(stair.railing ? { railingMode: stair.railing } : {}),
1268
+ ...(stair.railingStyle ? { railingStyle: stair.railingStyle } : {}),
1269
+ ...(stair.name ? { name: stair.name } : {}),
1270
+ levelId: levelNode.id,
1271
+ toLevelId: upper.id,
1272
+ }, { ...context, activeLevelId: levelNode.id });
1273
+ const result = outcome.result;
1274
+ // create_stair mints fresh ids; the conversion's own keep the stair across revisions.
1275
+ let json = JSON.stringify(outcome.changes);
1276
+ (outcome.changes?.create ?? []).forEach(({ node }, k) => {
1277
+ const kind = node.id.slice(0, node.id.indexOf('_'));
1278
+ json = json.replaceAll(node.id, mint(kind, k ? `${id}/${k}` : id));
1279
+ });
1280
+ const changes = JSON.parse(json);
1281
+ const [made, ...rest] = changes.create;
1282
+ changes.create = [
1283
+ {
1284
+ ...made,
1285
+ node: {
1286
+ ...made.node,
1287
+ metadata: { ...made.node.metadata, authorScene: { id } },
1288
+ },
1289
+ },
1290
+ ...rest,
1291
+ ];
1292
+ written = applySceneChanges(written, changes);
1293
+ const finishes = [
1294
+ ['body', stair.color],
1295
+ ['treads', stair.treads ?? stair.color],
1296
+ ['railing', stair.railingColor],
1297
+ ];
1298
+ for (const [role, color] of finishes) {
1299
+ const finish = finishRef(color, id);
1300
+ if (!finish)
1301
+ continue;
1302
+ if (!nodeSlotDeclarations(written[made.node.id])?.some((slot) => slot.slotId === role)) {
1303
+ say(`${id}: it has no ${role}, its ${role === 'railing' ? 'railingColor' : 'color'} ignored`);
1304
+ continue;
1305
+ }
1306
+ try {
1307
+ const { changes } = paint(written, { targets: [made.node.id], material: finish, role }, paintContext(levelNode.id));
1308
+ written = applySceneChanges(written, changes);
1309
+ }
1310
+ catch (error) {
1311
+ defect(id, 'paint_refused', error.message);
1312
+ }
1313
+ }
1314
+ report.ids[id] = made.node.id;
1315
+ report.stairs++;
1316
+ stairsWritten = true;
1317
+ if (result.slabHoleCut)
1318
+ say(`${id}: ${result.message}`);
1319
+ else
1320
+ warn(id, 'no_floor_opening', result.message);
1321
+ }
1322
+ catch (error) {
1323
+ const e = error;
1324
+ defect(id, e.code ?? 'stair_refused', e.message ?? String(error));
1325
+ }
1326
+ });
1327
+ });
1328
+ /** The plan footprints of a level's walls as written, each grown by `grow` on both faces. */
1329
+ const wallFootprints = (levelId, grow = 0) => {
1330
+ const walls = Object.values(written).flatMap((node) => node.type === 'wall' && node.parentId === levelId
1331
+ ? [grow ? { ...node, thickness: getWallThickness(node) + 2 * grow } : node]
1332
+ : []);
1333
+ const miters = calculateLevelMiters(walls);
1334
+ return walls.map((wall) => getWallPlanFootprint(wall, miters).map(({ x, y }) => [x, y]));
1335
+ };
1336
+ // ---------- voids: openings in a level's floor, the room below rising through them ----------
1337
+ const voidCuts = new Map();
1338
+ const voidsCut = [];
1339
+ payload.levels.forEach((level, index) => {
1340
+ const levelNode = levelNodes[index];
1341
+ (level.voids ?? []).forEach((v, i) => {
1342
+ const id = `void/${index}/${i}`;
1343
+ const label = v.name ? `"${v.name}"` : `void ${i}`;
1344
+ if (index === 0) {
1345
+ defect(id, 'no_level_below', `${label} is in the floor of "${level.name}", the first level, with nothing under it to open onto: give it in the floor of the level above the room that rises through it.`);
1346
+ return;
1347
+ }
1348
+ // The cut stops at the faces of the walls standing on this floor, as the floor carries them,
1349
+ // and at the floor's own edge (a void drawn past a curved outside wall).
1350
+ const floor = union(Object.values(written).flatMap((node) => node.type === 'slab' && node.parentId === levelNode.id && node.plateRole === 'base'
1351
+ ? [node.polygon]
1352
+ : []));
1353
+ const cut = intersection(difference(v.polygon, union(wallFootprints(levelNode.id))), floor).filter((part) => area([part]) > 1e-4);
1354
+ const opened = area(cut);
1355
+ if (opened < 1e-4) {
1356
+ defect(id, 'no_floor', `No floor of "${level.name}" lies under ${label} clear of its walls: there is nothing to open.`);
1357
+ return;
1358
+ }
1359
+ if (cut.some((part) => part.holes.length))
1360
+ warn(id, 'wall_in_void', `A wall of "${level.name}" stands inside ${label}, over the opening around it: end the void at the wall, or move the wall.`);
1361
+ let cutsCeiling = false;
1362
+ cut.forEach((part, k) => {
1363
+ const plan = cutFloorOpening(written, {
1364
+ levelId: levelNode.id,
1365
+ polygon: part.outer,
1366
+ drawnOn: 'floor',
1367
+ mintId: () => mint('floor-opening', k ? `${id}/${k}` : id),
1368
+ });
1369
+ for (const change of plan.changes) {
1370
+ if (change.op === 'create') {
1371
+ const opening = change.node;
1372
+ cutsCeiling ||= opening.cutsAdjacent;
1373
+ written[opening.id] = {
1374
+ ...opening,
1375
+ name: v.name ?? 'Void',
1376
+ metadata: { ...opening.metadata, authorScene: { id } },
1377
+ };
1378
+ if (!k)
1379
+ report.ids[id] = opening.id;
1380
+ }
1381
+ else if (change.op === 'update')
1382
+ written[change.id] = { ...written[change.id], ...change.data };
1383
+ }
1384
+ });
1385
+ report.voids++;
1386
+ say(`${id}: the floor of "${level.name}" opened over ${opened.toFixed(1)} m², ${cutsCeiling ? `and the ceiling of "${payload.levels[index - 1].name}" under it` : `with no ceiling of "${payload.levels[index - 1].name}" under it`}`);
1387
+ voidCuts.set(index, union([...(voidCuts.get(index) ?? []), ...cut]));
1388
+ voidsCut.push({
1389
+ id,
1390
+ index,
1391
+ cut,
1392
+ railing: v.railing === null ? null : (v.railing ?? { height: 1.1 }),
1393
+ });
1394
+ });
1395
+ });
1396
+ // The openings the stairs and voids own cut the floor plates and ceilings they pass, as on load.
1397
+ if (stairsWritten || report.voids)
1398
+ written = {
1399
+ ...reconcileSceneStructure({ nodes: written, levelIds, mintId: (kind) => mint(kind) }).nodes,
1400
+ };
1401
+ // ---------- each void's railing: along its open edges, on the floor, as the slab railing looks ----------
1402
+ for (const { id, index, cut, railing } of voidsCut) {
1403
+ const levelNode = levelNodes[index];
1404
+ // Inside the double height the floor's edge round the void continues the walls it rises
1405
+ // between: it wears their inside paint, where the facade's edge keeps the cladding.
1406
+ const insides = (payload.levels[index - 1].walls ?? []).flatMap((w) => w.colorInside ? [w.colorInside] : []);
1407
+ const edge = finishRef(mostCommon(insides), id);
1408
+ if (edge)
1409
+ for (const zone of Object.values(written))
1410
+ if (zone.type === 'zone' &&
1411
+ zone.parentId === levelNode.id &&
1412
+ zone.spaceRole === 'room' &&
1413
+ area(intersection(cut, zone.polygon)) > 1e-4)
1414
+ written[zone.id] = { ...zone, floorEdgeFinish: edge };
1415
+ if (!railing)
1416
+ continue;
1417
+ // A run stops at a wall standing on the edge and where a stair arrives.
1418
+ const arrivals = Object.values(written).flatMap((node) => node.type === 'stair' && node.toLevelId === levelNode.id
1419
+ ? [stairArrivalOpening(node, written)]
1420
+ : []);
1421
+ const cover = union([
1422
+ ...wallFootprints(levelNode.id, 0.005),
1423
+ ...arrivals.filter((ring) => ring.length >= 3),
1424
+ ]);
1425
+ const runs = exposedIntervals(cut, [], cover).filter(({ start, end }) => dist(start, end) >= 0.1);
1426
+ const plate = Object.values(written)
1427
+ .filter((node) => node.type === 'slab' && node.parentId === levelNode.id && node.plateRole === 'base')
1428
+ .map((node) => ({ node, shared: area(intersection(cut, node.polygon)) }))
1429
+ .sort((a, b) => b.shared - a.shared)[0]?.node;
1430
+ const fences = runs.map((run, k) => FenceNode.parse({
1431
+ object: 'node',
1432
+ id: mint('fence', `${id}/railing/${k}`),
1433
+ parentId: levelNode.id,
1434
+ visible: true,
1435
+ metadata: { authorScene: { id } },
1436
+ name: 'Railing',
1437
+ start: run.start,
1438
+ end: run.end,
1439
+ height: railing.height,
1440
+ style: 'rail',
1441
+ baseStyle: 'floating',
1442
+ baseHeight: 0,
1443
+ postCap: 'none',
1444
+ ...(plate ? { supportSlabId: plate.id } : {}),
1445
+ }));
1446
+ if (!fences.length)
1447
+ continue;
1448
+ const level = written[levelNode.id];
1449
+ written[level.id] = {
1450
+ ...level,
1451
+ children: [...level.children, ...fences.map((f) => f.id)],
1452
+ };
1453
+ for (const fence of fences)
1454
+ written[fence.id] = fence;
1455
+ const finish = finishRef(railing.color, id);
1456
+ if (finish)
1457
+ try {
1458
+ const { changes } = paint(written, { targets: fences.map((fence) => fence.id), material: finish }, paintContext(levelNode.id));
1459
+ for (const u of changes?.update ?? [])
1460
+ written[u.id] = { ...written[u.id], ...u.data };
1461
+ }
1462
+ catch (error) {
1463
+ defect(id, 'paint_refused', error.message);
1464
+ }
1465
+ say(`${id}: a ${metres(railing.height)} railing along ${metres(runs.reduce((sum, run) => sum + dist(run.start, run.end), 0))} of its edges`);
1466
+ }
1467
+ // ---------- items, on their floors and ceilings, then each wall's paint ----------
1468
+ payload.levels.forEach((level, index) => {
1469
+ const levelNode = levelNodes[index];
1470
+ const { walls, wallNodes } = levelWalls[index];
711
1471
  // ---------- items, hosts first ----------
712
1472
  const pending = [...(level.items ?? [])];
713
1473
  let progress = true;
@@ -725,14 +1485,20 @@ export function authorScene(scene, input, context) {
725
1485
  defect(item.id, 'compile_failed', input.compileErrors?.[item.id] ?? 'The module did not compile.');
726
1486
  continue;
727
1487
  }
1488
+ if (/^(stairs?|staircases?)$/i.test(item.category))
1489
+ warn(item.id, 'use_stairs', `"${item.name}" is a stair written as an item: give it as its level's stairs, so it rises with the storey and opens the floor above.`);
728
1490
  const world = [item.position[0], item.position[1], item.position[2]];
729
1491
  const yaw = (item.rotation * Math.PI) / 180;
730
1492
  let placed;
1493
+ if (hostWall && isCurvedWall(hostWall.node)) {
1494
+ defect(item.id, 'curved_host', `Wall ${item.host} is curved: nothing hangs on a curved wall, as in the editor. Hang "${item.name}" on a straight wall, or stand it on the floor.`);
1495
+ continue;
1496
+ }
731
1497
  if (hostWall) {
732
1498
  // On the wall as place_items puts a wall-side item there: along it from its start, the
733
1499
  // payload's height for its bottom, on the face nearest the point, facing the room.
734
1500
  const [width, height] = scriptedSize(compiled.manifest);
735
- const wallHeight = hostWall.spec.height;
1501
+ const wallHeight = hostWall.height;
736
1502
  if (height > wallHeight) {
737
1503
  defect(item.id, 'item_too_tall', `"${item.name}" is ${metres(height)} tall; wall ${item.host} is ${metres(wallHeight)}.`);
738
1504
  continue;
@@ -773,8 +1539,27 @@ export function authorScene(scene, input, context) {
773
1539
  },
774
1540
  };
775
1541
  }
1542
+ else if (compiled.mount === 'ceiling' && !item.host) {
1543
+ // From the ceiling of the room its plan point is in, its top on that ceiling.
1544
+ const over = ceilingAt(index, world[0], world[2]);
1545
+ if (!over) {
1546
+ defect(item.id, 'no_ceiling', `"${item.name}" hangs from a ceiling, and no room on "${level.name}" contains (${world[0]}, ${world[2]}): move it into a room, or build it with mount 'floor'.`);
1547
+ continue;
1548
+ }
1549
+ say(`${item.id}: hung from the ceiling of ${over.room.name}, ${metres(over.height)} up`);
1550
+ placed = {
1551
+ compiled,
1552
+ parentId: over.ceiling.id,
1553
+ position: [world[0], 0, world[2]],
1554
+ rotation: item.rotation,
1555
+ frame: { position: [world[0], over.height, world[2]], yaw },
1556
+ };
1557
+ }
776
1558
  else {
777
1559
  const host = hostItemId ? itemFrames.get(item.host) : undefined;
1560
+ const opening = voidCuts.get(index);
1561
+ if (!host && opening && containsPoint(opening, [world[0], world[2]]))
1562
+ warn(item.id, 'over_void', `"${item.name}" stands at (${world[0]}, ${world[2]}), over a void in the floor of "${level.name}": nothing is under it. Move it onto the floor.`);
778
1563
  placed = {
779
1564
  compiled,
780
1565
  parentId: hostItemId ?? levelNode.id,
@@ -784,7 +1569,7 @@ export function authorScene(scene, input, context) {
784
1569
  };
785
1570
  }
786
1571
  try {
787
- const outcome = addObject(draft, {
1572
+ const outcome = addObject(written, {
788
1573
  compiled: { ...placed.compiled, nodeId: mint('item', item.id) },
789
1574
  name: item.name,
790
1575
  category: item.category,
@@ -795,13 +1580,16 @@ export function authorScene(scene, input, context) {
795
1580
  ...(placed.side ? { side: placed.side } : {}),
796
1581
  }, { ...context, activeLevelId: levelNode.id });
797
1582
  for (const { node, parentId } of outcome.changes?.create ?? []) {
798
- draft[node.id] = {
1583
+ written[node.id] = {
799
1584
  ...node,
800
1585
  slots: exactSlots(compiled, item.id),
801
1586
  ...(placed.wallT === undefined ? {} : { wallT: placed.wallT }),
802
1587
  };
803
- const parent = draft[parentId ?? levelNode.id];
804
- parent.children = [...(parent.children ?? []), node.id];
1588
+ const parent = written[parentId ?? levelNode.id];
1589
+ written[parent.id] = {
1590
+ ...parent,
1591
+ children: [...(parent.children ?? []), node.id],
1592
+ };
805
1593
  itemNodeIds.set(item.id, node.id);
806
1594
  itemFrames.set(item.id, placed.frame);
807
1595
  report.ids[item.id] = node.id;
@@ -817,15 +1605,46 @@ export function authorScene(scene, input, context) {
817
1605
  for (const item of pending)
818
1606
  defect(item.id, 'host_missing', `Its host ${item.host} was not placed.`);
819
1607
  // ---------- wall paint, through the editor's paint: both faces, or each its own ----------
1608
+ const inside = {
1609
+ left: [],
1610
+ right: [],
1611
+ both: [],
1612
+ neither: [],
1613
+ };
820
1614
  for (const w of walls) {
821
1615
  const node = wallNodes.get(w.spec.id)?.node;
822
1616
  if (!node)
823
1617
  continue;
824
- const faces = {
825
- a: w.spec.colorLeft ?? w.spec.color,
826
- b: w.spec.colorRight ?? w.spec.color,
827
- };
828
1618
  const coats = [];
1619
+ if (w.spec.curtain) {
1620
+ // Glass and mullions: no face to paint; its frame takes the colour.
1621
+ const ignored = ['color', 'colorInside', 'colorOutside', 'colorLeft', 'colorRight'].filter((field) => w.spec[field]);
1622
+ if (ignored.length)
1623
+ say(`wall ${w.spec.id}: a curtain wall has no faces to paint, its ${ignored.join(', ')} ignored; curtain.frameColor colours its frame`);
1624
+ const frame = finishRef(w.spec.curtain.frameColor, w.spec.id);
1625
+ if (frame)
1626
+ coats.push({ role: 'curtain-frame', material: frame });
1627
+ }
1628
+ const { colorInside, colorOutside } = w.spec.curtain ? {} : w.spec;
1629
+ // A face is inside when a room of this level stands on it, known once the rooms are derived.
1630
+ const cover = colorInside || colorOutside ? roomFaceCover(written, node.id) : undefined;
1631
+ const meant = (face) => cover && (cover[face] === 'none' ? colorOutside : colorInside);
1632
+ if (cover) {
1633
+ const faces = ['a', 'b'].filter((face) => cover[face] !== 'none');
1634
+ inside[faces.length === 2
1635
+ ? 'both'
1636
+ : faces.length === 0
1637
+ ? 'neither'
1638
+ : faces[0] === 'a'
1639
+ ? 'left'
1640
+ : 'right'].push(w.spec.id);
1641
+ }
1642
+ const faces = w.spec.curtain
1643
+ ? {}
1644
+ : {
1645
+ a: w.spec.colorLeft ?? meant('a') ?? w.spec.color,
1646
+ b: w.spec.colorRight ?? meant('b') ?? w.spec.color,
1647
+ };
829
1648
  if (faces.a && faces.a === faces.b) {
830
1649
  const ref = finishRef(faces.a, w.spec.id);
831
1650
  if (ref)
@@ -839,84 +1658,24 @@ export function authorScene(scene, input, context) {
839
1658
  }
840
1659
  for (const coat of coats) {
841
1660
  try {
842
- const { changes } = paint(draft, { targets: [node.id], ...coat }, paintContext(levelNode.id));
1661
+ const { changes } = paint(written, { targets: [node.id], ...coat }, paintContext(levelNode.id));
843
1662
  for (const u of changes?.update ?? [])
844
- draft[u.id] = { ...draft[u.id], ...u.data };
1663
+ written[u.id] = { ...written[u.id], ...u.data };
845
1664
  }
846
1665
  catch (error) {
847
1666
  defect(w.spec.id, 'paint_refused', error.message);
848
1667
  }
849
1668
  }
850
1669
  }
1670
+ const resolved = [
1671
+ inside.left.length && `the left face of ${inside.left.join(', ')}`,
1672
+ inside.right.length && `the right face of ${inside.right.join(', ')}`,
1673
+ inside.both.length && `both faces of ${inside.both.join(', ')}`,
1674
+ inside.neither.length && `neither face of ${inside.neither.join(', ')}`,
1675
+ ].filter(Boolean);
1676
+ if (resolved.length)
1677
+ say(`colorInside / colorOutside on "${level.name}", left and right as seen from each wall's start to its end: inside is ${resolved.join('; ')}`);
851
1678
  });
852
- report.levels = levelNodes.length;
853
- if (untypedWindows)
854
- say(`${untypedWindows} window${untypedWindows === 1 ? ' has' : 's have'} no type: written fixed (cannot open); give type`);
855
- // ---------- which face of each wall looks into a room: read from the plan, as the editor does ----------
856
- for (const levelNode of levelNodes) {
857
- const boundaries = Object.values(draft).filter((n) => n.parentId === levelNode.id && (n.type === 'wall' || n.type === 'separator'));
858
- for (const { wallId, ...sides } of detectSpacesForLevel(levelNode.id, boundaries).wallUpdates)
859
- draft[wallId] = { ...draft[wallId], ...sides };
860
- }
861
- // ---------- rooms: Pascal's own derivation, then the payload's names and floors ----------
862
- const { nodes: withZones, createdZoneIds } = migrateRoomZones(draft);
863
- const zones = createdZoneIds.map((id) => ZoneNode.parse(withZones[id]));
864
- for (const zone of zones) {
865
- draft[zone.id] = zone;
866
- const level = draft[zone.parentId];
867
- draft[zone.parentId] = { ...level, children: [...level.children, zone.id] };
868
- report.rooms++;
869
- }
870
- const claimed = new Map();
871
- payload.levels.forEach((level, index) => {
872
- const levelNode = levelNodes[index];
873
- for (const [i, room] of level.rooms.entries()) {
874
- const id = `room/${index}/${i}`;
875
- const zone = zones.find((z) => z.parentId === levelNode.id && pointInPolygon(room.seed, z.polygon));
876
- // Two seeds in one enclosure: the walls do not separate the rooms the payload names.
877
- if (zone && claimed.has(zone.id)) {
878
- defect(id, 'room_shares_enclosure', `"${room.name}" and "${claimed.get(zone.id)}" are one enclosure: no wall separates them.`);
879
- continue;
880
- }
881
- if (!zone) {
882
- defect(id, 'room_not_enclosed', `No closed room around (${room.seed[0]}, ${room.seed[1]}) "${room.name}": its walls do not meet.`);
883
- continue;
884
- }
885
- if (room.floorRegions.length > MANY_REGIONS)
886
- warn(id, 'many_regions', `"${room.name}" has ${room.floorRegions.length} floor regions: regions are fixed polygons that do not follow a resize; use one floor finish.`);
887
- const finish = finishRef(room.floor, id);
888
- const regions = room.floorRegions.map((r, k) => ({
889
- id: `region_${k}`,
890
- polygon: r.polygon,
891
- finish: finishRef(r.finish, id) ?? r.finish,
892
- }));
893
- draft[zone.id] = ZoneNode.parse({
894
- ...zone,
895
- name: room.name,
896
- spaceRole: 'room',
897
- ...(finish || regions.length
898
- ? {
899
- floor: {
900
- ...zone.floor,
901
- ...(finish ? { finish } : {}),
902
- ...(regions.length ? { regions } : {}),
903
- },
904
- }
905
- : {}),
906
- metadata: { ...zone.metadata, authorScene: { seed: room.seed } },
907
- });
908
- claimed.set(zone.id, room.name);
909
- report.ids[id] = zone.id;
910
- report.roomsNamed++;
911
- }
912
- });
913
- // ---------- floors and ceilings, as the editor derives them on load ----------
914
- const structure = reconcileSceneStructure({
915
- nodes: draft,
916
- levelIds: levelNodes.map((l) => l.id),
917
- mintId: (kind) => mint(kind),
918
- });
919
- const written = { ...structure.nodes };
920
1679
  // ---------- skirting, native on each wall, along the faces its rooms stand on ----------
921
1680
  for (const { id, nodeId, levelId, skirting } of skirtings) {
922
1681
  const finish = finishRef(skirting.color, id);
@@ -944,22 +1703,47 @@ export function authorScene(scene, input, context) {
944
1703
  // Ceilings exist only after structure reconciliation; lights can now use their room's host.
945
1704
  payload.levels.forEach((level, index) => {
946
1705
  const levelNode = levelNodes[index];
947
- for (const [i, light] of level.lights.entries()) {
948
- const id = `light/${index}/${i}`;
1706
+ const entries = level.lights.map((light, i) => ({ id: `light/${index}/${i}`, light }));
1707
+ // A named room with no light of its own (none whose plan point is in it, given or built into
1708
+ // an item) gets a plain ceiling light at its seed.
1709
+ const lit = [
1710
+ ...level.lights.map((light) => [light.position[0], light.position[2]]),
1711
+ ...(level.items ?? []).flatMap((item) => {
1712
+ const frame = itemFrames.get(item.id);
1713
+ return frame && input.compiled[item.id]?.manifest.lights?.length
1714
+ ? [[frame.position[0], frame.position[2]]]
1715
+ : [];
1716
+ }),
1717
+ ];
1718
+ const seen = new Set();
1719
+ level.rooms.forEach((room, i) => {
1720
+ const zone = written[report.ids[`room/${index}/${i}`] ?? ''];
1721
+ if (!zone || seen.has(zone.id))
1722
+ return;
1723
+ seen.add(zone.id);
1724
+ const inside = (p) => pointInPolygon(p, zone.polygon) && !zone.holes.some((hole) => pointInPolygon(p, hole));
1725
+ if (lit.some(inside))
1726
+ return;
1727
+ const over = ceilingAt(index, room.seed[0], room.seed[1]);
1728
+ if (!over)
1729
+ return;
1730
+ entries.push({
1731
+ id: roomLightId(index, i),
1732
+ light: {
1733
+ position: [room.seed[0], over.height - 0.1, room.seed[1]],
1734
+ color: '#ffffff',
1735
+ intensity: 1,
1736
+ },
1737
+ room: zone.name ?? room.name,
1738
+ });
1739
+ });
1740
+ for (const { id, light, room: unlit } of entries) {
949
1741
  const at = [light.position[0], light.position[1], light.position[2]];
950
1742
  let hostId = light.fixture ? itemNodeIds.get(light.fixture) : undefined;
951
1743
  let hostFrame = light.fixture ? itemFrames.get(light.fixture) : undefined;
952
- const room = !light.fixture
953
- ? Object.values(written).find((node) => node.type === 'zone' &&
954
- node.spaceRole === 'room' &&
955
- node.parentId === levelNode.id &&
956
- pointInPolygon([at[0], at[2]], node.polygon) &&
957
- !node.holes.some((hole) => pointInPolygon([at[0], at[2]], hole)))
958
- : undefined;
959
- const ceiling = room
960
- ? Object.values(written).find((node) => node.type === 'ceiling' && node.zoneId === room.id && node.parentId === levelNode.id)
961
- : undefined;
962
- if (ceiling) {
1744
+ const over = light.fixture ? undefined : ceilingAt(index, at[0], at[2]);
1745
+ if (over) {
1746
+ const { room, ceiling } = over;
963
1747
  const compiled = input.compiled[id];
964
1748
  if (!compiled) {
965
1749
  defect(id, 'compile_failed', input.compileErrors?.[id] ?? 'The ceiling point module did not compile.');
@@ -980,12 +1764,14 @@ export function authorScene(scene, input, context) {
980
1764
  written[ceiling.id] = { ...ceiling, children: [...ceiling.children, node.id] };
981
1765
  hostId = node.id;
982
1766
  hostFrame = {
983
- position: [at[0], resolveCeilingHeight(ceiling, written) - CEILING_DRAW_OFFSET, at[2]],
1767
+ position: [at[0], over.height, at[2]],
984
1768
  yaw: 0,
985
1769
  };
986
1770
  report.ids[id] = node.id;
987
1771
  report.items++;
988
- say(`light at [${at.join(', ')}] has no fixture: a ceiling point in ${room.name}`);
1772
+ say(unlit
1773
+ ? `${unlit} had no light: a ceiling light at its centre`
1774
+ : `light at [${at.join(', ')}] has no fixture: a ceiling point in ${room.name}`);
989
1775
  }
990
1776
  catch (error) {
991
1777
  const e = error;
@@ -1023,6 +1809,9 @@ export function authorScene(scene, input, context) {
1023
1809
  color: light.color,
1024
1810
  intensityRange: [0, light.intensity],
1025
1811
  offset: toLocal(hostFrame, at),
1812
+ spot: lightSpot(light, at, hostFrame),
1813
+ shadows: light.shadows,
1814
+ glow: light.glow,
1026
1815
  },
1027
1816
  ];
1028
1817
  written[hostNode.id] = {
@@ -1032,14 +1821,129 @@ export function authorScene(scene, input, context) {
1032
1821
  report.lights++;
1033
1822
  }
1034
1823
  });
1035
- const nodes = Object.fromEntries(Object.entries(written).filter(([id]) => !scaffoldIds.has(id)));
1036
- const candidate = {
1037
- version: 1,
1038
- buildingId: building.id,
1039
- seed,
1040
- ids: report.ids,
1041
- nodes,
1042
- };
1824
+ // ---------- the roof: on the building's roof level above the top storey, as create_roof puts it ----------
1825
+ const roof = payload.roof;
1826
+ if (roof && (roof.segments.length || roof.items?.length)) {
1827
+ const top = levelNodes.at(-1);
1828
+ const segments = roof.segments.map((spec, i) => {
1829
+ const segment = RoofSegmentNode.parse({
1830
+ roofType: spec.roofType,
1831
+ width: spec.width,
1832
+ depth: spec.depth,
1833
+ wallHeight: 0,
1834
+ rotation: (spec.rotation * Math.PI) / 180,
1835
+ position: [spec.center[0], 0, spec.center[1]],
1836
+ ...(spec.pitch !== undefined && { pitch: spec.pitch }),
1837
+ ...(spec.gableOverhang !== undefined && { gableOverhang: spec.gableOverhang }),
1838
+ });
1839
+ // The overhang is asked in plan; the segment stores it along its slope (a barrel's, at its ends).
1840
+ const overhang = spec.roofType === 'barrel'
1841
+ ? spec.overhang
1842
+ : spec.overhang / getSegmentSlopeFrame(segment).cosTheta;
1843
+ return { ...segment, id: mint('rseg', `roof/segment/${i}`), overhang };
1844
+ });
1845
+ const peak = Math.max(0, ...segments.map((segment) => getActiveRoofHeight(segment)));
1846
+ // Nothing stands above the payload's top level in its candidate: the plan is a roof level.
1847
+ const plan = planRoofLevel(written, top.id, peak);
1848
+ const created = plan.kind === 'roof' ? plan.create : null;
1849
+ const levelId = created ? mint('level', 'level/roof') : plan.levelId;
1850
+ if (created)
1851
+ written = applySceneChanges(written, {
1852
+ create: [{ node: { ...created, id: levelId }, parentId: building.id }],
1853
+ });
1854
+ report.ids['level/roof'] = levelId;
1855
+ // A roof measured from the top storey's floor stands this much lower on the roof level.
1856
+ const offset = plan.storeyOffset;
1857
+ let rises = peak;
1858
+ if (segments.length) {
1859
+ const cx = segments.reduce((sum, s) => sum + s.position[0], 0) / segments.length;
1860
+ const cz = segments.reduce((sum, s) => sum + s.position[2], 0) / segments.length;
1861
+ const node = RoofNode.parse({
1862
+ id: mint('roof', 'roof'),
1863
+ name: roof.name ?? 'Roof',
1864
+ parentId: levelId,
1865
+ position: [cx, 0, cz],
1866
+ rotation: 0,
1867
+ support: { kind: 'walls' },
1868
+ children: segments.map((segment) => segment.id),
1869
+ metadata: { authorScene: { id: 'roof' } },
1870
+ });
1871
+ written = applySceneChanges(written, {
1872
+ create: [
1873
+ { node, parentId: levelId },
1874
+ ...segments.map((segment) => ({
1875
+ node: {
1876
+ ...segment,
1877
+ parentId: node.id,
1878
+ position: [segment.position[0] - cx, 0, segment.position[2] - cz],
1879
+ },
1880
+ parentId: node.id,
1881
+ })),
1882
+ ],
1883
+ });
1884
+ const elevation = resolveRoofElevation(written[node.id], written);
1885
+ written[node.id] = { ...node, position: [cx, elevation, cz] };
1886
+ rises = elevation + peak;
1887
+ report.ids.roof = node.id;
1888
+ report.roofSegments = segments.length;
1889
+ for (const [role, color] of [
1890
+ ['top', roof.color],
1891
+ ['wall', roof.wallColor],
1892
+ ]) {
1893
+ const finish = finishRef(color, 'roof');
1894
+ if (finish)
1895
+ try {
1896
+ const { changes } = paint(written, { targets: [node.id], material: finish, role }, paintContext(levelId));
1897
+ for (const u of changes?.update ?? [])
1898
+ written[u.id] = { ...written[u.id], ...u.data };
1899
+ }
1900
+ catch (error) {
1901
+ defect('roof', 'paint_refused', error.message);
1902
+ }
1903
+ }
1904
+ }
1905
+ for (const item of roof.items ?? []) {
1906
+ const compiled = input.compiled[item.id];
1907
+ if (!compiled) {
1908
+ defect(item.id, 'compile_failed', input.compileErrors?.[item.id] ?? 'The module did not compile.');
1909
+ continue;
1910
+ }
1911
+ try {
1912
+ const outcome = addObject(written, {
1913
+ compiled: { ...compiled, nodeId: mint('item', item.id) },
1914
+ name: item.name,
1915
+ category: item.category,
1916
+ reason: `author_scene: ${item.name}`,
1917
+ position: [item.position[0], item.position[1] + offset, item.position[2]],
1918
+ rotation: item.rotation,
1919
+ parentId: levelId,
1920
+ role: 'roof',
1921
+ }, { ...context, activeLevelId: levelId });
1922
+ for (const { node } of outcome.changes?.create ?? []) {
1923
+ written = applySceneChanges(written, {
1924
+ create: [
1925
+ {
1926
+ node: { ...node, slots: exactSlots(compiled, item.id) },
1927
+ parentId: levelId,
1928
+ },
1929
+ ],
1930
+ });
1931
+ report.ids[item.id] = node.id;
1932
+ report.items++;
1933
+ rises = Math.max(rises, item.position[1] + offset + scriptedSize(compiled.manifest)[1]);
1934
+ }
1935
+ }
1936
+ catch (error) {
1937
+ const e = error;
1938
+ defect(item.id, e.code ?? 'item_refused', e.message ?? String(error));
1939
+ }
1940
+ }
1941
+ // A roof level made for it is as tall as the roof rises over it.
1942
+ if (created)
1943
+ written[levelId] = { ...written[levelId], height: Math.max(rises, 0.2) };
1944
+ }
1945
+ const nodes = plain(Object.fromEntries(Object.entries(written).filter(([id]) => !scaffoldIds.has(id))));
1946
+ const candidate = { buildingId: building.id, seed, ids: report.ids, nodes };
1043
1947
  const outcome = {
1044
1948
  result: report,
1045
1949
  candidate,
@@ -1053,24 +1957,88 @@ export function finalizeAuthorScene(scene, outcome) {
1053
1957
  const building = scene[outcome.buildingId];
1054
1958
  if (building?.type !== 'building')
1055
1959
  refuse('no_building', 'The target building no longer exists.');
1056
- const previous = building.metadata?.authorSceneState;
1057
- const base = previous?.nodes ??
1058
- Object.fromEntries(Object.values(scene)
1059
- .filter((node) => node.type === 'level' && node.parentId === building.id)
1060
- .map((node) => [node.id, node]));
1061
- const changes = authoredSceneChanges(scene, base, outcome.candidate.nodes);
1960
+ const { candidate } = outcome;
1961
+ const current = Object.fromEntries(Object.entries(scene).map(([id, node]) => [id, withoutBookkeeping(node)]));
1962
+ const base = authoredBaseline(scene, building)?.nodes ??
1963
+ Object.fromEntries(queryNodes(current, { type: 'level', parentId: building.id }).map((node) => [node.id, node]));
1964
+ const changes = authoredSceneChanges(current, base, candidate.nodes);
1965
+ // Each written node keeps its own baseline; each written level lists what stands on it and the
1966
+ // building what it holds itself (its units), so a revision finds every node of the last one.
1967
+ const holderOf = (id) => {
1968
+ let node = candidate.nodes[id];
1969
+ while (node && node.type !== 'level') {
1970
+ if (node.parentId === building.id)
1971
+ return building.id;
1972
+ node = node.parentId ? candidate.nodes[node.parentId] : undefined;
1973
+ }
1974
+ return node?.id;
1975
+ };
1976
+ const keyOf = new Map(Object.entries(candidate.ids).map(([key, id]) => [id, key]));
1977
+ const writtenOn = new Map();
1978
+ for (const id of Object.keys(candidate.nodes)) {
1979
+ const holder = holderOf(id);
1980
+ if (!holder)
1981
+ continue;
1982
+ const written = writtenOn.get(holder) ?? { ids: [], keys: {} };
1983
+ written.ids.push(id);
1984
+ const key = keyOf.get(id);
1985
+ if (key)
1986
+ written.keys[key] = id;
1987
+ writtenOn.set(holder, written);
1988
+ }
1989
+ const bookkeeping = (id) => ({
1990
+ authorSceneBase: withoutBookkeeping(candidate.nodes[id]),
1991
+ ...(writtenOn.has(id) ? { authorSceneWritten: writtenOn.get(id) } : {}),
1992
+ });
1993
+ const create = (changes.create ?? []).map((entry) => candidate.nodes[entry.node.id]
1994
+ ? {
1995
+ ...entry,
1996
+ node: {
1997
+ ...entry.node,
1998
+ metadata: { ...entry.node.metadata, ...bookkeeping(entry.node.id) },
1999
+ },
2000
+ }
2001
+ : entry);
2002
+ const created = new Set(create.map((entry) => entry.node.id));
2003
+ const removed = new Set(changes.delete ?? []);
2004
+ const updated = new Map((changes.update ?? []).map((entry) => [entry.id, entry]));
2005
+ for (const id of Object.keys(candidate.nodes)) {
2006
+ const now = scene[id];
2007
+ if (created.has(id) || removed.has(id) || !now)
2008
+ continue;
2009
+ const kept = { ...bookkeeping(id) };
2010
+ const stored = Object.fromEntries(BOOKKEEPING.flatMap((key) => now.metadata && key in now.metadata ? [[key, now.metadata[key]]] : []));
2011
+ const entry = updated.get(id);
2012
+ if (!entry && equalJson(stored, kept))
2013
+ continue;
2014
+ const metadata = (entry?.data.metadata ??
2015
+ current[id]?.metadata ??
2016
+ {});
2017
+ updated.set(id, {
2018
+ id,
2019
+ data: { ...entry?.data, metadata: { ...metadata, ...kept } },
2020
+ });
2021
+ }
2022
+ const state = { version: 2, buildingId: building.id, seed: candidate.seed };
2023
+ updated.set(building.id, {
2024
+ id: building.id,
2025
+ data: {
2026
+ ...updated.get(building.id)?.data,
2027
+ metadata: {
2028
+ ...withoutBookkeeping(building).metadata,
2029
+ authorSceneState: state,
2030
+ ...(writtenOn.has(building.id) ? { authorSceneWritten: writtenOn.get(building.id) } : {}),
2031
+ },
2032
+ },
2033
+ });
1062
2034
  return {
1063
2035
  ...outcome,
1064
2036
  changes: {
1065
2037
  ...changes,
2038
+ create,
1066
2039
  ...(outcome.changes?.materials ? { materials: outcome.changes.materials } : {}),
1067
- update: [
1068
- ...(changes.update ?? []),
1069
- {
1070
- id: building.id,
1071
- data: { metadata: { ...building.metadata, authorSceneState: outcome.candidate } },
1072
- },
1073
- ],
2040
+ update: [...updated.values()],
1074
2041
  },
1075
2042
  };
1076
2043
  }
2044
+ const equalJson = (a, b) => JSON.stringify(a) === JSON.stringify(b);