@pascal-app/core 0.7.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. package/dist/events/bus.d.ts +47 -18
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/events/bus.js +1 -1
  4. package/dist/hooks/scene-registry/scene-registry.d.ts +6 -22
  5. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  6. package/dist/hooks/scene-registry/scene-registry.js +33 -26
  7. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
  8. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
  9. package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
  10. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
  11. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +273 -0
  13. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +13 -2
  14. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  15. package/dist/hooks/spatial-grid/spatial-grid-manager.js +95 -9
  16. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
  17. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  18. package/dist/hooks/spatial-grid/spatial-grid-sync.js +91 -18
  19. package/dist/index.d.ts +22 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +19 -2
  22. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  23. package/dist/lib/polygon-relations.d.ts +10 -0
  24. package/dist/lib/polygon-relations.d.ts.map +1 -0
  25. package/dist/lib/polygon-relations.js +73 -0
  26. package/dist/lib/space-detection-pause.test.d.ts +2 -0
  27. package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
  28. package/dist/lib/space-detection-pause.test.js +42 -0
  29. package/dist/lib/space-detection.d.ts +30 -1
  30. package/dist/lib/space-detection.d.ts.map +1 -1
  31. package/dist/lib/space-detection.js +222 -41
  32. package/dist/lib/space-detection.test.d.ts +2 -0
  33. package/dist/lib/space-detection.test.d.ts.map +1 -0
  34. package/dist/lib/space-detection.test.js +77 -0
  35. package/dist/material-library.d.ts +1 -1
  36. package/dist/material-library.d.ts.map +1 -1
  37. package/dist/material-library.js +1957 -141
  38. package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
  39. package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
  40. package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
  41. package/dist/registry/handles.d.ts +321 -0
  42. package/dist/registry/handles.d.ts.map +1 -0
  43. package/dist/registry/handles.js +24 -0
  44. package/dist/registry/index.d.ts +7 -0
  45. package/dist/registry/index.d.ts.map +1 -0
  46. package/dist/registry/index.js +4 -0
  47. package/dist/registry/registry.d.ts +93 -0
  48. package/dist/registry/registry.d.ts.map +1 -0
  49. package/dist/registry/registry.js +201 -0
  50. package/dist/registry/registry.test.d.ts +2 -0
  51. package/dist/registry/registry.test.d.ts.map +1 -0
  52. package/dist/registry/registry.test.js +183 -0
  53. package/dist/registry/relations-resolver.d.ts +42 -0
  54. package/dist/registry/relations-resolver.d.ts.map +1 -0
  55. package/dist/registry/relations-resolver.js +77 -0
  56. package/dist/registry/relations-resolver.test.d.ts +2 -0
  57. package/dist/registry/relations-resolver.test.d.ts.map +1 -0
  58. package/dist/registry/relations-resolver.test.js +183 -0
  59. package/dist/registry/scene-api.d.ts +44 -0
  60. package/dist/registry/scene-api.d.ts.map +1 -0
  61. package/dist/registry/scene-api.js +111 -0
  62. package/dist/registry/scene-api.test.d.ts +2 -0
  63. package/dist/registry/scene-api.test.d.ts.map +1 -0
  64. package/dist/registry/scene-api.test.js +183 -0
  65. package/dist/registry/subtree.d.ts +67 -0
  66. package/dist/registry/subtree.d.ts.map +1 -0
  67. package/dist/registry/subtree.js +119 -0
  68. package/dist/registry/subtree.test.d.ts +2 -0
  69. package/dist/registry/subtree.test.d.ts.map +1 -0
  70. package/dist/registry/subtree.test.js +133 -0
  71. package/dist/registry/types.d.ts +1420 -0
  72. package/dist/registry/types.d.ts.map +1 -0
  73. package/dist/registry/types.js +1 -0
  74. package/dist/schema/asset-url.test.js +0 -4
  75. package/dist/schema/index.d.ts +17 -2
  76. package/dist/schema/index.d.ts.map +1 -1
  77. package/dist/schema/index.js +17 -3
  78. package/dist/schema/material.d.ts +12 -2
  79. package/dist/schema/material.d.ts.map +1 -1
  80. package/dist/schema/material.js +10 -0
  81. package/dist/schema/nodes/box-vent.d.ts +74 -0
  82. package/dist/schema/nodes/box-vent.d.ts.map +1 -0
  83. package/dist/schema/nodes/box-vent.js +63 -0
  84. package/dist/schema/nodes/building.d.ts +1 -1
  85. package/dist/schema/nodes/building.d.ts.map +1 -1
  86. package/dist/schema/nodes/building.js +3 -2
  87. package/dist/schema/nodes/ceiling.d.ts +3 -1
  88. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  89. package/dist/schema/nodes/ceiling.js +1 -1
  90. package/dist/schema/nodes/chimney.d.ts +158 -0
  91. package/dist/schema/nodes/chimney.d.ts.map +1 -0
  92. package/dist/schema/nodes/chimney.js +67 -0
  93. package/dist/schema/nodes/column.d.ts +598 -6
  94. package/dist/schema/nodes/column.d.ts.map +1 -1
  95. package/dist/schema/nodes/column.js +586 -6
  96. package/dist/schema/nodes/cupola.d.ts +66 -0
  97. package/dist/schema/nodes/cupola.d.ts.map +1 -0
  98. package/dist/schema/nodes/cupola.js +28 -0
  99. package/dist/schema/nodes/door.d.ts +1 -1
  100. package/dist/schema/nodes/dormer.d.ts +228 -0
  101. package/dist/schema/nodes/dormer.d.ts.map +1 -0
  102. package/dist/schema/nodes/dormer.js +119 -0
  103. package/dist/schema/nodes/downspout.d.ts +75 -0
  104. package/dist/schema/nodes/downspout.d.ts.map +1 -0
  105. package/dist/schema/nodes/downspout.js +63 -0
  106. package/dist/schema/nodes/elevator.d.ts +105 -0
  107. package/dist/schema/nodes/elevator.d.ts.map +1 -0
  108. package/dist/schema/nodes/elevator.js +54 -0
  109. package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
  110. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
  111. package/dist/schema/nodes/eyebrow-vent.js +40 -0
  112. package/dist/schema/nodes/fence.d.ts +3 -2
  113. package/dist/schema/nodes/fence.d.ts.map +1 -1
  114. package/dist/schema/nodes/fence.js +4 -2
  115. package/dist/schema/nodes/gutter.d.ts +84 -0
  116. package/dist/schema/nodes/gutter.d.ts.map +1 -0
  117. package/dist/schema/nodes/gutter.js +76 -0
  118. package/dist/schema/nodes/item.d.ts +16 -0
  119. package/dist/schema/nodes/item.d.ts.map +1 -1
  120. package/dist/schema/nodes/item.js +21 -0
  121. package/dist/schema/nodes/level.d.ts +1 -1
  122. package/dist/schema/nodes/level.d.ts.map +1 -1
  123. package/dist/schema/nodes/level.js +2 -0
  124. package/dist/schema/nodes/ridge-vent.d.ts +67 -0
  125. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
  126. package/dist/schema/nodes/ridge-vent.js +30 -0
  127. package/dist/schema/nodes/roof-segment.d.ts +198 -4
  128. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  129. package/dist/schema/nodes/roof-segment.js +254 -3
  130. package/dist/schema/nodes/roof.d.ts +6 -6
  131. package/dist/schema/nodes/roof.d.ts.map +1 -1
  132. package/dist/schema/nodes/roof.js +9 -21
  133. package/dist/schema/nodes/shelf.d.ts +105 -0
  134. package/dist/schema/nodes/shelf.d.ts.map +1 -0
  135. package/dist/schema/nodes/shelf.js +84 -0
  136. package/dist/schema/nodes/site.d.ts +1 -116
  137. package/dist/schema/nodes/site.d.ts.map +1 -1
  138. package/dist/schema/nodes/site.js +2 -6
  139. package/dist/schema/nodes/skylight.d.ts +279 -0
  140. package/dist/schema/nodes/skylight.d.ts.map +1 -0
  141. package/dist/schema/nodes/skylight.js +152 -0
  142. package/dist/schema/nodes/slab.d.ts +3 -1
  143. package/dist/schema/nodes/slab.d.ts.map +1 -1
  144. package/dist/schema/nodes/slab.js +1 -1
  145. package/dist/schema/nodes/solar-panel.d.ts +117 -0
  146. package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
  147. package/dist/schema/nodes/solar-panel.js +47 -0
  148. package/dist/schema/nodes/stair-segment.d.ts +1 -1
  149. package/dist/schema/nodes/stair.d.ts +6 -6
  150. package/dist/schema/nodes/stair.d.ts.map +1 -1
  151. package/dist/schema/nodes/stair.js +9 -7
  152. package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
  153. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  154. package/dist/schema/nodes/surface-hole-metadata.js +3 -1
  155. package/dist/schema/nodes/turbine-vent.d.ts +68 -0
  156. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
  157. package/dist/schema/nodes/turbine-vent.js +41 -0
  158. package/dist/schema/nodes/wall.d.ts +3 -3
  159. package/dist/schema/nodes/window.d.ts +1 -1
  160. package/dist/schema/types.d.ts +1448 -236
  161. package/dist/schema/types.d.ts.map +1 -1
  162. package/dist/schema/types.js +26 -0
  163. package/dist/services/alignment-anchors.d.ts +100 -0
  164. package/dist/services/alignment-anchors.d.ts.map +1 -0
  165. package/dist/services/alignment-anchors.js +283 -0
  166. package/dist/services/alignment-anchors.test.d.ts +2 -0
  167. package/dist/services/alignment-anchors.test.d.ts.map +1 -0
  168. package/dist/services/alignment-anchors.test.js +383 -0
  169. package/dist/services/alignment.d.ts +126 -0
  170. package/dist/services/alignment.d.ts.map +1 -0
  171. package/dist/services/alignment.js +169 -0
  172. package/dist/services/alignment.test.d.ts +2 -0
  173. package/dist/services/alignment.test.d.ts.map +1 -0
  174. package/dist/services/alignment.test.js +92 -0
  175. package/dist/services/drag-session.d.ts +47 -0
  176. package/dist/services/drag-session.d.ts.map +1 -0
  177. package/dist/services/drag-session.js +100 -0
  178. package/dist/services/drag-session.test.d.ts +2 -0
  179. package/dist/services/drag-session.test.d.ts.map +1 -0
  180. package/dist/services/drag-session.test.js +219 -0
  181. package/dist/services/hosting.d.ts +80 -0
  182. package/dist/services/hosting.d.ts.map +1 -0
  183. package/dist/services/hosting.js +122 -0
  184. package/dist/services/hosting.test.d.ts +2 -0
  185. package/dist/services/hosting.test.d.ts.map +1 -0
  186. package/dist/services/hosting.test.js +199 -0
  187. package/dist/services/index.d.ts +7 -0
  188. package/dist/services/index.d.ts.map +1 -0
  189. package/dist/services/index.js +6 -0
  190. package/dist/services/movement.d.ts +48 -0
  191. package/dist/services/movement.d.ts.map +1 -0
  192. package/dist/services/movement.js +65 -0
  193. package/dist/services/movement.test.d.ts +2 -0
  194. package/dist/services/movement.test.d.ts.map +1 -0
  195. package/dist/services/movement.test.js +133 -0
  196. package/dist/services/single-undo-dance.test.d.ts +2 -0
  197. package/dist/services/single-undo-dance.test.d.ts.map +1 -0
  198. package/dist/services/single-undo-dance.test.js +210 -0
  199. package/dist/services/snap.d.ts +72 -0
  200. package/dist/services/snap.d.ts.map +1 -0
  201. package/dist/services/snap.js +120 -0
  202. package/dist/services/snap.test.d.ts +2 -0
  203. package/dist/services/snap.test.d.ts.map +1 -0
  204. package/dist/services/snap.test.js +95 -0
  205. package/dist/solar-panel-presets.d.ts +17 -0
  206. package/dist/solar-panel-presets.d.ts.map +1 -0
  207. package/dist/solar-panel-presets.js +43 -0
  208. package/dist/store/actions/node-actions.d.ts +14 -2
  209. package/dist/store/actions/node-actions.d.ts.map +1 -1
  210. package/dist/store/actions/node-actions.js +499 -20
  211. package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
  212. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
  213. package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
  214. package/dist/store/actions/reparent.test.d.ts +2 -0
  215. package/dist/store/actions/reparent.test.d.ts.map +1 -0
  216. package/dist/store/actions/reparent.test.js +212 -0
  217. package/dist/store/use-interactive.d.ts +39 -0
  218. package/dist/store/use-interactive.d.ts.map +1 -1
  219. package/dist/store/use-interactive.js +75 -0
  220. package/dist/store/use-live-node-overrides.d.ts +22 -0
  221. package/dist/store/use-live-node-overrides.d.ts.map +1 -0
  222. package/dist/store/use-live-node-overrides.js +59 -0
  223. package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
  224. package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
  225. package/dist/store/use-scene-elevator-migration.test.js +109 -0
  226. package/dist/store/use-scene.d.ts +11 -0
  227. package/dist/store/use-scene.d.ts.map +1 -1
  228. package/dist/store/use-scene.js +207 -11
  229. package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
  230. package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
  231. package/dist/systems/elevator/elevator-dispatch.js +57 -0
  232. package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
  233. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
  234. package/dist/systems/elevator/elevator-geometry.js +50 -0
  235. package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
  236. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
  237. package/dist/systems/elevator/elevator-opening-sync.js +218 -0
  238. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
  239. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
  240. package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
  241. package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
  242. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
  243. package/dist/systems/elevator/elevator-opening-system.js +48 -0
  244. package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
  245. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
  246. package/dist/systems/elevator/elevator-runtime-system.js +8 -0
  247. package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
  248. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
  249. package/dist/systems/elevator/elevator-runtime.js +225 -0
  250. package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
  251. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
  252. package/dist/systems/elevator/elevator-runtime.test.js +64 -0
  253. package/dist/systems/elevator/elevator-service.d.ts +19 -0
  254. package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
  255. package/dist/systems/elevator/elevator-service.js +111 -0
  256. package/dist/systems/stair/stair-footprint.d.ts +44 -0
  257. package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
  258. package/dist/systems/stair/stair-footprint.js +168 -0
  259. package/dist/systems/stair/stair-opening-preview.d.ts +1777 -0
  260. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
  261. package/dist/systems/stair/stair-opening-preview.js +93 -0
  262. package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
  263. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
  264. package/dist/systems/stair/stair-opening-preview.test.js +80 -0
  265. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  266. package/dist/systems/stair/stair-opening-sync.js +183 -185
  267. package/dist/systems/stair/stair-opening-sync.test.js +385 -4
  268. package/dist/systems/stair/stair-opening-system.d.ts +2 -0
  269. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
  270. package/dist/systems/stair/stair-opening-system.js +104 -0
  271. package/dist/systems/wall/wall-curve.d.ts +1 -1
  272. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  273. package/dist/systems/wall/wall-curve.js +1 -1
  274. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  275. package/dist/systems/wall/wall-mitering.js +2 -6
  276. package/dist/systems/wall/wall-move.d.ts +48 -0
  277. package/dist/systems/wall/wall-move.d.ts.map +1 -0
  278. package/dist/systems/wall/wall-move.js +193 -0
  279. package/dist/utils/clone-scene-graph.d.ts +6 -4
  280. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  281. package/dist/utils/clone-scene-graph.js +6 -4
  282. package/dist/utils/clone-scene-graph.test.d.ts +2 -0
  283. package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
  284. package/dist/utils/clone-scene-graph.test.js +59 -0
  285. package/dist/validation/validate-build-json.d.ts +43 -0
  286. package/dist/validation/validate-build-json.d.ts.map +1 -0
  287. package/dist/validation/validate-build-json.js +227 -0
  288. package/package.json +11 -3
  289. package/dist/materials.d.ts +0 -10
  290. package/dist/materials.d.ts.map +0 -1
  291. package/dist/materials.js +0 -22
  292. package/dist/systems/ceiling/ceiling-system.d.ts +0 -8
  293. package/dist/systems/ceiling/ceiling-system.d.ts.map +0 -1
  294. package/dist/systems/ceiling/ceiling-system.js +0 -92
  295. package/dist/systems/door/door-system.d.ts +0 -2
  296. package/dist/systems/door/door-system.d.ts.map +0 -1
  297. package/dist/systems/door/door-system.js +0 -195
  298. package/dist/systems/fence/fence-system.d.ts +0 -2
  299. package/dist/systems/fence/fence-system.d.ts.map +0 -1
  300. package/dist/systems/fence/fence-system.js +0 -187
  301. package/dist/systems/item/item-system.d.ts +0 -2
  302. package/dist/systems/item/item-system.d.ts.map +0 -1
  303. package/dist/systems/item/item-system.js +0 -48
  304. package/dist/systems/roof/roof-system.d.ts +0 -16
  305. package/dist/systems/roof/roof-system.d.ts.map +0 -1
  306. package/dist/systems/roof/roof-system.js +0 -797
  307. package/dist/systems/slab/slab-system.d.ts +0 -8
  308. package/dist/systems/slab/slab-system.d.ts.map +0 -1
  309. package/dist/systems/slab/slab-system.js +0 -214
  310. package/dist/systems/stair/stair-system.d.ts +0 -2
  311. package/dist/systems/stair/stair-system.d.ts.map +0 -1
  312. package/dist/systems/stair/stair-system.js +0 -776
  313. package/dist/systems/wall/wall-system.d.ts +0 -12
  314. package/dist/systems/wall/wall-system.d.ts.map +0 -1
  315. package/dist/systems/wall/wall-system.js +0 -455
  316. package/dist/systems/window/window-system.d.ts +0 -2
  317. package/dist/systems/window/window-system.d.ts.map +0 -1
  318. package/dist/systems/window/window-system.js +0 -131
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=alignment.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alignment.test.d.ts","sourceRoot":"","sources":["../../src/services/alignment.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,92 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { bboxAnchors, resolveAlignment } from './alignment';
3
+ function center(nodeId, x, z) {
4
+ return { nodeId, kind: 'center', x, z };
5
+ }
6
+ function corner(nodeId, x, z) {
7
+ return { nodeId, kind: 'corner', x, z };
8
+ }
9
+ describe('resolveAlignment', () => {
10
+ test('returns empty when no candidates within threshold', () => {
11
+ const result = resolveAlignment({
12
+ moving: [center('m', 0, 0)],
13
+ candidates: [center('a', 1, 1)],
14
+ threshold: 0.5,
15
+ });
16
+ expect(result.guides).toEqual([]);
17
+ expect(result.snap).toBeNull();
18
+ });
19
+ test('snaps moving anchor onto candidate X when within threshold', () => {
20
+ const result = resolveAlignment({
21
+ moving: [center('m', 0.03, 5)],
22
+ candidates: [center('a', 0, 2)],
23
+ threshold: 0.1,
24
+ });
25
+ expect(result.snap).toEqual({ dx: -0.03, dz: 0 });
26
+ expect(result.guides).toHaveLength(1);
27
+ expect(result.guides[0].axis).toBe('x');
28
+ expect(result.guides[0].coord).toBe(0);
29
+ expect(result.guides[0].from.z).toBe(2);
30
+ expect(result.guides[0].to.z).toBe(5);
31
+ });
32
+ test('snaps both axes when both match', () => {
33
+ const result = resolveAlignment({
34
+ moving: [center('m', 0.03, 0.04)],
35
+ candidates: [center('a', 0, 0)],
36
+ threshold: 0.1,
37
+ });
38
+ expect(result.snap).toEqual({ dx: -0.03, dz: -0.04 });
39
+ expect(result.guides).toHaveLength(2);
40
+ });
41
+ test('picks closest candidate per axis', () => {
42
+ const result = resolveAlignment({
43
+ moving: [center('m', 0.08, 0)],
44
+ candidates: [center('a', 0, 5), center('b', 0.1, 5), center('c', 0.05, 10)],
45
+ threshold: 0.1,
46
+ });
47
+ // |0.1 - 0.08| = 0.02 wins over |0.05 - 0.08| = 0.03 and |0 - 0.08| = 0.08
48
+ expect(result.snap?.dx).toBeCloseTo(0.02, 10);
49
+ expect(result.guides[0].candidateNodeId).toBe('b');
50
+ });
51
+ test('ties on the matched axis break toward the nearest perpendicular anchor', () => {
52
+ const result = resolveAlignment({
53
+ moving: [corner('m', 0.02, 4)],
54
+ candidates: [corner('far', 0, 0), corner('near', 0, 5)],
55
+ threshold: 0.1,
56
+ });
57
+ // Both share X (Δx = 0.02); 'near' (z=5) is closer to the moving z=4 than
58
+ // 'far' (z=0), so the guide connects to the nearest real anchor.
59
+ expect(result.guides[0].candidateNodeId).toBe('near');
60
+ });
61
+ test('threshold = 0 disables alignment', () => {
62
+ const result = resolveAlignment({
63
+ moving: [center('m', 0, 0)],
64
+ candidates: [center('a', 0, 0)],
65
+ threshold: 0,
66
+ });
67
+ expect(result.guides).toEqual([]);
68
+ expect(result.snap).toBeNull();
69
+ });
70
+ test('distance is the perpendicular gap to the matched axis', () => {
71
+ const result = resolveAlignment({
72
+ moving: [center('m', 0.02, 3)],
73
+ candidates: [center('a', 0, 0)],
74
+ threshold: 0.1,
75
+ });
76
+ // After snap: moving at (0, 3). X guide runs along x=0 from z=0 to z=3.
77
+ expect(result.guides[0].distance).toBeCloseTo(3, 10);
78
+ });
79
+ });
80
+ describe('bboxAnchors', () => {
81
+ test('returns 9 anchors with correct kinds and positions', () => {
82
+ const anchors = bboxAnchors('node', 0, 0, 2, 4);
83
+ expect(anchors).toHaveLength(9);
84
+ const corners = anchors.filter((a) => a.kind === 'corner');
85
+ const edges = anchors.filter((a) => a.kind === 'edge-mid');
86
+ const centers = anchors.filter((a) => a.kind === 'center');
87
+ expect(corners).toHaveLength(4);
88
+ expect(edges).toHaveLength(4);
89
+ expect(centers).toHaveLength(1);
90
+ expect(centers[0]).toEqual({ nodeId: 'node', kind: 'center', x: 1, z: 2 });
91
+ });
92
+ });
@@ -0,0 +1,47 @@
1
+ import { type ChildQuery, type SpatialQuery } from '../registry/relations-resolver';
2
+ import type { DragAction, Modifiers, SceneApi } from '../registry/types';
3
+ import type { AnyNode } from '../schema/types';
4
+ import type { Vec2 } from './snap';
5
+ /**
6
+ * Pure orchestrator for a single `DragAction` lifecycle:
7
+ * begin → (preview → snap? → apply → cascade dirty)* → commit | cancel
8
+ *
9
+ * Bracketed by `pauseHistory()` / `resumeHistory()` so the entire drag is one
10
+ * undo step. The React hook (`useDragAction` in `@pascal-app/editor`) wraps
11
+ * this with event subscriptions; tests drive it directly.
12
+ */
13
+ export type DragSessionInput = {
14
+ node?: AnyNode;
15
+ point: Vec2;
16
+ handleId?: string;
17
+ modifiers?: Modifiers;
18
+ };
19
+ export type DragSessionOptions = {
20
+ spatialQuery?: SpatialQuery;
21
+ childQuery?: ChildQuery;
22
+ /** Called once the session terminates via `commit()`. */
23
+ onCommit?: () => void;
24
+ /** Called once the session terminates via `cancel()` or `dispose()`. */
25
+ onCancel?: () => void;
26
+ };
27
+ export type DragSession<Ctx, Draft> = {
28
+ /** Begin the drag — pause history, capture ctx via `action.begin`. */
29
+ start: (input: DragSessionInput) => void;
30
+ /** Per-pointer-move tick — run preview/snap/apply and cascade dirty marks. */
31
+ move: (point: Vec2, modifiers: Modifiers) => void;
32
+ /** Pointer-up / discrete commit. Returns true if `action.commit` agreed. */
33
+ commit: () => boolean;
34
+ /** Pointer-cancel / Esc / external abort — restores all touched nodes. */
35
+ cancel: () => void;
36
+ /** Returns the latest draft `apply` produced (or null before first move). */
37
+ getDraft: () => Draft | null;
38
+ isActive: () => boolean;
39
+ /** Idempotent cleanup. If active, restores scene state and resumes
40
+ * history, but does **not** fire `onCancel`. Use for React-effect
41
+ * teardown — onCancel would re-trigger the parent's state machine and
42
+ * break StrictMode's double-mount cycle. Esc / external aborts must
43
+ * still call `cancel()` directly. */
44
+ dispose: () => void;
45
+ };
46
+ export declare function createDragSession<Ctx, Draft>(action: DragAction<Ctx, Draft>, scene: SceneApi, options?: DragSessionOptions): DragSession<Ctx, Draft>;
47
+ //# sourceMappingURL=drag-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drag-session.d.ts","sourceRoot":"","sources":["../../src/services/drag-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAgB,KAAK,YAAY,EAAE,MAAM,gCAAgC,CAAA;AACjG,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACxE,OAAO,KAAK,EAAE,OAAO,EAAa,MAAM,iBAAiB,CAAA;AACzD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAElC;;;;;;;GAOG;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,EAAE,IAAI,CAAA;IACX,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,WAAW,CAAC,GAAG,EAAE,KAAK,IAAI;IACpC,sEAAsE;IACtE,KAAK,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAA;IACxC,8EAA8E;IAC9E,IAAI,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,KAAK,IAAI,CAAA;IACjD,4EAA4E;IAC5E,MAAM,EAAE,MAAM,OAAO,CAAA;IACrB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,KAAK,GAAG,IAAI,CAAA;IAC5B,QAAQ,EAAE,MAAM,OAAO,CAAA;IACvB;;;;yCAIqC;IACrC,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAID,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAC1C,MAAM,EAAE,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,EAC9B,KAAK,EAAE,QAAQ,EACf,OAAO,GAAE,kBAAuB,GAC/B,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAiGzB"}
@@ -0,0 +1,100 @@
1
+ import { cascadeDirty } from '../registry/relations-resolver';
2
+ const EMPTY_MODIFIERS = { shift: false, alt: false, ctrl: false, meta: false };
3
+ export function createDragSession(action, scene, options = {}) {
4
+ let active = false;
5
+ let ctx = null;
6
+ let draft = null;
7
+ let dirtyMarked = new Set();
8
+ function markWithCascade(id) {
9
+ if (dirtyMarked.has(id))
10
+ return;
11
+ const ids = cascadeDirty(id, {
12
+ scene,
13
+ spatialQuery: options.spatialQuery,
14
+ childQuery: options.childQuery,
15
+ });
16
+ for (const dirtyId of ids) {
17
+ if (!dirtyMarked.has(dirtyId)) {
18
+ scene.markDirty(dirtyId);
19
+ dirtyMarked.add(dirtyId);
20
+ }
21
+ }
22
+ }
23
+ function terminate(committed) {
24
+ if (!active)
25
+ return;
26
+ active = false;
27
+ ctx = null;
28
+ draft = null;
29
+ dirtyMarked = new Set();
30
+ scene.resumeHistory();
31
+ if (committed)
32
+ options.onCommit?.();
33
+ else
34
+ options.onCancel?.();
35
+ }
36
+ return {
37
+ start(input) {
38
+ if (active)
39
+ return; // ignore re-entry
40
+ scene.pauseHistory();
41
+ ctx = action.begin({
42
+ node: input.node,
43
+ point: input.point,
44
+ handleId: input.handleId,
45
+ modifiers: input.modifiers ?? EMPTY_MODIFIERS,
46
+ });
47
+ active = true;
48
+ },
49
+ move(point, modifiers) {
50
+ if (!active || ctx == null)
51
+ return;
52
+ let next = action.preview(ctx, point, modifiers);
53
+ if (action.snap) {
54
+ next = action.snap(next, ctx, undefined);
55
+ }
56
+ draft = next;
57
+ const dirtyIds = action.apply(next, ctx, scene);
58
+ for (const id of dirtyIds)
59
+ markWithCascade(id);
60
+ },
61
+ commit() {
62
+ if (!active || ctx == null)
63
+ return false;
64
+ const ok = action.commit?.(draft, ctx, scene) ?? true;
65
+ if (!ok) {
66
+ action.cancel(ctx, scene);
67
+ scene.restoreAll();
68
+ }
69
+ terminate(ok);
70
+ return ok;
71
+ },
72
+ cancel() {
73
+ if (!active || ctx == null)
74
+ return;
75
+ action.cancel(ctx, scene);
76
+ scene.restoreAll();
77
+ terminate(false);
78
+ },
79
+ getDraft() {
80
+ return draft;
81
+ },
82
+ isActive() {
83
+ return active;
84
+ },
85
+ dispose() {
86
+ if (active && ctx != null) {
87
+ action.cancel(ctx, scene);
88
+ scene.restoreAll();
89
+ // Silent terminate: no onCancel. The caller (e.g. useDragAction's
90
+ // effect cleanup) is reacting to the parent unmounting and would
91
+ // loop the state machine if onCancel re-set the parent's state.
92
+ active = false;
93
+ ctx = null;
94
+ draft = null;
95
+ dirtyMarked = new Set();
96
+ scene.resumeHistory();
97
+ }
98
+ },
99
+ };
100
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=drag-session.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drag-session.test.d.ts","sourceRoot":"","sources":["../../src/services/drag-session.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,219 @@
1
+ import { beforeEach, describe, expect, mock, test } from 'bun:test';
2
+ import { z } from 'zod';
3
+ import { nodeRegistry, registerNode } from '../registry/registry';
4
+ import { createDragSession } from './drag-session';
5
+ const id = (s) => s;
6
+ function makeSpyScene(initial = {}) {
7
+ const calls = {
8
+ pauseHistory: 0,
9
+ resumeHistory: 0,
10
+ restoreAll: 0,
11
+ markedDirty: [],
12
+ updated: [],
13
+ };
14
+ const nodes = { ...initial };
15
+ return {
16
+ get: ((nid) => nodes[nid]),
17
+ nodes: () => nodes,
18
+ update: (nid, patch) => {
19
+ calls.updated.push([nid, patch]);
20
+ const existing = nodes[nid];
21
+ if (existing)
22
+ nodes[nid] = { ...existing, ...patch };
23
+ },
24
+ upsert: (n) => {
25
+ nodes[n.id] = n;
26
+ return n.id;
27
+ },
28
+ delete: (nid) => {
29
+ delete nodes[nid];
30
+ },
31
+ restore: () => { },
32
+ restoreAll: () => {
33
+ calls.restoreAll += 1;
34
+ },
35
+ markDirty: (nid) => {
36
+ calls.markedDirty.push(nid);
37
+ },
38
+ pauseHistory: () => {
39
+ calls.pauseHistory += 1;
40
+ },
41
+ resumeHistory: () => {
42
+ calls.resumeHistory += 1;
43
+ },
44
+ getSubtree: () => null,
45
+ cloneNodesInto: () => null,
46
+ _calls: calls,
47
+ };
48
+ }
49
+ function makeDef(kind, relations) {
50
+ return {
51
+ kind,
52
+ schemaVersion: 1,
53
+ schema: z.object({ type: z.literal(kind) }),
54
+ category: 'utility',
55
+ defaults: () => ({}),
56
+ capabilities: {},
57
+ relations,
58
+ renderer: { kind: 'parametric', module: async () => ({ default: () => null }) },
59
+ };
60
+ }
61
+ function makeAction() {
62
+ return {
63
+ begin: ({ node }) => ({ id: node?.id ?? id('default') }),
64
+ preview: (_ctx, point) => ({ x: point[0] }),
65
+ apply: (_draft, ctx) => [ctx.id],
66
+ cancel: () => { },
67
+ };
68
+ }
69
+ describe('createDragSession', () => {
70
+ beforeEach(() => {
71
+ nodeRegistry._reset();
72
+ });
73
+ test('start pauses history; commit resumes', () => {
74
+ const scene = makeSpyScene();
75
+ const session = createDragSession(makeAction(), scene);
76
+ session.start({ point: [0, 0] });
77
+ expect(scene._calls.pauseHistory).toBe(1);
78
+ expect(scene._calls.resumeHistory).toBe(0);
79
+ session.commit();
80
+ expect(scene._calls.resumeHistory).toBe(1);
81
+ });
82
+ test('cancel resumes history and calls restoreAll', () => {
83
+ const scene = makeSpyScene();
84
+ const session = createDragSession(makeAction(), scene);
85
+ session.start({ point: [0, 0] });
86
+ session.cancel();
87
+ expect(scene._calls.resumeHistory).toBe(1);
88
+ expect(scene._calls.restoreAll).toBe(1);
89
+ });
90
+ test('move runs preview + apply and marks the returned id dirty', () => {
91
+ const scene = makeSpyScene({ a: { id: id('a'), type: 'thing' } });
92
+ const session = createDragSession(makeAction(), scene);
93
+ session.start({ point: [0, 0], node: { id: id('a') } });
94
+ session.move([1, 0], { shift: false, alt: false, ctrl: false, meta: false });
95
+ expect(session.getDraft()).toEqual({ x: 1 });
96
+ expect(scene._calls.markedDirty).toContain(id('a'));
97
+ });
98
+ test('snap callback is invoked when defined', () => {
99
+ const action = {
100
+ ...makeAction(),
101
+ snap: (draft) => ({ x: Math.round(draft.x) }),
102
+ };
103
+ const scene = makeSpyScene({ a: { id: id('a'), type: 'thing' } });
104
+ const session = createDragSession(action, scene);
105
+ session.start({ point: [0, 0], node: { id: id('a') } });
106
+ session.move([0.7, 0], { shift: false, alt: false, ctrl: false, meta: false });
107
+ expect(session.getDraft()).toEqual({ x: 1 });
108
+ });
109
+ test('commit returns false when action.commit returns false; calls action.cancel and restoreAll', () => {
110
+ const cancelSpy = mock(() => { });
111
+ const action = {
112
+ ...makeAction(),
113
+ cancel: cancelSpy,
114
+ commit: () => false,
115
+ };
116
+ const scene = makeSpyScene({ a: { id: id('a'), type: 'thing' } });
117
+ const session = createDragSession(action, scene);
118
+ session.start({ point: [0, 0], node: { id: id('a') } });
119
+ session.move([1, 0], { shift: false, alt: false, ctrl: false, meta: false });
120
+ const result = session.commit();
121
+ expect(result).toBe(false);
122
+ expect(cancelSpy).toHaveBeenCalledTimes(1);
123
+ expect(scene._calls.restoreAll).toBe(1);
124
+ });
125
+ test('move is a no-op when session is not active', () => {
126
+ const scene = makeSpyScene();
127
+ const session = createDragSession(makeAction(), scene);
128
+ session.move([1, 0], { shift: false, alt: false, ctrl: false, meta: false });
129
+ expect(scene._calls.markedDirty.length).toBe(0);
130
+ });
131
+ test('repeated start is a no-op (re-entry guard)', () => {
132
+ const scene = makeSpyScene();
133
+ const session = createDragSession(makeAction(), scene);
134
+ session.start({ point: [0, 0] });
135
+ session.start({ point: [99, 99] });
136
+ expect(scene._calls.pauseHistory).toBe(1); // only one pause
137
+ });
138
+ test('dispose mid-drag cancels and cleans up', () => {
139
+ const scene = makeSpyScene();
140
+ const session = createDragSession(makeAction(), scene);
141
+ session.start({ point: [0, 0] });
142
+ expect(session.isActive()).toBe(true);
143
+ session.dispose();
144
+ expect(session.isActive()).toBe(false);
145
+ expect(scene._calls.resumeHistory).toBe(1);
146
+ expect(scene._calls.restoreAll).toBe(1);
147
+ });
148
+ test('dispose when inactive is a no-op', () => {
149
+ const scene = makeSpyScene();
150
+ const session = createDragSession(makeAction(), scene);
151
+ session.dispose();
152
+ expect(scene._calls.pauseHistory).toBe(0);
153
+ expect(scene._calls.resumeHistory).toBe(0);
154
+ });
155
+ test('onCommit callback fires on successful commit', () => {
156
+ const onCommit = mock(() => { });
157
+ const onCancel = mock(() => { });
158
+ const scene = makeSpyScene();
159
+ const session = createDragSession(makeAction(), scene, { onCommit, onCancel });
160
+ session.start({ point: [0, 0] });
161
+ session.commit();
162
+ expect(onCommit).toHaveBeenCalledTimes(1);
163
+ expect(onCancel).toHaveBeenCalledTimes(0);
164
+ });
165
+ test('onCancel callback fires on explicit cancel', () => {
166
+ const onCommit = mock(() => { });
167
+ const onCancel = mock(() => { });
168
+ const scene = makeSpyScene();
169
+ const session = createDragSession(makeAction(), scene, { onCommit, onCancel });
170
+ session.start({ point: [0, 0] });
171
+ session.cancel();
172
+ expect(onCommit).toHaveBeenCalledTimes(0);
173
+ expect(onCancel).toHaveBeenCalledTimes(1);
174
+ });
175
+ test('dispose does NOT fire onCancel (silent cleanup)', () => {
176
+ const onCommit = mock(() => { });
177
+ const onCancel = mock(() => { });
178
+ const scene = makeSpyScene();
179
+ const session = createDragSession(makeAction(), scene, { onCommit, onCancel });
180
+ session.start({ point: [0, 0] });
181
+ session.dispose();
182
+ expect(onCommit).toHaveBeenCalledTimes(0);
183
+ expect(onCancel).toHaveBeenCalledTimes(0);
184
+ expect(scene._calls.resumeHistory).toBe(1);
185
+ expect(scene._calls.restoreAll).toBe(1);
186
+ });
187
+ test('dirty cascade fires once per id even across multiple move ticks', () => {
188
+ // Register a kind with no relations — cascade returns just {startId}.
189
+ registerNode(makeDef('thing'));
190
+ const scene = makeSpyScene({ a: { id: id('a'), type: 'thing' } });
191
+ const session = createDragSession(makeAction(), scene);
192
+ session.start({ point: [0, 0], node: { id: id('a') } });
193
+ session.move([1, 0], { shift: false, alt: false, ctrl: false, meta: false });
194
+ session.move([2, 0], { shift: false, alt: false, ctrl: false, meta: false });
195
+ session.move([3, 0], { shift: false, alt: false, ctrl: false, meta: false });
196
+ // a is marked once, not three times
197
+ expect(scene._calls.markedDirty.filter((mid) => mid === id('a')).length).toBe(1);
198
+ });
199
+ test('dirty cascade follows hosts relations from the registry', () => {
200
+ registerNode(makeDef('wall', { hosts: ['door'] }));
201
+ registerNode(makeDef('door'));
202
+ const scene = makeSpyScene({
203
+ w: { id: id('w'), type: 'wall', children: [id('d')] },
204
+ d: { id: id('d'), type: 'door', parentId: id('w') },
205
+ });
206
+ const action = {
207
+ begin: () => ({ id: id('w') }),
208
+ preview: (_ctx, point) => ({ x: point[0] }),
209
+ apply: (_draft, ctx) => [ctx.id],
210
+ cancel: () => { },
211
+ };
212
+ const session = createDragSession(action, scene);
213
+ session.start({ point: [0, 0], node: { id: id('w') } });
214
+ session.move([1, 0], { shift: false, alt: false, ctrl: false, meta: false });
215
+ // both wall and door marked dirty
216
+ expect(scene._calls.markedDirty).toContain(id('w'));
217
+ expect(scene._calls.markedDirty).toContain(id('d'));
218
+ });
219
+ });
@@ -0,0 +1,80 @@
1
+ import type { SceneApi, SurfacesConfig } from '../registry/types';
2
+ import type { AnyNode, AnyNodeId } from '../schema/types';
3
+ /**
4
+ * Maximum depth a node tree can host. Guards items-on-items-on-items chains
5
+ * from growing pathological — pre-Phase-1 the editor had no cap. Set high
6
+ * enough to allow legitimate stacking (chair on platform on truck on floor)
7
+ * while preventing AI/plugin-generated runaway.
8
+ */
9
+ export declare const MAX_HOST_DEPTH = 6;
10
+ export type Vec3 = readonly [number, number, number];
11
+ export type AttachError = {
12
+ kind: 'self-host';
13
+ nodeId: AnyNodeId;
14
+ } | {
15
+ kind: 'cycle';
16
+ nodeId: AnyNodeId;
17
+ hostId: AnyNodeId;
18
+ } | {
19
+ kind: 'depth-exceeded';
20
+ depth: number;
21
+ max: number;
22
+ } | {
23
+ kind: 'host-missing';
24
+ hostId: AnyNodeId;
25
+ } | {
26
+ kind: 'kind-not-allowed';
27
+ hostKind: string;
28
+ allowed: readonly string[];
29
+ };
30
+ export type AttachResult = {
31
+ ok: true;
32
+ } | {
33
+ ok: false;
34
+ error: AttachError;
35
+ };
36
+ /**
37
+ * Validates that attaching `child` to `host` is safe and either returns an
38
+ * actionable error or signals OK. Does NOT mutate the scene — callers apply
39
+ * the patch after a successful check.
40
+ *
41
+ * Rules:
42
+ * - A node cannot host itself.
43
+ * - The hosting chain (child → host → host.parent → ...) must not contain
44
+ * `child` (cycle prevention).
45
+ * - The resulting chain must not exceed {@link MAX_HOST_DEPTH}.
46
+ * - If the child's NodeDefinition declares `capabilities.hostable.parents`,
47
+ * `host.type` must appear in that list.
48
+ */
49
+ export declare function canAttach(childId: AnyNodeId, hostId: AnyNodeId, scene: SceneApi): AttachResult;
50
+ /**
51
+ * Returns the surfaces declared by a host's NodeDefinition. Surfaces describe
52
+ * where other nodes can stack/mount — the `top` of a slab, the `sides` of a
53
+ * wall, or a custom callback. Returns null when the host's def declares no
54
+ * surfaces (or no def is registered).
55
+ */
56
+ export declare function getSurface(host: AnyNode): SurfacesConfig | null;
57
+ /**
58
+ * Resolves the stackable top height of a host (e.g. table surface, slab top,
59
+ * stair landing). Returns `null` when the host has no `surfaces.top`.
60
+ */
61
+ export declare function getTopSurfaceHeight(host: AnyNode): number | null;
62
+ /**
63
+ * Pure host-discovery helper. Given a list of candidate hosts (already
64
+ * narrowed by spatial query) and a point, returns the first whose
65
+ * `capabilities.hostable` lists `placedKind` AND whose surface contains the
66
+ * point. The runtime is responsible for providing pre-filtered candidates;
67
+ * this function does not perform spatial queries itself.
68
+ */
69
+ export declare function pickHost(args: {
70
+ point: Vec3;
71
+ candidates: readonly AnyNode[];
72
+ placedKind: string;
73
+ hitTest?: (host: AnyNode, point: Vec3) => boolean;
74
+ }): AnyNode | null;
75
+ /**
76
+ * Convenience: clamps a Y coordinate to the top of a host surface, when one
77
+ * is declared. Returns the original Y if the host has no top surface.
78
+ */
79
+ export declare function clampYToHostTop(host: AnyNode, originalY: number): number;
80
+ //# sourceMappingURL=hosting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hosting.d.ts","sourceRoot":"","sources":["../../src/services/hosting.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEzD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,IAAI,CAAA;AAE/B,MAAM,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAEpD,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAA;AAE9E,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAA;AAE3E;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,GAAG,YAAY,CAoC9F;AAoBD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAG/D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAKhE;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE;IAC7B,KAAK,EAAE,IAAI,CAAA;IACX,UAAU,EAAE,SAAS,OAAO,EAAE,CAAA;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,KAAK,OAAO,CAAA;CAClD,GAAG,OAAO,GAAG,IAAI,CAajB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGxE"}