feat(editor): placement & interaction overhaul — FSM spine, bug tracks, perf

Implements plans/editor-placement-interaction-overhaul.md: an authoritative
interaction-scope state machine plus the catalogued placement/interaction
fixes, and split-view floor-plan performance.

- Interaction-scope spine (lib/interaction/* + store/use-interaction-scope),
  driven from central useEditor setters; overlay scoping (zone labels,
  context badges, floating action menu) reads resolveOverlayPolicy.
- Bug tracks A/B/D/E/F/G/H: handle/cutout raycast, footprint validity,
  auto-slab loop, ceiling hosting, B-key tool desync, 2D drop offset,
  per-frame jank.
- Snapping modes (grid/lines/angles/off) + contextual HUD chips; modifier
  model (Shift=cycle, Alt=free place, Ctrl=grid step).
- Item move now tracks the cursor 1:1 (was a laggy per-frame lerp); handle
  rig hides during a whole-node move; rotate gizmo advertises Shift=free
  rotation in the HUD and hides the move cross while rotating.
- Floor-plan perf: pause live reactivity while in 3D-only view; per-node
  geometry cache so only changed nodes rebuild on a drag; hoist wall miters
  to a once-per-pass ctx.levelData (O(N^2) -> O(N) on wall/opening drags).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-06-23 09:04:58 -04:00
co-authored by Claude Opus 4.8
parent b2f1a8432e
commit f773e6b8c5
71 changed files with 2362 additions and 598 deletions
@@ -10,6 +10,20 @@ export type OpeningCutoutRect = {
top: number
}
// The cutout proxy doubles as the invisible raycast hit target for an opening:
// centered on the wall and extending past both faces so it wins the scene
// raycast over the recessed door/window body for front AND back selection +
// paint. It only needs to clear the wall thickness plus a small proud margin —
// the wall CSG brush ignores this proxy's depth entirely (it rebuilds its own
// full-thickness box from the proxy's X/Y bounds in `collectCutoutBrushes`), so
// a snug depth keeps the cut intact while no longer blanketing the room floor in
// a top-down view (the bug a 1m-deep proxy caused in narrow hallways).
const OPENING_CUTOUT_PROXY_PROUD_MARGIN = 0.08
export function getOpeningCutoutProxyDepth(wallThickness: number): number {
return Math.max(wallThickness, 0) + OPENING_CUTOUT_PROXY_PROUD_MARGIN
}
type CornerRadii = {
topLeft: number
topRight: number