Files
editor/packages/nodes
4fca38a3ef fix: make room walls and slabs join cleanly (#497)
* fix(editor): wall endpoint move — detach/attach modifier sync and zero-move drop

- restore linked walls to original positions the moment alt-detach engages
- re-run the endpoint preview on alt keydown/keyup so re-attach doesn't wait for a mousemove; preview, HUD badge, and commit share one alt source
- second click at an unmoved position cleanly drops the endpoint (no history entry) instead of leaving the interaction stuck
- 2D floorplan parity: clear stale linked overrides on detach, re-apply drag sessions on modifier changes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core): demote orphaned auto slabs/ceilings to manual instead of deleting

Deleting a wall that encloses a room no longer destroys the room's
slab/ceiling (paint, holes, elevation). Unmatched auto surfaces are only
deleted when >=60% of their footprint is still covered by a detected room
(rooms-merged case); otherwise they are demoted to manual nodes.

- demoted slab polygons are baked (inset by SLAB_OUTSET + AUTO_SLAB_INSET)
  so the rendered footprint doesn't jump between the auto and manual paths
- auto-creation suppression now also matches manual nodes by mutual
  footprint coverage, so re-closing the room doesn't stack a duplicate
  auto surface on the demoted one

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(editor): hide alt-to-detach hint when the moving endpoint has no linked walls

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(editor): split host walls in every snap mode, stop wall chain on existing walls

Splitting a room with a wall now splits the walls it lands on and migrates
hosted doors/windows/items — matching what already happened to the slab and
ceiling. The split machinery existed but was gated on magnetic snapping
('lines' mode) while the wall tool defaults to 'grid'; split resolution now
always runs, with the join radius scoped to the active mode (0.35 magnetic,
0.05 connect snap otherwise), and the whole commit lands as one undo step.

The drawing chain now terminates when a committed segment ends on a wall
outside the current chain (T-junction), like the room auto-close — users
don't draw overlapping walls. Applied in 3D and 2D, and the 2D-only path
gains the previously missing wallClosesRoom parity check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(core): per-edge build-time slab offsets — slabs tile at wall centerlines

Replaces the stored-polygon render offsets (auto: centroid inset 0.02,
manual: flat outset 0.05) with one uniform per-edge rule computed at
geometry build time, for auto and manual slabs alike:

- edge shared with a sibling slab (collinear-overlap test, T-junction
  sub-segments included) → small relief inset; adjacent rooms tile
  exactly and can never overlap
- edge on a wall centerline with no slab neighbor → expand outward by
  that wall's thickness/2, flush with the facade
- free edge → rendered exactly as drawn

Slab demotion no longer bakes polygons (offsets never live in node
data); no stored-data migration. New slab system marks level slabs
dirty when wall geometry/thickness or sibling slab footprints change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(viewer): import KTX2Loader from its deep path, not the jsm aggregate

The Addons.js aggregate re-exports LottieLoader/TTFLoader whose CDN URL
imports (lottie-web, opentype.js) abort bun test in every package that
transitively imports the viewer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(editor): split host wall when an endpoint move lands on its interior

Moving a wall endpoint onto another wall's mid-span now splits that wall
and migrates its doors/windows/items, matching the draw tool — previously
the room closed (detection planarizes internally) but the wall node stayed
whole. New resolveEndpointWallSplit reuses the draw path's split pipeline;
endpoint write + split commit as one undo step. Applied in 3D and 2D.
Corner drops still join without splitting; straddling openings skip the
split; alt-detached commits split the stationary former sibling correctly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(core): slab-wall adoption — absolute edge projection, band snapping, rendered-footprint wall elevation

Slab edges near a wall now render projected onto the wall's exact
geometry instead of getting relative offsets, healing legacy scenes
(face-aligned edges, old baked outsets, hand-drag slop) with no data
migration:

- adoption band: an edge collinear with a wall centerline within
  thickness/2 + 6cm (both sides) is wall-backed; nearest centerline
  wins, a sibling slab in the same band forces the interior seam
- wall-backed exterior edges project to the outer face; interior edges
  to the centerline minus the relief gap; free edges render as drawn
- slab edge resize: wall snapping is now edge-based, not cursor-based —
  fixes the drop landing short by the grab offset (0.34m arrow gap in
  3D, hit-stroke slop in 2D); the snap translates the edge onto the
  wall centerline (canonical stored position), beacon/preview/commit
  agree, full band in lines mode, 5cm stick otherwise; 2D shows a
  dashed stored-boundary skeleton when it diverges from the fill
- wall elevation tests the slab's rendered footprint instead of the
  stored polygon with a 0.1mm epsilon, so walls sit on legacy slabs
  and re-elevate when slabs are reshaped

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core): subcut slab edges per backing span

A slab edge backed differently along its length (offset rooms sharing a
wall over part of the span, a wall shorter than the edge, collinear
walls of different thickness) is now subdivided at the backing-span
breakpoints; each sub-span classifies and projects independently, with
a perpendicular step connector at intra-edge transitions that lands
inside the crossing wall's footprint. Breakpoints closer than 5cm merge
so no sliver geometry reaches the ring, and same-target spans re-fuse
(curved-wall sampling doesn't balloon vertex counts; whole-edge cases
render bit-identically to before).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core): wall elevation picks the slab that supports it, not the highest graze

A wall touching a raised slab only at an endpoint no longer lifts
entirely to that slab. Elevation selection is now coverage-based:
per-slab support is the wall's centerline+face length covered by the
slab's rendered footprint minus holes; slabs within 0.1mm of elevation
pool their support (party walls spanning two rooms still lift); the
wall sits on the highest elevation covering >=50% of its length, else
the best-covered group (ties prefer higher), else 0. Sub-5cm grazes
are ignored outright.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core): interior slab edges meet exactly at the wall centerline

Removes the 2cm interior relief inset: both rooms' seam edges project
onto the same centerline (or the symmetric sibling midline when no wall
backs the seam), so adjacent slabs tile with a shared edge — the 4cm
slit under shared walls, visible at its open end on the facade, is
gone. Safe against z-fighting because slab side quads are single-sided
(FrontSide is enforced repo-wide for the MRT scene pass) and the
coincident seam faces have opposite normals; junction step pockets grow
to 5cm but remain strictly inside the intersecting wall footprints.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core): elevation-aware interior slab seams — close the vertical band slit

With unequal room elevations, both slabs stopping at the shared wall's
centerline left the lower room's half of the wall band open between the
lower slab top and the wall base (the wall seats on the higher slab).
Interior seams across a wall are now elevation-aware: equal elevations
keep the exact centerline seam; unequal elevations project BOTH slabs
to the wall face on the lower side — the higher slab runs through the
band under the wall, the lower butts the same plane. Wall-less unequal
seams keep the sibling midline (a visible step face is correct there).
Slab elevation joins the level dependency signature so height edits
rebuild sibling slabs live.

Verified against real local scenes: all wall bands solid where the
seam rule applies (remaining pockets are the known flat-wall-base
limitation, tracked separately).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(plugin-trees): run wind displacement before the instance transform

The r184->r185 upgrade broke the plant wind: r185 fixed TSL's statement
emission order so a material's positionNode now runs *after* the
instancing transform (r184 emitted it before — verified in the compiled
WGSL of both versions). The wind nodes were tuned against the r184
order, reading positionLocal as geometry-local coordinates, so on r185
the displacement moved into level space: sway no longer scaled with the
per-instance scale (scaled-down trees thrashed like a storm, leaf cards
visibly detaching from branches), leaf phase followed world placement,
and STEM_BEND's height term read the floor elevation, so grass/flowers
on upper levels slid around rigidly.

Restore the r184 semantics explicitly: WindNodeMaterial assigns the wind
node to positionLocal inside setupPosition() before super applies the
instance transform, instead of using positionNode. The emitted WGSL is
statement-for-statement identical to r184's.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core): resolve slab joins across floor elevations

* fix(editor): keep slab resize arrows visible

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 13:28:29 -04:00
..