Merge origin/main (#407 placement restructure) into opening-proximity-guides

#407 ("Always-visible placement ghosts + true-nearest 2D opening snap")
restructured the door/window placement tools: it split the old
create-in-resolve into a pure resolveWallPlacement() + side-effecting
applyWallTarget(), added an off-host floating ghost (fallbackPose / showGhostAt),
unified wall hover into onWallHover, and extracted commit{Door,Window}AtWall.

Conflict resolution (door/tool.tsx, window/tool.tsx):
- Re-homed the single publishOpeningGuidesForWallEvent() call into applyWallTarget
  (after the draft update + updateCursor), using that scope (wall,
  getSlabElevationForWall(wall)); door includeVertical:false, window true.
- Routed clearOpeningGuides3D() through showGhostAt so every off-host fallback
  path clears; kept clears in hideCursor, commit helpers, onRoofHover, teardown.
- Made the window sill snap (resolvePlacementY) event-free and call it from the
  pure resolveWallPlacement, so hover + click both get sill/centre/top snapping;
  Shift bypasses, the moving draft is excluded via ignoreId.
- Dropped the branch's inline onWallClick in favour of #407's onWallClick +
  commitWindowAtWall (no behavior lost).
- Reconstructed both files' import blocks, which the auto-merge had truncated to
  stubs (only tsc caught it).

All other conflicts auto-merged (registry types, floorplan-registry-layer,
both move-tools). Verified: typecheck 9/9, biome clean, nodes 169 + core 594
tests pass, editor `bun run build` 7/7. Merge resolution reviewed by Codex
(adversarial): no semantic regressions; all #407 behavior preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aymeric Rabot
2026-06-15 12:58:00 -04:00
co-authored by Claude Opus 4.8
59 changed files with 2212 additions and 807 deletions
+2 -2
View File
@@ -89,7 +89,7 @@ export {
createColumnTorusGeometry,
} from './systems/column/column-geometry'
export { DoorAnimationSystem } from './systems/door/door-animation-system'
export { DoorSystem } from './systems/door/door-system'
export { buildDoorPreviewMesh, DoorSystem } from './systems/door/door-system'
export { ElevatorInteractionSystem } from './systems/elevator/elevator-interaction-system'
// Fence system follows the wall re-export pattern — composed into the
// registry-driven fence definition's `def.system`. Removed in Phase 6
@@ -151,5 +151,5 @@ export { getVisibleWallMaterials } from './systems/wall/wall-materials'
// removed in Phase 6 when the legacy mount points are deleted.
export { WallSystem } from './systems/wall/wall-system'
export { WindowAnimationSystem } from './systems/window/window-animation-system'
export { WindowSystem } from './systems/window/window-system'
export { buildWindowPreviewMesh, WindowSystem } from './systems/window/window-system'
export { ZoneSystem } from './systems/zone/zone-system'
@@ -2372,3 +2372,13 @@ function syncDoorCutout(node: DoorNode, mesh: THREE.Mesh) {
}
cutout.visible = false
}
/**
* Build a fresh door mesh for preview/ghost rendering.
* Returns a mesh with an invisible hitbox root and visible children (frame, panels, hardware).
*/
export function buildDoorPreviewMesh(node: DoorNode): THREE.Mesh {
const mesh = new THREE.Mesh()
updateDoorMesh(node, mesh)
return mesh
}
@@ -3457,3 +3457,13 @@ function syncWindowCutout(node: WindowNode, mesh: THREE.Mesh) {
}
cutout.visible = false
}
/**
* Build a fresh window mesh for preview/ghost rendering.
* Returns a mesh with an invisible hitbox root and visible children (frame, glass, sash, hardware).
*/
export function buildWindowPreviewMesh(node: WindowNode): THREE.Mesh {
const mesh = new THREE.Mesh()
updateWindowMesh(node, mesh)
return mesh
}