{"version":3,"file":"floorPlan-DLe5XAx6.cjs","names":[],"sources":["../package/common/floorPlan/floorPlan.types.ts","../package/common/floorPlan/floorPlan.geometry.ts","../package/common/floorPlan/floorPlan.graph.ts","../package/common/floorPlan/floorPlan.plan.ts","../package/common/floorPlan/floorPlan.faces.ts","../package/common/floorPlan/floorPlan.rooms.ts","../package/common/floorPlan/floorPlan.path.ts","../package/common/floorPlan/floorPlan.hit.ts","../package/common/floorPlan/floorPlan.snap.ts","../package/common/floorPlan/floorPlan.json.ts","../package/common/floorPlan/floorPlan.triangulate.ts","../package/common/floorPlan/floorPlan.mesh.ts"],"sourcesContent":["/**\n * Floor plan model: a planar wall graph whose rooms are derived, not stored.\n *\n * Nodes and walls are first-class and shared, so the wall between two rooms is one\n * wall. An opening therefore cuts it exactly once, which is what the previous\n * room-polygon model could not express.\n *\n * GEOMETRY INVARIANT: every coordinate, length, thickness, offset and height in this\n * module is in MILLIMETRES. There is deliberately no per-plan scale factor: one would\n * have to be threaded through every geometry function, and a single missed call site\n * is a silent 1000x error that no type can catch. `unitSystem` below drives display\n * formatting only and is never read by geometry.\n */\n\n/** A point in plan space, in millimetres. y increases downwards, as in SVG. */\nexport interface PlanPoint {\n  x: number;\n  y: number;\n}\n\nexport enum PlanUnitSystem {\n  /** Display as m / cm / mm. */\n  Metric = 'metric',\n  /** Display as feet and inches. Storage stays millimetres. */\n  Imperial = 'imperial',\n}\n\nexport enum WallType {\n  /** Load-bearing envelope wall. */\n  Exterior = 'exterior',\n  /** Load-bearing interior wall. */\n  Interior = 'interior',\n  /** Non-load-bearing partition. */\n  Partition = 'partition',\n  /**\n   * A room boundary with no physical wall, for dividing open-plan space. It still\n   * closes a face, so it produces two rooms, but renders as a thin dashed line and\n   * extrudes to nothing in 3D.\n   */\n  Virtual = 'virtual',\n}\n\nexport enum OpeningKind {\n  /** A real door leaf, with a swing and a threshold. */\n  Door = 'door',\n  /** Glazed opening with a sill above floor level. */\n  Window = 'window',\n  /** Structural opening with a head but no leaf. */\n  Archway = 'archway',\n  /**\n   * A plain hole in the wall with no jamb detailing. Distinct from \"no wall at all\":\n   * a Gap is a marked span of a wall that exists, which is what lets a gap-style\n   * rendering stay unambiguous.\n   */\n  Gap = 'gap',\n}\n\nexport enum DoorSwing {\n  None = 'none',\n  /** Hinged at the opening's start, opening towards the wall's left normal. */\n  StartLeft = 'startLeft',\n  StartRight = 'startRight',\n  EndLeft = 'endLeft',\n  EndRight = 'endRight',\n  /** Two leaves, hinged at both ends. */\n  Double = 'double',\n  Sliding = 'sliding',\n}\n\n/** A graph vertex. Structurally assignable to `PlanPoint`. */\nexport interface PlanNode {\n  id: string;\n  x: number;\n  y: number;\n}\n\n/**\n * An undirected edge between two nodes. `a` and `b` define the wall's own direction,\n * which is what opening offsets and door swings are measured against.\n *\n * The wall is centred on the segment a..b, extending `thickness / 2` to each side.\n * There is no eccentric offset in this version.\n */\nexport interface PlanWall {\n  id: string;\n  a: string;\n  b: string;\n  /** Millimetres. Must be greater than 0. */\n  thickness: number;\n  /** Millimetres above this wall's base. Omit to inherit `PlanFloor.height`. */\n  height?: number;\n  /** Millimetres above the floor's finished level. Defaults to 0. */\n  baseHeight?: number;\n  type: WallType;\n}\n\n/**\n * An opening cut into exactly one wall. Because a shared wall is one wall, an opening\n * between two rooms exists exactly once and needs no de-duplication anywhere.\n */\nexport interface PlanOpening {\n  id: string;\n  wallId: string;\n  kind: OpeningKind;\n  /**\n   * Distance in millimetres from the wall's node `a` to the opening's CENTRE, measured\n   * along the centreline.\n   *\n   * Deliberately absolute rather than a 0..1 parameter: with a parameter, stretching a\n   * wall by dragging one of its nodes would rescale every door on it. Absolute offsets\n   * are clamped instead when a wall shortens.\n   */\n  offset: number;\n  /** Millimetres, along the wall. */\n  width: number;\n  /**\n   * Height of the opening's BOTTOM above the floor, in millimetres: 0 for a door, around\n   * 900 for a window. Nothing in the 2D plan reads it, since a plan is a horizontal cut\n   * and every opening looks the same from above. It is here so the same plan can drive a\n   * 3D view later without the heights having to be invented or asked for again.\n   */\n  sill: number;\n  /** Height of the opening's TOP above the floor, in millimetres. Typically 2100. */\n  head: number;\n  /** Only meaningful for `OpeningKind.Door`. */\n  swing?: DoorSwing;\n}\n\n/**\n * Room METADATA. This is not a geometric object: the polygon comes from face\n * extraction. The trailing `wallIds` / `area` fields are a match cache maintained by\n * `derivePlan`, and are the reason a room's name survives edits.\n */\nexport interface PlanRoom {\n  id: string;\n  name: string;\n  /** Any CSS colour. Omit to use the theme default. */\n  colour?: string;\n  /** Free-form usage tag (\"kitchen\", \"bath\"), for later fixture and 3D defaults. */\n  usage?: string;\n  /**\n   * A point that lay inside the room's polygon when it was last written. Used as the\n   * label position and as the secondary identity signal during matching.\n   */\n  anchor: PlanPoint;\n  /** Set once the user drags the label; `derivePlan` then stops moving it. */\n  anchorPinned: boolean;\n  /** Monotonic creation counter, used as a deterministic tie-break when matching. */\n  seq: number;\n  /** Match cache: the wall ids on this room's boundary at the last successful match. */\n  wallIds: string[];\n  /** Match cache: area in mm² at the last successful match. */\n  area: number;\n}\n\nexport interface PlanFloor {\n  id: string;\n  label: string;\n  /** Finished floor level of this storey, millimetres above datum. */\n  elevation: number;\n  /** Default storey height in millimetres, used when a wall omits its own `height`. */\n  height: number;\n  /**\n   * Keyed by id for O(1) lookup. Ids must not be numeric strings ('n_12', never '12'),\n   * so that JavaScript preserves insertion order and iteration stays reproducible.\n   */\n  nodes: Record<string, PlanNode>;\n  walls: Record<string, PlanWall>;\n  openings: Record<string, PlanOpening>;\n  rooms: Record<string, PlanRoom>;\n}\n\nexport interface FloorPlan {\n  id: string;\n  name?: string;\n  /** Schema version, for future migrations. */\n  version: 1;\n  /** Display formatting only. Never read by geometry. */\n  unitSystem: PlanUnitSystem;\n  floors: PlanFloor[];\n  /**\n   * Bumped by every mutating operation. Use it as the memo key for `derivePlan`, so a\n   * gesture that does not actually change the graph does not trigger a re-derive.\n   */\n  revision: number;\n}\n\n/**\n * A raw face from the planar walk, before room metadata is attached.\n *\n * WINDING: `outer` is positively wound under the shoelace formula, which in SVG's\n * y-down space means it reads as visually CLOCKWISE. Anything downstream that assumes\n * counter-clockwise (extruded slab normals, most triangulators) must flip it.\n */\nexport interface PlanFace {\n  /**\n   * Derived from the boundary itself, so it is stable across derives for as long as\n   * the geometry is unchanged. Safe to use as a React key.\n   */\n  id: string;\n  /** Outer ring, closed implicitly: the first vertex is not repeated at the end. */\n  outer: PlanPoint[];\n  /** Inner rings from nested components, wound opposite to `outer`. */\n  holes: PlanPoint[][];\n  /** mm², always positive, with holes already subtracted. */\n  area: number;\n  /** mm, outer ring only. */\n  perimeter: number;\n  /**\n   * Wall ids on the boundary, in traversal order. A dangling wall inside this face\n   * appears TWICE, once per direction, so consumers needing unique ids must dedupe.\n   */\n  wallIds: string[];\n  nodeIds: string[];\n}\n\n/** A face with its matched metadata. This is what the components render. */\nexport interface DerivedRoom extends PlanFace {\n  /** `null` when no `PlanRoom` matched this face during this derive. */\n  roomId: string | null;\n  meta: PlanRoom | null;\n  /** Guaranteed inside `outer` and outside every hole. The label position. */\n  anchor: PlanPoint;\n}\n\nexport enum PlanIssueKind {\n  ZeroLengthWall = 'zeroLengthWall',\n  SelfLoopWall = 'selfLoopWall',\n  DuplicateWall = 'duplicateWall',\n  CoincidentNodes = 'coincidentNodes',\n  /** Two walls cross with no node at the intersection. Corrupts face extraction. */\n  UnsplitCrossing = 'unsplitCrossing',\n  /** A node sits on a wall's interior but that wall was never split there. */\n  UnsplitNodeOnWall = 'unsplitNodeOnWall',\n  /** Two walls leave a node in the same direction, tying the angular sort. */\n  OverlappingWalls = 'overlappingWalls',\n  OrphanNode = 'orphanNode',\n  DanglingOpening = 'danglingOpening',\n  /** A `PlanRoom` matched no face this derive. It is kept, never deleted. */\n  UnmatchedRoom = 'unmatchedRoom',\n  /** An opening was moved or dropped because its wall no longer has room for it. */\n  OpeningDropped = 'openingDropped',\n}\n\nexport interface PlanIssue {\n  kind: PlanIssueKind;\n  message: string;\n  nodeIds?: string[];\n  wallIds?: string[];\n  roomIds?: string[];\n  openingIds?: string[];\n}\n\nexport interface PlanDerivation {\n  /** The floor with room match caches and anchors refreshed. Pure: a new object. */\n  floor: PlanFloor;\n  rooms: DerivedRoom[];\n  issues: PlanIssue[];\n}\n\n/** The uniform result of every graph mutation. All of them are pure. */\nexport interface PlanEdit<T = void> {\n  floor: PlanFloor;\n  result: T;\n  issues: PlanIssue[];\n}\n\n/**\n * The viewport: which world point sits at the canvas's top-left corner, and how many\n * screen pixels one world millimetre occupies.\n *\n * A 6m plan filling a 600px canvas is `zoom: 0.1`. Note this is a transform on a group,\n * not an SVG `viewBox`: handles, labels and hit tolerances are then sized in screen\n * pixels and stay constant as you zoom, instead of scaling with the drawing.\n */\nexport interface PlanView {\n  x: number;\n  y: number;\n  zoom: number;\n}\n\n/** What is currently selected or hovered. */\nexport type PlanSelection =\n  | { type: 'room'; id: string }\n  | { type: 'wall'; id: string }\n  | { type: 'opening'; id: string }\n  | { type: 'node'; id: string };\n","/**\n * Pure vector and polygon primitives. Nothing here knows about the plan model.\n *\n * All lengths are millimetres (see floorPlan.types.ts).\n */\n\nimport type { PlanPoint } from './floorPlan.types';\n\n// ─── Model-integrity epsilons ──────────────────────────────────────────────────\n// These are FIXED millimetre values describing when the model itself is degenerate.\n// They are a separate family from the user-intent tolerances in floorPlan.snap.ts,\n// which are screen pixels converted through the current zoom. Conflating the two\n// gives you either a model that accumulates 0.2mm walls, or snapping that stops\n// working when zoomed in.\n\n/** Shortest wall the graph will accept. Keeps zero-length walls out entirely. */\nexport const MIN_WALL_LENGTH = 10;\n/** Two points closer than this are the same point. */\nexport const EPS_COINCIDENT = 1;\n/** A ring with |area| below this encloses nothing (0.0001 m²). */\nexport const EPS_AREA = 100;\n/** Angular tolerance for \"these two walls are collinear\", in radians. */\nexport const EPS_ANGLE = 1e-4;\n/** Narrowest scanline span that counts as room interior rather than a slit. */\nexport const EPS_SPAN = 2;\n/** Narrowest opening worth keeping when a wall shortens under it. */\nexport const MIN_OPENING_WIDTH = 100;\n\n// ─── Vectors ───────────────────────────────────────────────────────────────────\n\nexport function dist(a: PlanPoint, b: PlanPoint): number {\n  return Math.hypot(a.x - b.x, a.y - b.y);\n}\n\nexport function distSq(a: PlanPoint, b: PlanPoint): number {\n  const dx = a.x - b.x;\n  const dy = a.y - b.y;\n  return dx * dx + dy * dy;\n}\n\n/** Cross product of ab and ac. Positive when c is clockwise of b in SVG's y-down space. */\nexport function cross(a: PlanPoint, b: PlanPoint, c: PlanPoint): number {\n  return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);\n}\n\nexport function lerp(a: PlanPoint, b: PlanPoint, t: number): PlanPoint {\n  return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };\n}\n\n/** Unit vector from a to b, or null when they are coincident. */\nexport function direction(a: PlanPoint, b: PlanPoint): PlanPoint | null {\n  const dx = b.x - a.x;\n  const dy = b.y - a.y;\n  const len = Math.hypot(dx, dy);\n  if (len < 1e-9) return null;\n  return { x: dx / len, y: dy / len };\n}\n\n/** Left-hand normal of the direction from a to b, or null when coincident. */\nexport function normal(a: PlanPoint, b: PlanPoint): PlanPoint | null {\n  const d = direction(a, b);\n  return d ? { x: -d.y, y: d.x } : null;\n}\n\n// ─── Point / segment ───────────────────────────────────────────────────────────\n\n/**\n * Distance from p to segment ab, plus the clamped parameter t of the closest point.\n * `t` is clamped to [0, 1], so the closest point is always on the segment.\n */\nexport function pointToSegment(\n  p: PlanPoint,\n  a: PlanPoint,\n  b: PlanPoint,\n): { dist: number; t: number; point: PlanPoint } {\n  const dx = b.x - a.x;\n  const dy = b.y - a.y;\n  const len2 = dx * dx + dy * dy;\n  if (len2 === 0) return { dist: dist(p, a), t: 0, point: { x: a.x, y: a.y } };\n  const raw = ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;\n  const t = raw < 0 ? 0 : raw > 1 ? 1 : raw;\n  const point = { x: a.x + t * dx, y: a.y + t * dy };\n  return { dist: dist(p, point), t, point };\n}\n\n/**\n * Exact projection of p onto segment ab, clamped to the segment.\n *\n * Used after a fuzzy hit tolerance has decided that p belongs to this segment: intent\n * is allowed to be approximate, the stored model never is.\n */\nexport function projectOnSegment(p: PlanPoint, a: PlanPoint, b: PlanPoint): PlanPoint {\n  return pointToSegment(p, a, b).point;\n}\n\nexport type SegmentIntersectionKind =\n  /** The segments cross strictly inside both. */\n  | 'proper'\n  /** They are parallel and do not touch. */\n  | 'none'\n  /** They lie on the same line and overlap over a non-zero length. */\n  | 'collinearOverlap'\n  /** They lie on the same line but do not overlap. */\n  | 'collinearDisjoint'\n  /** An endpoint of one lies on the other, or they share an endpoint. */\n  | 'touch';\n\nexport interface SegmentIntersection {\n  kind: SegmentIntersectionKind;\n  /** Parameter along ab, for 'proper' and 'touch'. */\n  t?: number;\n  /** Parameter along cd, for 'proper' and 'touch'. */\n  s?: number;\n  point?: PlanPoint;\n}\n\n/**\n * Classifies how segments ab and cd meet.\n *\n * `eps` is a millimetre distance tolerance, not a parameter tolerance, so the result\n * does not change with segment length.\n */\nexport function segmentIntersection(\n  a: PlanPoint,\n  b: PlanPoint,\n  c: PlanPoint,\n  d: PlanPoint,\n  eps = EPS_COINCIDENT,\n): SegmentIntersection {\n  const abLen = dist(a, b);\n  const cdLen = dist(c, d);\n  if (abLen < 1e-9 || cdLen < 1e-9) return { kind: 'none' };\n\n  const rx = b.x - a.x;\n  const ry = b.y - a.y;\n  const sx = d.x - c.x;\n  const sy = d.y - c.y;\n  const denom = rx * sy - ry * sx;\n\n  // Perpendicular distance of c and d from line ab, used to tell parallel from collinear.\n  const distC = Math.abs(cross(a, b, c)) / abLen;\n  const distD = Math.abs(cross(a, b, d)) / abLen;\n\n  if (Math.abs(denom) < 1e-12 || (distC < eps && distD < eps)) {\n    if (distC >= eps || distD >= eps) return { kind: 'none' };\n    // Collinear: compare parameters along ab.\n    const len2 = rx * rx + ry * ry;\n    const tc = ((c.x - a.x) * rx + (c.y - a.y) * ry) / len2;\n    const td = ((d.x - a.x) * rx + (d.y - a.y) * ry) / len2;\n    const lo = Math.min(tc, td);\n    const hi = Math.max(tc, td);\n    const epsT = eps / abLen;\n    if (hi < epsT || lo > 1 - epsT) return { kind: 'collinearDisjoint' };\n    if (Math.min(hi, 1) - Math.max(lo, 0) < epsT) return { kind: 'collinearDisjoint' };\n    return { kind: 'collinearOverlap' };\n  }\n\n  const t = ((c.x - a.x) * sy - (c.y - a.y) * sx) / denom;\n  const s = ((c.x - a.x) * ry - (c.y - a.y) * rx) / denom;\n  const epsT = eps / abLen;\n  const epsS = eps / cdLen;\n\n  if (t < -epsT || t > 1 + epsT || s < -epsS || s > 1 + epsS) return { kind: 'none' };\n\n  const point = { x: a.x + t * rx, y: a.y + t * ry };\n  const atAbEnd = t <= epsT || t >= 1 - epsT;\n  const atCdEnd = s <= epsS || s >= 1 - epsS;\n  if (atAbEnd || atCdEnd) return { kind: 'touch', t, s, point };\n  return { kind: 'proper', t, s, point };\n}\n\n// ─── Rings ─────────────────────────────────────────────────────────────────────\n\n/**\n * Shoelace area of a closed ring (first vertex not repeated).\n *\n * Positive means visually clockwise in SVG's y-down space. See `PlanFace.outer`.\n */\nexport function signedArea(ring: readonly PlanPoint[]): number {\n  const n = ring.length;\n  if (n < 3) return 0;\n  let sum = 0;\n  for (let i = 0, j = n - 1; i < n; j = i++) {\n    sum += ring[j].x * ring[i].y - ring[i].x * ring[j].y;\n  }\n  return sum / 2;\n}\n\nexport function perimeter(ring: readonly PlanPoint[]): number {\n  const n = ring.length;\n  if (n < 2) return 0;\n  let sum = 0;\n  for (let i = 0, j = n - 1; i < n; j = i++) sum += dist(ring[j], ring[i]);\n  return sum;\n}\n\n/**\n * Area-weighted centroid of a ring.\n *\n * Deliberately not the vertex mean: a dangling wall adds an out-and-back vertex pair\n * that skews the mean but contributes nothing to the area moment. Falls back to the\n * vertex mean only for a degenerate (zero-area) ring.\n */\nexport function areaCentroid(ring: readonly PlanPoint[]): PlanPoint {\n  const n = ring.length;\n  if (n === 0) return { x: 0, y: 0 };\n  if (n < 3) {\n    return {\n      x: ring.reduce((s, p) => s + p.x, 0) / n,\n      y: ring.reduce((s, p) => s + p.y, 0) / n,\n    };\n  }\n  let a = 0;\n  let cx = 0;\n  let cy = 0;\n  for (let i = 0, j = n - 1; i < n; j = i++) {\n    const f = ring[j].x * ring[i].y - ring[i].x * ring[j].y;\n    a += f;\n    cx += (ring[j].x + ring[i].x) * f;\n    cy += (ring[j].y + ring[i].y) * f;\n  }\n  if (Math.abs(a) < 1e-9) {\n    return {\n      x: ring.reduce((s, p) => s + p.x, 0) / n,\n      y: ring.reduce((s, p) => s + p.y, 0) / n,\n    };\n  }\n  return { x: cx / (3 * a), y: cy / (3 * a) };\n}\n\n/** Ray-casting point-in-ring test. Points exactly on an edge are unspecified. */\nexport function pointInRing(p: PlanPoint, ring: readonly PlanPoint[]): boolean {\n  let inside = false;\n  for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n    const xi = ring[i].x;\n    const yi = ring[i].y;\n    const xj = ring[j].x;\n    const yj = ring[j].y;\n    if (yi > p.y !== yj > p.y && p.x < ((xj - xi) * (p.y - yi)) / (yj - yi) + xi) {\n      inside = !inside;\n    }\n  }\n  return inside;\n}\n\n/** Inside `outer` and outside every hole. */\nexport function pointInRings(\n  p: PlanPoint,\n  outer: readonly PlanPoint[],\n  holes: readonly (readonly PlanPoint[])[] = [],\n): boolean {\n  if (!pointInRing(p, outer)) return false;\n  for (const hole of holes) if (pointInRing(p, hole)) return false;\n  return true;\n}\n\n/**\n * A point guaranteed to lie strictly inside the ring set, placed somewhere visually\n * central where possible.\n *\n * The area centroid is preferred, but it is not guaranteed to be inside a concave\n * polygon (an L-shaped room's centroid can land in the notch), so a scanline fallback\n * picks the widest interior span across several horizontal lines. The EPS_SPAN test on\n * that span is what stops an anchor landing inside the zero-width slit that a dangling\n * wall leaves in a face.\n */\nexport function representativePoint(\n  outer: readonly PlanPoint[],\n  holes: readonly (readonly PlanPoint[])[] = [],\n): PlanPoint {\n  if (outer.length === 0) return { x: 0, y: 0 };\n\n  const centre = areaCentroid(outer);\n  if (pointInRings(centre, outer, holes)) return centre;\n\n  let ymin = Infinity;\n  let ymax = -Infinity;\n  for (const p of outer) {\n    if (p.y < ymin) ymin = p.y;\n    if (p.y > ymax) ymax = p.y;\n  }\n  if (!Number.isFinite(ymin) || ymax - ymin < 1e-9) return centre;\n\n  const rings: readonly (readonly PlanPoint[])[] = [outer, ...holes];\n  let best: { point: PlanPoint; width: number } | null = null;\n\n  const SAMPLES = 7;\n  for (let k = 1; k <= SAMPLES; k++) {\n    const y = ymin + ((ymax - ymin) * k) / (SAMPLES + 1);\n    const xs: number[] = [];\n    // One crossing list across ALL rings, so even-odd pairing yields interior spans\n    // directly. Running the scanline per ring would not compose with holes.\n    for (const ring of rings) {\n      for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n        const p = ring[j];\n        const q = ring[i];\n        if (p.y > y !== q.y > y) {\n          xs.push(p.x + ((y - p.y) * (q.x - p.x)) / (q.y - p.y));\n        }\n      }\n    }\n    xs.sort((m, n) => m - n);\n    for (let i = 0; i + 1 < xs.length; i += 2) {\n      const width = xs[i + 1] - xs[i];\n      if (width > EPS_SPAN && (!best || width > best.width)) {\n        best = { point: { x: (xs[i] + xs[i + 1]) / 2, y }, width };\n      }\n    }\n  }\n\n  return best ? best.point : centre;\n}\n\n/**\n * Removes out-and-back spurs (`... P, Q, P ...`) from a ring, repeatedly until none\n * remain.\n *\n * A face containing a dangling wall is a valid fill but is not a simple polygon, which\n * breaks polygon offsetting and makes some triangulators emit zero-area triangles. Run\n * this before triangulating a floor slab; keep the raw ring for the 2D fill, where the\n * slit is harmless and `wallIds` alignment matters.\n */\nexport function stripSpurs(ring: readonly PlanPoint[]): PlanPoint[] {\n  let out = ring.slice();\n  let changed = true;\n  while (changed && out.length > 3) {\n    changed = false;\n    const next: PlanPoint[] = [];\n    const n = out.length;\n    for (let i = 0; i < n; i++) {\n      const prev = out[(i - 1 + n) % n];\n      const nextPt = out[(i + 1) % n];\n      // The middle of an out-and-back is the vertex whose neighbours coincide.\n      if (dist(prev, nextPt) < EPS_COINCIDENT) {\n        changed = true;\n        continue;\n      }\n      next.push(out[i]);\n    }\n    if (changed) {\n      // Drop the now-duplicated turning point left behind by each removed spur tip.\n      out = next.filter((p, i) => dist(p, next[(i + 1) % next.length]) >= EPS_COINCIDENT);\n    }\n  }\n  return out;\n}\n\nexport function rectToPoints(x: number, y: number, w: number, h: number): PlanPoint[] {\n  return [\n    { x, y },\n    { x: x + w, y },\n    { x: x + w, y: y + h },\n    { x, y: y + h },\n  ];\n}\n\nexport function boundsOf(points: readonly PlanPoint[]): {\n  minX: number;\n  minY: number;\n  maxX: number;\n  maxY: number;\n} | null {\n  if (points.length === 0) return null;\n  let minX = Infinity;\n  let minY = Infinity;\n  let maxX = -Infinity;\n  let maxY = -Infinity;\n  for (const p of points) {\n    if (p.x < minX) minX = p.x;\n    if (p.y < minY) minY = p.y;\n    if (p.x > maxX) maxX = p.x;\n    if (p.y > maxY) maxY = p.y;\n  }\n  return { minX, minY, maxX, maxY };\n}\n","/**\n * Graph maintenance: the operations that keep the wall graph a valid planar embedding\n * while the user draws.\n *\n * The invariant every function here protects: no two walls may cross without a node at\n * the intersection, and no node may lie on a wall's interior without that wall being\n * split there. Break either and the face walk silently merges two rooms into one\n * figure-eight face, with no error anywhere.\n *\n * Every operation is pure and returns a new floor, for direct use as React state.\n */\n\nimport {\n  EPS_ANGLE,\n  EPS_COINCIDENT,\n  MIN_OPENING_WIDTH,\n  MIN_WALL_LENGTH,\n  cross,\n  dist,\n  pointToSegment,\n  projectOnSegment,\n  segmentIntersection,\n} from './floorPlan.geometry';\nimport { PlanIssueKind, WallType } from './floorPlan.types';\nimport type { PlanEdit, PlanFloor, PlanIssue, PlanNode, PlanOpening, PlanPoint, PlanWall } from './floorPlan.types';\n\n// ─── Ids ───────────────────────────────────────────────────────────────────────\n\nlet idSeq = 0;\n\n/**\n * Ids are deliberately non-numeric strings, so `Record` iteration keeps insertion\n * order (JavaScript reorders integer-like keys), which is what makes derivation\n * reproducible.\n */\nexport function newId(prefix: string): string {\n  idSeq += 1;\n  const rand =\n    typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n      ? crypto.randomUUID().slice(0, 8)\n      : Math.random().toString(36).slice(2, 10);\n  return `${prefix}_${rand}${idSeq.toString(36)}`;\n}\n\n/**\n * The first of `nameFor(start)`, `nameFor(start + 1)`, … that `used` does not hold.\n *\n * Bounded because `nameFor` is the caller's: one that ignores its number would never reach a\n * free name. An injective one always finds one within `used.size + 1` tries.\n */\nexport function freshName(nameFor: (n: number) => string, start: number, used: ReadonlySet<string>): string {\n  let name = nameFor(start);\n  for (let n = start + 1; used.has(name) && n <= start + used.size; n++) name = nameFor(n);\n  return name;\n}\n\n/** Test-only: makes generated ids reproducible within a single test. */\nexport function __resetIdSeq(): void {\n  idSeq = 0;\n}\n\n// ─── Internals ─────────────────────────────────────────────────────────────────\n\nfunction cloneFloor(floor: PlanFloor): PlanFloor {\n  return {\n    ...floor,\n    nodes: { ...floor.nodes },\n    walls: { ...floor.walls },\n    openings: { ...floor.openings },\n    rooms: { ...floor.rooms },\n  };\n}\n\nfunction issue(kind: PlanIssueKind, message: string, extra: Partial<PlanIssue> = {}): PlanIssue {\n  return { kind, message, ...extra };\n}\n\nfunction wallLength(floor: PlanFloor, wall: PlanWall): number {\n  const a = floor.nodes[wall.a];\n  const b = floor.nodes[wall.b];\n  if (!a || !b) return 0;\n  return dist(a, b);\n}\n\nfunction wallsAtNode(floor: PlanFloor, nodeId: string): PlanWall[] {\n  return Object.values(floor.walls).filter((w) => w.a === nodeId || w.b === nodeId);\n}\n\nfunction findWallBetween(floor: PlanFloor, a: string, b: string): PlanWall | null {\n  for (const w of Object.values(floor.walls)) {\n    if ((w.a === a && w.b === b) || (w.a === b && w.b === a)) return w;\n  }\n  return null;\n}\n\n// ─── Node spatial hash ─────────────────────────────────────────────────────────\n\n/**\n * Bucket size for the node hash, in millimetres. Any lookup checks the 3x3\n * neighbourhood, so this only has to exceed the largest snap tolerance in world units.\n */\nconst HASH_CELL = 250;\n\nfunction hashKey(x: number, y: number): string {\n  return `${Math.round(x / HASH_CELL)}|${Math.round(y / HASH_CELL)}`;\n}\n\nfunction buildNodeHash(floor: PlanFloor): Map<string, PlanNode[]> {\n  const map = new Map<string, PlanNode[]>();\n  for (const node of Object.values(floor.nodes)) {\n    const key = hashKey(node.x, node.y);\n    const list = map.get(key);\n    if (list) list.push(node);\n    else map.set(key, [node]);\n  }\n  return map;\n}\n\n/** Nearest existing node within `tol`, using the spatial hash. */\nexport function findNodeNear(floor: PlanFloor, p: PlanPoint, tol: number): PlanNode | null {\n  const hash = buildNodeHash(floor);\n  const cx = Math.round(p.x / HASH_CELL);\n  const cy = Math.round(p.y / HASH_CELL);\n  let best: PlanNode | null = null;\n  let bestDist = tol;\n  const span = Math.max(1, Math.ceil(tol / HASH_CELL));\n  for (let dx = -span; dx <= span; dx++) {\n    for (let dy = -span; dy <= span; dy++) {\n      for (const node of hash.get(`${cx + dx}|${cy + dy}`) ?? []) {\n        const d = dist(p, node);\n        if (d <= bestDist) {\n          best = node;\n          bestDist = d;\n        }\n      }\n    }\n  }\n  return best;\n}\n\n/** Nearest wall whose centreline passes within `tol` of `p`. */\nexport function findWallNear(\n  floor: PlanFloor,\n  p: PlanPoint,\n  tol: number,\n  exclude?: ReadonlySet<string>,\n): { wall: PlanWall; t: number; point: PlanPoint; dist: number } | null {\n  let best: { wall: PlanWall; t: number; point: PlanPoint; dist: number } | null = null;\n  for (const wall of Object.values(floor.walls)) {\n    if (exclude?.has(wall.id)) continue;\n    const a = floor.nodes[wall.a];\n    const b = floor.nodes[wall.b];\n    if (!a || !b) continue;\n    const r = pointToSegment(p, a, b);\n    if (r.dist <= tol && (!best || r.dist < best.dist)) {\n      best = { wall, t: r.t, point: r.point, dist: r.dist };\n    }\n  }\n  return best;\n}\n\n// ─── splitWall ─────────────────────────────────────────────────────────────────\n\nexport interface SplitWallResult {\n  nodeId: string;\n  wallIds: [string, string] | null;\n  droppedOpeningIds: string[];\n}\n\n/**\n * Splits a wall at a point or parameter, returning the node at the cut.\n *\n * Refuses to cut within `MIN_WALL_LENGTH` of either end, returning that endpoint's\n * existing node instead. That single rule is what keeps zero-length walls out of the\n * graph entirely, which in turn is what stops the angular sort from being fed a\n * direction-less dart.\n */\nexport function splitWall(floor: PlanFloor, wallId: string, at: PlanPoint | number): PlanEdit<SplitWallResult> {\n  const wall = floor.walls[wallId];\n  if (!wall) {\n    return { floor, result: { nodeId: '', wallIds: null, droppedOpeningIds: [] }, issues: [] };\n  }\n  const a = floor.nodes[wall.a];\n  const b = floor.nodes[wall.b];\n  if (!a || !b) {\n    return { floor, result: { nodeId: '', wallIds: null, droppedOpeningIds: [] }, issues: [] };\n  }\n\n  const len = dist(a, b);\n  const point = typeof at === 'number' ? { x: a.x + (b.x - a.x) * at, y: a.y + (b.y - a.y) * at } : at;\n  // Project exactly, so the two children stay perfectly collinear and dissolveNode can\n  // undo the split cleanly later.\n  const exact = projectOnSegment(point, a, b);\n  const fromA = dist(a, exact);\n\n  if (fromA < MIN_WALL_LENGTH) {\n    return { floor, result: { nodeId: wall.a, wallIds: null, droppedOpeningIds: [] }, issues: [] };\n  }\n  if (len - fromA < MIN_WALL_LENGTH) {\n    return { floor, result: { nodeId: wall.b, wallIds: null, droppedOpeningIds: [] }, issues: [] };\n  }\n\n  const next = cloneFloor(floor);\n  const nodeId = newId('n');\n  next.nodes[nodeId] = { id: nodeId, x: exact.x, y: exact.y };\n\n  const firstId = newId('w');\n  const secondId = newId('w');\n  const base = { thickness: wall.thickness, type: wall.type, height: wall.height, baseHeight: wall.baseHeight };\n  next.walls[firstId] = { ...base, id: firstId, a: wall.a, b: nodeId };\n  next.walls[secondId] = { ...base, id: secondId, a: nodeId, b: wall.b };\n  delete next.walls[wallId];\n\n  // Reassign openings by the side their CENTRE falls on. An opening straddling the cut\n  // is a genuine conflict: move it wholly into that child and clamp it, or drop it and\n  // say so. Silently deleting a window is not acceptable.\n  const droppedOpeningIds: string[] = [];\n  const issues: PlanIssue[] = [];\n  for (const opening of Object.values(floor.openings)) {\n    if (opening.wallId !== wallId) continue;\n    const intoFirst = opening.offset <= fromA;\n    const childId = intoFirst ? firstId : secondId;\n    const childLen = intoFirst ? fromA : len - fromA;\n    const offset = intoFirst ? opening.offset : opening.offset - fromA;\n\n    const maxWidth = Math.min(opening.width, childLen - 2, offset * 2, (childLen - offset) * 2);\n    if (maxWidth < MIN_OPENING_WIDTH) {\n      delete next.openings[opening.id];\n      droppedOpeningIds.push(opening.id);\n      issues.push(\n        issue(PlanIssueKind.OpeningDropped, 'An opening was removed: the wall it sat on became too short for it.', {\n          openingIds: [opening.id],\n          wallIds: [wallId],\n        }),\n      );\n      continue;\n    }\n    next.openings[opening.id] = { ...opening, wallId: childId, offset, width: maxWidth };\n  }\n\n  return { floor: next, result: { nodeId, wallIds: [firstId, secondId], droppedOpeningIds }, issues };\n}\n\n// ─── resolveNodeAt ─────────────────────────────────────────────────────────────\n\nexport interface ResolveNodeResult {\n  nodeId: string;\n  createdNode: boolean;\n  splitWallId: string | null;\n}\n\n/**\n * Returns the node to use at `p`: an existing one within `tol`, a new one created by\n * splitting a wall that passes under `p`, or a brand new free-standing node.\n */\nexport function resolveNodeAt(floor: PlanFloor, p: PlanPoint, tol: number): PlanEdit<ResolveNodeResult> {\n  const existing = findNodeNear(floor, p, tol);\n  if (existing) {\n    return { floor, result: { nodeId: existing.id, createdNode: false, splitWallId: null }, issues: [] };\n  }\n\n  const onWall = findWallNear(floor, p, tol);\n  if (onWall) {\n    const split = splitWall(floor, onWall.wall.id, onWall.point);\n    if (split.result.nodeId) {\n      return {\n        floor: split.floor,\n        result: {\n          nodeId: split.result.nodeId,\n          createdNode: split.result.wallIds !== null,\n          splitWallId: split.result.wallIds ? onWall.wall.id : null,\n        },\n        issues: split.issues,\n      };\n    }\n  }\n\n  const next = cloneFloor(floor);\n  const nodeId = newId('n');\n  next.nodes[nodeId] = { id: nodeId, x: p.x, y: p.y };\n  return { floor: next, result: { nodeId, createdNode: true, splitWallId: null }, issues: [] };\n}\n\n// ─── addWall ───────────────────────────────────────────────────────────────────\n\nexport type WallAttrs = Pick<PlanWall, 'thickness' | 'type'> & Partial<Pick<PlanWall, 'height' | 'baseHeight'>>;\n\nexport const DEFAULT_WALL_ATTRS: WallAttrs = { thickness: 200, type: WallType.Interior };\n\ninterface Cut {\n  t: number;\n  nodeId: string | null;\n  /** Set while the node does not exist yet because its wall has not been split. */\n  pending?: { wallId: string; s: number };\n}\n\n/**\n * Adds a wall from `a` to `b`, splitting anything it crosses so the graph stays planar.\n *\n * Collect-then-apply, in two phases: the naive version splits walls while iterating\n * over walls, which both invalidates the iteration and reparameterises the very\n * segments it is about to test.\n */\nexport function addWall(\n  floor: PlanFloor,\n  a: PlanPoint,\n  b: PlanPoint,\n  attrs: WallAttrs = DEFAULT_WALL_ATTRS,\n  tol: number = EPS_COINCIDENT,\n): PlanEdit<{ wallIds: string[]; nodeIds: string[] }> {\n  const issues: PlanIssue[] = [];\n  if (dist(a, b) < MIN_WALL_LENGTH) {\n    return { floor, result: { wallIds: [], nodeIds: [] }, issues };\n  }\n\n  // 1. Endpoints. Each may reuse a node, split a wall, or create a node.\n  const startEdit = resolveNodeAt(floor, a, tol);\n  let work = startEdit.floor;\n  issues.push(...startEdit.issues);\n  const endEdit = resolveNodeAt(work, b, tol);\n  work = endEdit.floor;\n  issues.push(...endEdit.issues);\n\n  const na = startEdit.result.nodeId;\n  const nb = endEdit.result.nodeId;\n  if (na === nb) return { floor, result: { wallIds: [], nodeIds: [] }, issues };\n\n  const A = work.nodes[na];\n  const B = work.nodes[nb];\n  const len = dist(A, B);\n  if (len < MIN_WALL_LENGTH) return { floor, result: { wallIds: [], nodeIds: [] }, issues };\n\n  // 2. COLLECT. Nothing is mutated in this phase.\n  const cuts: Cut[] = [\n    { t: 0, nodeId: na },\n    { t: 1, nodeId: nb },\n  ];\n  const splits = new Map<string, number[]>();\n  const overlaps: Array<[number, number]> = [];\n\n  const paramAlong = (p: PlanPoint): number => pointToSegment(p, A, B).t;\n\n  for (const wall of Object.values(work.walls)) {\n    const wa = work.nodes[wall.a];\n    const wb = work.nodes[wall.b];\n    if (!wa || !wb) continue;\n\n    const hit = segmentIntersection(A, B, wa, wb, tol);\n\n    if (hit.kind === 'collinearOverlap') {\n      const t0 = paramAlong(wa);\n      const t1 = paramAlong(wb);\n      overlaps.push([Math.min(t0, t1), Math.max(t0, t1)]);\n      // Endpoints of the overlapping wall that fall strictly inside AB still need cuts,\n      // or the new wall would run straight past them.\n      for (const [node, t] of [\n        [wa, t0],\n        [wb, t1],\n      ] as const) {\n        if (t * len > MIN_WALL_LENGTH && (1 - t) * len > MIN_WALL_LENGTH) {\n          cuts.push({ t, nodeId: node.id });\n        }\n      }\n      continue;\n    }\n\n    if (hit.kind === 'proper' && hit.t !== undefined && hit.s !== undefined) {\n      const list = splits.get(wall.id);\n      if (list) list.push(hit.s);\n      else splits.set(wall.id, [hit.s]);\n      cuts.push({ t: hit.t, nodeId: null, pending: { wallId: wall.id, s: hit.s } });\n      continue;\n    }\n\n    if (hit.kind === 'touch' && hit.t !== undefined && hit.s !== undefined) {\n      // A T junction where one of the wall's own endpoints sits on AB: reuse it.\n      const atWallStart = hit.s <= 0.5;\n      const endpoint = atWallStart ? wa : wb;\n      const tOnAb = paramAlong(endpoint);\n      if (\n        pointToSegment(endpoint, A, B).dist <= tol &&\n        tOnAb * len > MIN_WALL_LENGTH &&\n        (1 - tOnAb) * len > MIN_WALL_LENGTH\n      ) {\n        cuts.push({ t: tOnAb, nodeId: endpoint.id });\n      }\n    }\n  }\n\n  // 3. Nodes lying on AB that no crossing test found. NOT redundant with step 2: a node\n  // whose incident walls all run away from AB is invisible to every crossing test, yet\n  // failing to cut AB there leaves a node on a wall interior, which silently merges the\n  // two rooms either side of it.\n  for (const node of Object.values(work.nodes)) {\n    if (node.id === na || node.id === nb) continue;\n    const r = pointToSegment(node, A, B);\n    if (r.dist > tol) continue;\n    if (r.t * len <= MIN_WALL_LENGTH || (1 - r.t) * len <= MIN_WALL_LENGTH) continue;\n    cuts.push({ t: r.t, nodeId: node.id });\n  }\n\n  // 4. APPLY splits, per wall, in DESCENDING parameter order so that earlier\n  // parameters stay valid as later ones are consumed.\n  for (const [wallId, params] of splits) {\n    const sorted = [...params].sort((x, y) => y - x);\n    let currentWallId = wallId;\n    const remaining = new Set(sorted);\n    for (const s of sorted) {\n      const target = work.walls[currentWallId];\n      if (!target) break;\n      const wa = work.nodes[target.a];\n      const wb = work.nodes[target.b];\n      if (!wa || !wb) break;\n      // Re-project onto the wall as it stands now, rather than reusing the original\n      // parameter, which the previous split may have reparameterised.\n      const original = floor.walls[wallId];\n      const oa = work.nodes[original?.a ?? target.a] ?? wa;\n      const ob = work.nodes[original?.b ?? target.b] ?? wb;\n      const worldPoint = { x: oa.x + (ob.x - oa.x) * s, y: oa.y + (ob.y - oa.y) * s };\n      const edit = splitWall(work, currentWallId, worldPoint);\n      work = edit.floor;\n      issues.push(...edit.issues);\n      remaining.delete(s);\n      for (const cut of cuts) {\n        if (cut.pending && cut.pending.wallId === wallId && cut.pending.s === s) {\n          cut.nodeId = edit.result.nodeId;\n          cut.pending = undefined;\n        }\n      }\n      // Continue splitting whichever child still holds the remaining parameters.\n      if (edit.result.wallIds) {\n        const [first, second] = edit.result.wallIds;\n        const nextS = Math.max(...[...remaining], -1);\n        currentWallId = nextS >= 0 && nextS > s ? second : first;\n      }\n    }\n  }\n\n  // 5. Chain the cuts into walls.\n  const resolved = cuts.filter((c): c is Cut & { nodeId: string } => Boolean(c.nodeId));\n  resolved.sort((x, y) => x.t - y.t);\n\n  const chain: Array<Cut & { nodeId: string }> = [];\n  for (const cut of resolved) {\n    const last = chain[chain.length - 1];\n    if (!last) {\n      chain.push(cut);\n      continue;\n    }\n    if (last.nodeId === cut.nodeId) continue;\n    if ((cut.t - last.t) * len < MIN_WALL_LENGTH) {\n      // Too close to be a separate wall. Prefer whichever end is an endpoint of AB.\n      if (cut.t >= 1 - 1e-9) chain[chain.length - 1] = cut;\n      continue;\n    }\n    chain.push(cut);\n  }\n\n  const createdWallIds: string[] = [];\n  for (let i = 0; i + 1 < chain.length; i++) {\n    const from = chain[i];\n    const to = chain[i + 1];\n    const midT = (from.t + to.t) / 2;\n    if (overlaps.some(([lo, hi]) => midT > lo + 1e-9 && midT < hi - 1e-9)) {\n      // Already walled. Do not create a duplicate, and do not rewrite the existing\n      // wall's attributes from a stroke that was aimed elsewhere.\n      continue;\n    }\n    if (findWallBetween(work, from.nodeId, to.nodeId)) continue;\n    const wa = work.nodes[from.nodeId];\n    const wb = work.nodes[to.nodeId];\n    if (!wa || !wb || dist(wa, wb) < MIN_WALL_LENGTH) continue;\n\n    const id = newId('w');\n    work = { ...work, walls: { ...work.walls, [id]: { ...attrs, id, a: from.nodeId, b: to.nodeId } } };\n    createdWallIds.push(id);\n  }\n\n  if (createdWallIds.length === 0 && overlaps.length > 0) {\n    issues.push(issue(PlanIssueKind.DuplicateWall, 'A wall already covers that span, so nothing was added.'));\n  }\n\n  return {\n    floor: work,\n    result: { wallIds: createdWallIds, nodeIds: chain.map((c) => c.nodeId) },\n    issues,\n  };\n}\n\n/** Draws the four walls of an axis-aligned rectangle, welding onto anything already there. */\nexport function addRect(\n  floor: PlanFloor,\n  corner: PlanPoint,\n  opposite: PlanPoint,\n  attrs: WallAttrs = DEFAULT_WALL_ATTRS,\n  tol: number = EPS_COINCIDENT,\n): PlanEdit<{ wallIds: string[] }> {\n  const x0 = Math.min(corner.x, opposite.x);\n  const x1 = Math.max(corner.x, opposite.x);\n  const y0 = Math.min(corner.y, opposite.y);\n  const y1 = Math.max(corner.y, opposite.y);\n  if (x1 - x0 < MIN_WALL_LENGTH || y1 - y0 < MIN_WALL_LENGTH) {\n    return { floor, result: { wallIds: [] }, issues: [] };\n  }\n\n  const corners: PlanPoint[] = [\n    { x: x0, y: y0 },\n    { x: x1, y: y0 },\n    { x: x1, y: y1 },\n    { x: x0, y: y1 },\n  ];\n\n  let work = floor;\n  const wallIds: string[] = [];\n  const issues: PlanIssue[] = [];\n  for (let i = 0; i < 4; i++) {\n    const edit = addWall(work, corners[i], corners[(i + 1) % 4], attrs, tol);\n    work = edit.floor;\n    wallIds.push(...edit.result.wallIds);\n    issues.push(...edit.issues);\n  }\n  return { floor: work, result: { wallIds }, issues };\n}\n\n// ─── mergeNodes ────────────────────────────────────────────────────────────────\n\n/**\n * Merges `removeId` into `keepId`, as when a node is dragged onto another.\n *\n * Order matters, and step 3 is mandatory rather than cosmetic: a duplicate wall between\n * the same pair of nodes is exactly the identical-angle degeneracy that corrupts the\n * face walk's angular sort.\n */\nexport function mergeNodes(floor: PlanFloor, keepId: string, removeId: string): PlanEdit<{ removedWallIds: string[] }> {\n  if (keepId === removeId || !floor.nodes[keepId] || !floor.nodes[removeId]) {\n    return { floor, result: { removedWallIds: [] }, issues: [] };\n  }\n\n  const next = cloneFloor(floor);\n  const removedWallIds: string[] = [];\n  const issues: PlanIssue[] = [];\n\n  // 1. Rewire.\n  for (const wall of Object.values(next.walls)) {\n    if (wall.a === removeId) next.walls[wall.id] = { ...next.walls[wall.id], a: keepId };\n    if (wall.b === removeId) next.walls[wall.id] = { ...next.walls[wall.id], b: keepId };\n  }\n\n  // 2. Drop self-loops, and their openings with them.\n  for (const wall of Object.values(next.walls)) {\n    if (wall.a !== wall.b) continue;\n    delete next.walls[wall.id];\n    removedWallIds.push(wall.id);\n    for (const opening of Object.values(next.openings)) {\n      if (opening.wallId === wall.id) delete next.openings[opening.id];\n    }\n  }\n\n  // 3. Deduplicate walls that now share the same unordered node pair.\n  const seen = new Map<string, PlanWall>();\n  for (const wall of Object.values(next.walls)) {\n    const key = wall.a < wall.b ? `${wall.a}|${wall.b}` : `${wall.b}|${wall.a}`;\n    const prior = seen.get(key);\n    if (!prior) {\n      seen.set(key, wall);\n      continue;\n    }\n    // Keep the thicker wall, and carry both walls' openings onto it.\n    const keep = wall.thickness >= prior.thickness ? wall : prior;\n    const drop = keep === wall ? prior : wall;\n    for (const opening of Object.values(next.openings)) {\n      if (opening.wallId === drop.id) {\n        next.openings[opening.id] = { ...opening, wallId: keep.id };\n      }\n    }\n    delete next.walls[drop.id];\n    removedWallIds.push(drop.id);\n    seen.set(key, keep);\n    issues.push(\n      issue(PlanIssueKind.DuplicateWall, 'Two walls became the same wall and were merged.', {\n        wallIds: [keep.id, drop.id],\n      }),\n    );\n  }\n\n  // 4. Drop the now-unreferenced node.\n  delete next.nodes[removeId];\n\n  return { floor: next, result: { removedWallIds }, issues };\n}\n\n// ─── dissolveNode ──────────────────────────────────────────────────────────────\n\nexport interface DissolveNodeOptions {\n  /**\n   * Rejoin the two walls even when the corner is bent or the walls differ in\n   * construction, taking the thicker wall's attributes and rescaling openings onto the\n   * shorter merged wall.\n   *\n   * This is \"delete this corner\" as a user means it. The default, unforced behaviour is\n   * the stricter one used by cleanup and by `removeWall`, where dissolving must not\n   * change the drawing's geometry at all.\n   */\n  force?: boolean;\n}\n\n/**\n * Merges the two walls meeting at a degree-2 node back into one, the natural inverse of\n * `splitWall`.\n *\n * Returns `null` unless the node is genuinely a seam: degree exactly 2, both walls\n * identical in construction, and the turn within `EPS_ANGLE` of straight. Pass\n * `{ force: true }` to rejoin a bent corner as well. Never call it mid-drag: it destroys\n * node ids the user may be holding.\n */\nexport function dissolveNode(\n  floor: PlanFloor,\n  nodeId: string,\n  options: DissolveNodeOptions = {},\n): PlanEdit<{ wallId: string } | null> {\n  const { force = false } = options;\n  const node = floor.nodes[nodeId];\n  if (!node) return { floor, result: null, issues: [] };\n\n  const incident = wallsAtNode(floor, nodeId);\n  if (incident.length !== 2) return { floor, result: null, issues: [] };\n\n  const [w1, w2] = incident;\n  const sameConstruction =\n    w1.thickness === w2.thickness && w1.type === w2.type && w1.height === w2.height && w1.baseHeight === w2.baseHeight;\n  if (!sameConstruction && !force) return { floor, result: null, issues: [] };\n\n  const far1 = w1.a === nodeId ? w1.b : w1.a;\n  const far2 = w2.a === nodeId ? w2.b : w2.a;\n  if (far1 === far2) return { floor, result: null, issues: [] };\n\n  const p1 = floor.nodes[far1];\n  const p2 = floor.nodes[far2];\n  if (!p1 || !p2) return { floor, result: null, issues: [] };\n\n  const span = dist(p1, p2);\n  if (span < MIN_WALL_LENGTH) return { floor, result: null, issues: [] };\n\n  if (!force) {\n    // Straightness by perpendicular offset rather than raw angle, so the tolerance stays\n    // meaningful at any wall length.\n    const offset = Math.abs(cross(p1, p2, node)) / span;\n    if (offset > Math.max(EPS_COINCIDENT, span * EPS_ANGLE)) {\n      return { floor, result: null, issues: [] };\n    }\n  }\n\n  // Forced over a bent corner, the survivor is the thicker of the two, since that is the\n  // one whose construction the rest of the plan is more likely to depend on.\n  const keep = !sameConstruction && w1.thickness < w2.thickness ? w2 : w1;\n\n  const next = cloneFloor(floor);\n  const id = newId('w');\n  next.walls[id] = {\n    id,\n    a: far1,\n    b: far2,\n    thickness: keep.thickness,\n    type: keep.type,\n    height: keep.height,\n    baseHeight: keep.baseHeight,\n  };\n\n  // Re-express every opening's offset against the merged wall's direction (far1 -> far2).\n  const len1 = dist(p1, node);\n  const len2 = dist(node, p2);\n  // Cutting a bent corner shortens the run, so positions are scaled along it rather than\n  // carried over, which would push an opening off the end of its own wall.\n  const scale = len1 + len2 > 0 ? span / (len1 + len2) : 1;\n  const issues: PlanIssue[] = [];\n\n  for (const opening of Object.values(floor.openings)) {\n    if (opening.wallId !== w1.id && opening.wallId !== w2.id) continue;\n    const onFirst = opening.wallId === w1.id;\n    const wall = onFirst ? w1 : w2;\n    const startsAtNode = wall.a === nodeId;\n    const lenOfWall = wallLength(floor, wall);\n    // Distance from the seam, then from the merged wall's own start.\n    const fromSeam = startsAtNode ? opening.offset : lenOfWall - opening.offset;\n    const alongOriginal = onFirst ? len1 - fromSeam : len1 + fromSeam;\n    const offsetOnMerged = alongOriginal * scale;\n\n    const width = Math.min(opening.width * scale, span - 2);\n    if (width < MIN_OPENING_WIDTH) {\n      issues.push(\n        issue(PlanIssueKind.OpeningDropped, 'An opening was removed: the wall it sat on became too short for it.', {\n          openingIds: [opening.id],\n          wallIds: [wall.id],\n        }),\n      );\n      delete next.openings[opening.id];\n      continue;\n    }\n\n    next.openings[opening.id] = {\n      ...opening,\n      wallId: id,\n      width,\n      offset: Math.min(Math.max(offsetOnMerged, width / 2), span - width / 2),\n    };\n  }\n\n  delete next.walls[w1.id];\n  delete next.walls[w2.id];\n  delete next.nodes[nodeId];\n\n  return { floor: next, result: { wallId: id }, issues };\n}\n\n// ─── detachWall ────────────────────────────────────────────────────────────────\n\nexport interface DetachWallResult {\n  /** The wall's end nodes after the detach, in `[a, b]` order. */\n  nodeIds: [string, string];\n  /** Connecting walls minted where an end was shared with a neighbour. */\n  connectorIds: string[];\n  /** Ends that became a straight seam once the wall left, and were dissolved away. */\n  dissolvedNodeIds: string[];\n}\n\n/**\n * Pulls a wall off the nodes it shares with its neighbours and moves it by `delta`,\n * leaving a connecting wall behind at each end that was shared.\n *\n * This is the extrude half of dragging a wall. A plain drag moves the shared end nodes,\n * so everything meeting the wall follows it; here the neighbours keep their ends and\n * only the new connectors grow, which is what you want when a partition joins the wall\n * halfway along and should stay put.\n *\n * A free end is only moved: there is nothing there to detach from, and a connector would\n * be zero length. For the same reason `delta` must be at least `MIN_WALL_LENGTH` long —\n * below that the caller should `moveNode` both ends instead.\n *\n * The connector continues whatever was already at the corner (its thickness, type and\n * heights) when exactly one wall was, so pulling a wall off a plain corner redraws\n * exactly as a plain drag would: the seam that leaves behind is straight and is\n * dissolved before returning. At a junction there is no single wall to continue, so the\n * connector takes the dragged wall's own construction.\n */\nexport function detachWall(floor: PlanFloor, wallId: string, delta: PlanPoint): PlanEdit<DetachWallResult | null> {\n  const wall = floor.walls[wallId];\n  if (!wall || wall.a === wall.b) return { floor, result: null, issues: [] };\n  if (Math.hypot(delta.x, delta.y) < MIN_WALL_LENGTH) return { floor, result: null, issues: [] };\n\n  const ends: [string, string] = [wall.a, wall.b];\n  const nodeIds: [string, string] = [wall.a, wall.b];\n  const sources: (PlanWall | null)[] = [null, null];\n  const connectorIds: string[] = [];\n  const issues: PlanIssue[] = [];\n  let out = floor;\n\n  // 1. Give the wall its own ends. `resolveNodeAt` rather than a bare node, because the\n  //    new end often lands in the middle of another wall — the usual case is pushing the\n  //    shared wall of two rooms sideways, where each end slides along the wall above and\n  //    below it — and only a split keeps a node off a wall's interior.\n  ends.forEach((nodeId, index) => {\n    const origin = floor.nodes[nodeId];\n    if (!origin) return;\n    const to = { x: origin.x + delta.x, y: origin.y + delta.y };\n    const others = wallsAtNode(floor, nodeId).filter((w) => w.id !== wallId);\n\n    if (others.length === 0) {\n      // A free end has nothing to detach from, and a connector there would be a wall\n      // between a node and itself.\n      const next = cloneFloor(out);\n      next.nodes[nodeId] = { ...next.nodes[nodeId], x: to.x, y: to.y };\n      out = next;\n      return;\n    }\n\n    const edit = resolveNodeAt(out, to, EPS_COINCIDENT);\n    out = edit.floor;\n    nodeIds[index] = edit.result.nodeId;\n    // The connector continues whatever was already at this corner, so pulling a wall off\n    // a plain corner redraws exactly as a plain drag would. At a junction there is no\n    // single wall to continue, so it takes the dragged wall's own construction.\n    sources[index] = others.length === 1 ? others[0] : wall;\n  });\n\n  // 2. Move the wall onto them. Its openings ride along untouched: a detach is a pure\n  //    translation, so the wall keeps its id, its direction and its length.\n  const moved = cloneFloor(out);\n  moved.walls[wallId] = { ...moved.walls[wallId], a: nodeIds[0], b: nodeIds[1] };\n  out = moved;\n\n  // 3. Bridge each end back to where it came from, unless a wall already runs there.\n  ends.forEach((nodeId, index) => {\n    const source = sources[index];\n    const from = out.nodes[nodeId];\n    const to = out.nodes[nodeIds[index]];\n    if (!source || !from || !to) return;\n    const edit = addWall(out, from, to, {\n      thickness: source.thickness,\n      type: source.type,\n      height: source.height,\n      baseHeight: source.baseHeight,\n    });\n    out = edit.floor;\n    connectorIds.push(...edit.result.wallIds);\n    issues.push(...edit.issues);\n  });\n\n  // 4. An end that was a plain corner is now a straight seam. Dissolving it is what\n  //    makes detaching a wall with nothing attached to it a no-op on the drawing.\n  const dissolvedNodeIds: string[] = [];\n  ends.forEach((nodeId, index) => {\n    if (nodeIds[index] === nodeId) return;\n    const edit = dissolveNode(out, nodeId);\n    if (!edit.result) return;\n    out = edit.floor;\n    dissolvedNodeIds.push(nodeId);\n    issues.push(...edit.issues);\n  });\n\n  return { floor: out, result: { nodeIds, connectorIds, dissolvedNodeIds }, issues };\n}\n\n// ─── weldShortWalls ────────────────────────────────────────────────────────────\n\n/**\n * Collapses every wall shorter than `minLength` by merging its two ends together.\n *\n * Squashing a wall to nothing is the one thing a drag can do that the model forbids, and\n * it is easy to do by accident: pushing a wall back onto the line it was detached from,\n * or dragging a corner past the one next to it. Deleting the short wall is the wrong\n * repair — it leaves two nodes sitting on the same point with the plan split between\n * them, which reads as two corners stacked on top of each other and derives as two\n * rooms. Merging is lossless, since the two ends are within `minLength` of each other by\n * definition.\n *\n * The end with more walls on it survives, so the corner that was already there keeps its\n * position and the one that arrived is the one that gives way.\n */\nexport function weldShortWalls(\n  floor: PlanFloor,\n  minLength: number = MIN_WALL_LENGTH,\n): PlanEdit<{ weldedWallIds: string[] }> {\n  let out = floor;\n  const weldedWallIds: string[] = [];\n  const issues: PlanIssue[] = [];\n\n  // Re-scan rather than iterate: one weld moves nodes, so it can shorten another wall to\n  // nothing in turn. Bounded by the wall count, since every pass removes at least one.\n  for (let guard = Object.keys(floor.walls).length; guard > 0; guard--) {\n    const short = Object.values(out.walls).find((w) => {\n      if (w.a === w.b) return false;\n      const a = out.nodes[w.a];\n      const b = out.nodes[w.b];\n      return a && b && dist(a, b) < minLength;\n    });\n    if (!short) break;\n\n    const keep = wallsAtNode(out, short.b).length > wallsAtNode(out, short.a).length ? short.b : short.a;\n    const edit = mergeNodes(out, keep, keep === short.a ? short.b : short.a);\n    if (edit.floor === out) break;\n    out = edit.floor;\n    weldedWallIds.push(short.id);\n    issues.push(...edit.issues);\n  }\n\n  return { floor: out, result: { weldedWallIds }, issues };\n}\n\n// ─── removeWall / moveNode ─────────────────────────────────────────────────────\n\nexport function removeWall(\n  floor: PlanFloor,\n  wallId: string,\n  opts: { pruneOrphanNodes?: boolean; dissolveCollinear?: boolean } = {},\n): PlanEdit<{ removedNodeIds: string[]; dissolvedWallIds: string[] }> {\n  const { pruneOrphanNodes = true, dissolveCollinear = true } = opts;\n  const wall = floor.walls[wallId];\n  if (!wall) return { floor, result: { removedNodeIds: [], dissolvedWallIds: [] }, issues: [] };\n\n  let next = cloneFloor(floor);\n  delete next.walls[wallId];\n  for (const opening of Object.values(next.openings)) {\n    if (opening.wallId === wallId) delete next.openings[opening.id];\n  }\n\n  const removedNodeIds: string[] = [];\n  const dissolvedWallIds: string[] = [];\n  const issues: PlanIssue[] = [];\n\n  for (const endpoint of [wall.a, wall.b]) {\n    const degree = wallsAtNode(next, endpoint).length;\n    if (degree === 0 && pruneOrphanNodes) {\n      delete next.nodes[endpoint];\n      removedNodeIds.push(endpoint);\n    } else if (degree === 2 && dissolveCollinear) {\n      // The natural inverse of the split that created this seam.\n      const edit = dissolveNode(next, endpoint);\n      if (edit.result) {\n        next = edit.floor;\n        dissolvedWallIds.push(edit.result.wallId);\n        removedNodeIds.push(endpoint);\n        issues.push(...edit.issues);\n      }\n    }\n  }\n\n  return { floor: next, result: { removedNodeIds, dissolvedWallIds }, issues };\n}\n\nexport function moveNode(floor: PlanFloor, nodeId: string, to: PlanPoint): PlanEdit {\n  const node = floor.nodes[nodeId];\n  if (!node) return { floor, result: undefined, issues: [] };\n  const next = cloneFloor(floor);\n  next.nodes[nodeId] = { ...node, x: to.x, y: to.y };\n  return { floor: next, result: undefined, issues: [] };\n}\n\n/**\n * Translates every node, and every room anchor with it.\n *\n * Room matching does not depend on this (it is primarily wall-id based), but a plan\n * that is dragged and then saved without a derive should still have sane anchors.\n */\nexport function transformPlan(floor: PlanFloor, dx: number, dy: number): PlanFloor {\n  const next = cloneFloor(floor);\n  for (const node of Object.values(floor.nodes)) {\n    next.nodes[node.id] = { ...node, x: node.x + dx, y: node.y + dy };\n  }\n  for (const room of Object.values(floor.rooms)) {\n    next.rooms[room.id] = { ...room, anchor: { x: room.anchor.x + dx, y: room.anchor.y + dy } };\n  }\n  return next;\n}\n\n/**\n * Multiplies every coordinate and dimension by `factor`.\n *\n * The one place a scale factor belongs: converting geometry drawn in another unit into\n * the module's millimetre invariant, once, where it enters. The caller picks the factor,\n * because nothing in the data says what it should be.\n */\nexport function scalePlan(floor: PlanFloor, factor: number): PlanFloor {\n  const next = cloneFloor(floor);\n  for (const node of Object.values(floor.nodes)) {\n    next.nodes[node.id] = { ...node, x: node.x * factor, y: node.y * factor };\n  }\n  for (const wall of Object.values(floor.walls)) {\n    next.walls[wall.id] = {\n      ...wall,\n      thickness: wall.thickness * factor,\n      height: wall.height === undefined ? undefined : wall.height * factor,\n      baseHeight: wall.baseHeight === undefined ? undefined : wall.baseHeight * factor,\n    };\n  }\n  for (const opening of Object.values(floor.openings)) {\n    next.openings[opening.id] = {\n      ...opening,\n      offset: opening.offset * factor,\n      width: opening.width * factor,\n      sill: opening.sill * factor,\n      head: opening.head * factor,\n    };\n  }\n  for (const room of Object.values(floor.rooms)) {\n    next.rooms[room.id] = {\n      ...room,\n      anchor: { x: room.anchor.x * factor, y: room.anchor.y * factor },\n      area: room.area * factor * factor,\n    };\n  }\n  return next;\n}\n\n// ─── Openings ──────────────────────────────────────────────────────────────────\n\nexport function addOpening(floor: PlanFloor, opening: Omit<PlanOpening, 'id'>): PlanEdit<{ openingId: string | null }> {\n  const wall = floor.walls[opening.wallId];\n  if (!wall) return { floor, result: { openingId: null }, issues: [] };\n\n  const len = wallLength(floor, wall);\n  const width = Math.min(opening.width, len - 2);\n  if (width < MIN_OPENING_WIDTH) {\n    return {\n      floor,\n      result: { openingId: null },\n      issues: [\n        issue(PlanIssueKind.OpeningDropped, 'That wall is too short for an opening.', {\n          wallIds: [wall.id],\n        }),\n      ],\n    };\n  }\n\n  const offset = Math.min(Math.max(opening.offset, width / 2), len - width / 2);\n  const next = cloneFloor(floor);\n  const id = newId('o');\n  next.openings[id] = { ...opening, id, offset, width };\n  return { floor: next, result: { openingId: id }, issues: [] };\n}\n\nexport function updateOpening(\n  floor: PlanFloor,\n  openingId: string,\n  patch: Partial<Omit<PlanOpening, 'id' | 'wallId'>>,\n): PlanEdit {\n  const opening = floor.openings[openingId];\n  if (!opening) return { floor, result: undefined, issues: [] };\n  const wall = floor.walls[opening.wallId];\n  if (!wall) return { floor, result: undefined, issues: [] };\n\n  const len = wallLength(floor, wall);\n  const merged = { ...opening, ...patch };\n  const width = Math.min(Math.max(merged.width, MIN_OPENING_WIDTH), Math.max(len - 2, MIN_OPENING_WIDTH));\n  const offset = Math.min(Math.max(merged.offset, width / 2), Math.max(len - width / 2, width / 2));\n\n  const next = cloneFloor(floor);\n  next.openings[openingId] = { ...merged, width, offset };\n  return { floor: next, result: undefined, issues: [] };\n}\n\nexport function removeOpening(floor: PlanFloor, openingId: string): PlanEdit {\n  if (!floor.openings[openingId]) return { floor, result: undefined, issues: [] };\n  const next = cloneFloor(floor);\n  delete next.openings[openingId];\n  return { floor: next, result: undefined, issues: [] };\n}\n\n// ─── Validation and cleanup ────────────────────────────────────────────────────\n\n/**\n * Reports every way the graph currently violates the planar invariant.\n *\n * O(W²), so run it in tests and in development, not on every keystroke.\n */\nexport function validatePlan(floor: PlanFloor): PlanIssue[] {\n  const issues: PlanIssue[] = [];\n  const walls = Object.values(floor.walls);\n\n  for (const wall of walls) {\n    const a = floor.nodes[wall.a];\n    const b = floor.nodes[wall.b];\n    if (wall.a === wall.b) {\n      issues.push(\n        issue(PlanIssueKind.SelfLoopWall, 'A wall starts and ends at the same node.', { wallIds: [wall.id] }),\n      );\n      continue;\n    }\n    if (!a || !b) {\n      issues.push(issue(PlanIssueKind.ZeroLengthWall, 'A wall references a missing node.', { wallIds: [wall.id] }));\n      continue;\n    }\n    if (dist(a, b) < MIN_WALL_LENGTH) {\n      issues.push(\n        issue(PlanIssueKind.ZeroLengthWall, 'A wall is shorter than the minimum length.', { wallIds: [wall.id] }),\n      );\n    }\n  }\n\n  const pairSeen = new Map<string, string>();\n  for (const wall of walls) {\n    const key = wall.a < wall.b ? `${wall.a}|${wall.b}` : `${wall.b}|${wall.a}`;\n    const prior = pairSeen.get(key);\n    if (prior) {\n      issues.push(\n        issue(PlanIssueKind.DuplicateWall, 'Two walls connect the same pair of nodes.', {\n          wallIds: [prior, wall.id],\n        }),\n      );\n    } else {\n      pairSeen.set(key, wall.id);\n    }\n  }\n\n  const nodes = Object.values(floor.nodes);\n  for (let i = 0; i < nodes.length; i++) {\n    for (let j = i + 1; j < nodes.length; j++) {\n      if (dist(nodes[i], nodes[j]) < EPS_COINCIDENT) {\n        issues.push(\n          issue(PlanIssueKind.CoincidentNodes, 'Two nodes occupy the same position.', {\n            nodeIds: [nodes[i].id, nodes[j].id],\n          }),\n        );\n      }\n    }\n  }\n\n  for (let i = 0; i < walls.length; i++) {\n    for (let j = i + 1; j < walls.length; j++) {\n      const w1 = walls[i];\n      const w2 = walls[j];\n      const a = floor.nodes[w1.a];\n      const b = floor.nodes[w1.b];\n      const c = floor.nodes[w2.a];\n      const d = floor.nodes[w2.b];\n      if (!a || !b || !c || !d) continue;\n      const hit = segmentIntersection(a, b, c, d);\n      if (hit.kind === 'proper') {\n        issues.push(\n          issue(PlanIssueKind.UnsplitCrossing, 'Two walls cross with no node at the intersection.', {\n            wallIds: [w1.id, w2.id],\n          }),\n        );\n      } else if (hit.kind === 'collinearOverlap') {\n        issues.push(\n          issue(PlanIssueKind.OverlappingWalls, 'Two walls overlap along the same line.', {\n            wallIds: [w1.id, w2.id],\n          }),\n        );\n      }\n    }\n  }\n\n  for (const wall of walls) {\n    const a = floor.nodes[wall.a];\n    const b = floor.nodes[wall.b];\n    if (!a || !b) continue;\n    for (const node of nodes) {\n      if (node.id === wall.a || node.id === wall.b) continue;\n      const r = pointToSegment(node, a, b);\n      if (r.dist < EPS_COINCIDENT && r.t > 0 && r.t < 1) {\n        issues.push(\n          issue(PlanIssueKind.UnsplitNodeOnWall, 'A node sits on a wall that was never split there.', {\n            wallIds: [wall.id],\n            nodeIds: [node.id],\n          }),\n        );\n      }\n    }\n  }\n\n  for (const opening of Object.values(floor.openings)) {\n    if (!floor.walls[opening.wallId]) {\n      issues.push(\n        issue(PlanIssueKind.DanglingOpening, 'An opening references a wall that no longer exists.', {\n          openingIds: [opening.id],\n        }),\n      );\n    }\n  }\n\n  const referenced = new Set<string>();\n  for (const wall of walls) {\n    referenced.add(wall.a);\n    referenced.add(wall.b);\n  }\n  for (const node of nodes) {\n    if (!referenced.has(node.id)) {\n      issues.push(issue(PlanIssueKind.OrphanNode, 'A node has no walls.', { nodeIds: [node.id] }));\n    }\n  }\n\n  return issues;\n}\n\n/**\n * Removes what validation can safely remove: degenerate walls, orphan nodes, dangling\n * openings, and duplicate walls. Idempotent.\n *\n * Deliberately does not touch unsplit crossings: fixing one changes the room structure,\n * which is the user's call, not a cleanup pass's.\n */\nexport function cleanupPlan(floor: PlanFloor): PlanEdit<PlanIssue[]> {\n  const fixed: PlanIssue[] = [];\n\n  // Welding comes first, and is why the sweep below only ever sees walls that are\n  // degenerate for some other reason: a wall squashed to nothing has two perfectly good\n  // ends, and deleting it would leave them stacked on the same point.\n  const welded = weldShortWalls(floor);\n  const next = cloneFloor(welded.floor);\n  for (const wallId of welded.result.weldedWallIds) {\n    fixed.push(\n      issue(PlanIssueKind.ZeroLengthWall, 'Welded the two ends of a wall with no length.', {\n        wallIds: [wallId],\n      }),\n    );\n  }\n\n  for (const wall of Object.values(next.walls)) {\n    const a = next.nodes[wall.a];\n    const b = next.nodes[wall.b];\n    if (wall.a === wall.b || !a || !b || dist(a, b) < MIN_WALL_LENGTH) {\n      delete next.walls[wall.id];\n      fixed.push(issue(PlanIssueKind.ZeroLengthWall, 'Removed a degenerate wall.', { wallIds: [wall.id] }));\n    }\n  }\n\n  const pairSeen = new Map<string, PlanWall>();\n  for (const wall of Object.values(next.walls)) {\n    const key = wall.a < wall.b ? `${wall.a}|${wall.b}` : `${wall.b}|${wall.a}`;\n    const prior = pairSeen.get(key);\n    if (!prior) {\n      pairSeen.set(key, wall);\n      continue;\n    }\n    const keep = wall.thickness >= prior.thickness ? wall : prior;\n    const drop = keep === wall ? prior : wall;\n    for (const opening of Object.values(next.openings)) {\n      if (opening.wallId === drop.id) next.openings[opening.id] = { ...opening, wallId: keep.id };\n    }\n    delete next.walls[drop.id];\n    pairSeen.set(key, keep);\n    fixed.push(issue(PlanIssueKind.DuplicateWall, 'Removed a duplicate wall.', { wallIds: [drop.id] }));\n  }\n\n  for (const opening of Object.values(next.openings)) {\n    if (!next.walls[opening.wallId]) {\n      delete next.openings[opening.id];\n      fixed.push(\n        issue(PlanIssueKind.DanglingOpening, 'Removed an opening with no wall.', {\n          openingIds: [opening.id],\n        }),\n      );\n    }\n  }\n\n  // One pass to count degrees, rather than scanning every wall per node: the naive form\n  // is O(nodes x walls), which is the dominant cost on a large plan.\n  const referenced = new Set<string>();\n  for (const wall of Object.values(next.walls)) {\n    referenced.add(wall.a);\n    referenced.add(wall.b);\n  }\n  for (const node of Object.values(next.nodes)) {\n    if (!referenced.has(node.id)) {\n      delete next.nodes[node.id];\n      fixed.push(issue(PlanIssueKind.OrphanNode, 'Removed a node with no walls.', { nodeIds: [node.id] }));\n    }\n  }\n\n  return { floor: next, result: fixed, issues: fixed };\n}\n\nexport { wallLength, wallsAtNode, findWallBetween };\n","/**\n * Document-level helpers: creating a plan, addressing a floor within it, and the\n * revision counter that memoisation keys off.\n */\n\nimport { boundsOf } from './floorPlan.geometry';\nimport { freshName, newId } from './floorPlan.graph';\nimport { PlanUnitSystem } from './floorPlan.types';\nimport type { FloorPlan, PlanEdit, PlanFloor, PlanPoint } from './floorPlan.types';\n\nexport interface EmptyFloorPlanOptions {\n  id?: string;\n  name?: string;\n  floorId?: string;\n  floorLabel?: string;\n  unitSystem?: PlanUnitSystem;\n  /** Default storey height in millimetres. */\n  height?: number;\n}\n\nexport function emptyFloor(options: Omit<EmptyFloorPlanOptions, 'id' | 'name'> = {}): PlanFloor {\n  return {\n    id: options.floorId ?? newId('f'),\n    label: options.floorLabel ?? 'Ground floor',\n    elevation: 0,\n    height: options.height ?? 2500,\n    nodes: {},\n    walls: {},\n    openings: {},\n    rooms: {},\n  };\n}\n\nexport function emptyFloorPlan(options: EmptyFloorPlanOptions = {}): FloorPlan {\n  return {\n    id: options.id ?? newId('plan'),\n    name: options.name,\n    version: 1,\n    unitSystem: options.unitSystem ?? PlanUnitSystem.Metric,\n    floors: [emptyFloor(options)],\n    revision: 0,\n  };\n}\n\nexport function getFloor(plan: FloorPlan, floorId?: string): PlanFloor | null {\n  if (!floorId) return plan.floors[0] ?? null;\n  return plan.floors.find((f) => f.id === floorId) ?? null;\n}\n\n/**\n * Applies a floor-level edit to one floor of a plan, bumping `revision` so memoised\n * derivations know to recompute.\n *\n * The revision only moves when the edit actually returned a different floor, so a\n * gesture that changed nothing does not invalidate anything.\n */\nexport function updateFloor<T>(\n  plan: FloorPlan,\n  floorId: string | undefined,\n  edit: (floor: PlanFloor) => PlanEdit<T>,\n): PlanEdit<T> & { plan: FloorPlan } {\n  const floor = getFloor(plan, floorId);\n  if (!floor) {\n    return { plan, floor: emptyFloor(), result: undefined as T, issues: [] };\n  }\n\n  const applied = edit(floor);\n  if (applied.floor === floor) {\n    return { ...applied, plan };\n  }\n\n  return {\n    ...applied,\n    plan: {\n      ...plan,\n      floors: plan.floors.map((f) => (f.id === floor.id ? applied.floor : f)),\n      revision: plan.revision + 1,\n    },\n  };\n}\n\n/** Replaces one floor outright, bumping the revision. */\nexport function setFloor(plan: FloorPlan, floor: PlanFloor): FloorPlan {\n  const exists = plan.floors.some((f) => f.id === floor.id);\n  return {\n    ...plan,\n    floors: exists ? plan.floors.map((f) => (f.id === floor.id ? floor : f)) : [...plan.floors, floor],\n    revision: plan.revision + 1,\n  };\n}\n\n// ─── Floors ────────────────────────────────────────────────────────────────────\n\n/**\n * Floors run bottom to top: index 0 is the lowest storey, and `elevation` rises with\n * the index. Nothing enforces that beyond these helpers, but `floorBelow` and the\n * drawer's underlay both read it, so an out-of-order `floors` array shows the wrong\n * storey underneath.\n */\nexport function floorIndex(plan: FloorPlan, floorId: string): number {\n  return plan.floors.findIndex((f) => f.id === floorId);\n}\n\n/** The storey directly under this one, which is what an underlay traces. */\nexport function floorBelow(plan: FloorPlan, floorId: string): PlanFloor | null {\n  const i = floorIndex(plan, floorId);\n  return i > 0 ? plan.floors[i - 1] : null;\n}\n\nexport interface AddFloorOptions {\n  label?: string;\n  /**\n   * Names the floor when `label` is not given. `n` is the floor count plus one, moved past\n   * any number whose name is taken. Default `` (n) => `Floor ${n}` ``.\n   */\n  nameFor?: (n: number) => string;\n  /** Millimetres. Defaults to the storey height of the floor it is added above. */\n  height?: number;\n  /** Where to put it. Defaults to directly above `aboveFloorId`, else on top. */\n  index?: number;\n  id?: string;\n}\n\nconst defaultFloorName = (n: number) => `Floor ${n}`;\n\n/**\n * Adds an empty storey and returns the plan with it, plus the floor itself.\n *\n * Its elevation is the top of the storey below, so a plan stays physically coherent\n * without the caller doing arithmetic. Everything above is pushed up by the new storey's\n * height, since inserting a floor into a building raises the ones over it.\n */\nexport function addFloor(plan: FloorPlan, options: AddFloorOptions = {}): { plan: FloorPlan; floor: PlanFloor } {\n  const base = plan.floors[0]?.elevation ?? 0;\n  const at = Math.max(0, Math.min(options.index ?? plan.floors.length, plan.floors.length));\n  const below = plan.floors[at - 1];\n  const height = options.height ?? below?.height ?? 2500;\n\n  const floor: PlanFloor = {\n    ...emptyFloor({ height }),\n    id: options.id ?? newId('f'),\n    label:\n      options.label ??\n      freshName(options.nameFor ?? defaultFloorName, plan.floors.length + 1, new Set(plan.floors.map((f) => f.label))),\n    elevation: below ? below.elevation + below.height : 0,\n  };\n\n  const floors = [...plan.floors.slice(0, at), floor, ...plan.floors.slice(at)];\n  return { plan: { ...plan, floors: reElevate(floors, base), revision: plan.revision + 1 }, floor };\n}\n\n/**\n * Removes a storey, unless it is the last one: a plan with no floors has nowhere to\n * draw, and every consumer would need a special case for it.\n */\nexport function removeFloor(plan: FloorPlan, floorId: string): FloorPlan {\n  if (plan.floors.length <= 1) return plan;\n  const base = plan.floors[0].elevation;\n  const floors = plan.floors.filter((f) => f.id !== floorId);\n  if (floors.length === plan.floors.length) return plan;\n  return { ...plan, floors: reElevate(floors, base), revision: plan.revision + 1 };\n}\n\n/**\n * Copies a storey, including its geometry, directly above itself.\n *\n * Every id is regenerated and every reference rewired to the copy, so the two storeys\n * share nothing: an edit to one must not reach through into the other. This is the usual\n * way to start an upper floor, since the walls below are mostly where the walls above go.\n */\nexport function duplicateFloor(\n  plan: FloorPlan,\n  floorId: string,\n  options: { label?: string } = {},\n): { plan: FloorPlan; floor: PlanFloor | null } {\n  const source = getFloor(plan, floorId);\n  if (!source) return { plan, floor: null };\n\n  const nodeIds = new Map(Object.keys(source.nodes).map((id) => [id, newId('n')]));\n  const wallIds = new Map(Object.keys(source.walls).map((id) => [id, newId('w')]));\n\n  const copy: PlanFloor = {\n    ...source,\n    id: newId('f'),\n    label: options.label ?? `${source.label} copy`,\n    nodes: {},\n    walls: {},\n    openings: {},\n    rooms: {},\n  };\n\n  for (const node of Object.values(source.nodes)) {\n    const id = nodeIds.get(node.id)!;\n    copy.nodes[id] = { ...node, id };\n  }\n  for (const wall of Object.values(source.walls)) {\n    const id = wallIds.get(wall.id)!;\n    copy.walls[id] = { ...wall, id, a: nodeIds.get(wall.a)!, b: nodeIds.get(wall.b)! };\n  }\n  for (const opening of Object.values(source.openings)) {\n    const wallId = wallIds.get(opening.wallId);\n    if (!wallId) continue;\n    const id = newId('o');\n    copy.openings[id] = { ...opening, id, wallId };\n  }\n  for (const room of Object.values(source.rooms)) {\n    const id = newId('r');\n    copy.rooms[id] = {\n      ...room,\n      id,\n      anchor: { ...room.anchor },\n      // Room identity is matched on these, so they have to point at the copy's own walls\n      // or the first derive would strand every room in it.\n      wallIds: room.wallIds.map((w) => wallIds.get(w)).filter((w): w is string => !!w),\n    };\n  }\n\n  const base = plan.floors[0].elevation;\n  const at = floorIndex(plan, floorId) + 1;\n  const floors = [...plan.floors.slice(0, at), copy, ...plan.floors.slice(at)];\n  return { plan: { ...plan, floors: reElevate(floors, base), revision: plan.revision + 1 }, floor: copy };\n}\n\n/** Millimetres. Low enough for a crawl space, high enough to stop a zero-height storey. */\nexport const MIN_FLOOR_HEIGHT = 500;\n\n/**\n * Sets a storey's height, and drops or lifts everything above it to suit.\n *\n * This is why it is not a plain `setFloor`: a storey's height is what the next one sits\n * on, so changing it without re-stacking leaves the floors above either buried in it or\n * floating over it, which nothing in 2D would ever show.\n */\nexport function setFloorHeight(plan: FloorPlan, floorId: string, height: number): FloorPlan {\n  const floor = getFloor(plan, floorId);\n  const next = Math.max(MIN_FLOOR_HEIGHT, Math.round(height));\n  if (!floor || floor.height === next) return plan;\n\n  const base = plan.floors[0].elevation;\n  const floors = plan.floors.map((f) => (f.id === floor.id ? { ...f, height: next } : f));\n  return { ...plan, floors: reElevate(floors, base), revision: plan.revision + 1 };\n}\n\n/** Renames a storey. Its own field, since nothing else about a floor is text. */\nexport function renameFloor(plan: FloorPlan, floorId: string, label: string): FloorPlan {\n  const floor = getFloor(plan, floorId);\n  if (!floor || floor.label === label) return plan;\n  return setFloor(plan, { ...floor, label });\n}\n\n/** Moves a storey up or down the stack, re-stacking the elevations behind it. */\nexport function moveFloor(plan: FloorPlan, floorId: string, toIndex: number): FloorPlan {\n  const from = floorIndex(plan, floorId);\n  const to = Math.max(0, Math.min(toIndex, plan.floors.length - 1));\n  if (from < 0 || from === to) return plan;\n  const base = plan.floors[0].elevation;\n  const floors = [...plan.floors];\n  const [moved] = floors.splice(from, 1);\n  floors.splice(to, 0, moved);\n  return { ...plan, floors: reElevate(floors, base), revision: plan.revision + 1 };\n}\n\n/**\n * Re-stacks elevations so each storey sits on the one below. Without this, adding or\n * removing a floor leaves a gap or an overlap in the building that only shows up in 3D.\n *\n * `base` is the building's own ground level and must come from the plan BEFORE the\n * edit: reading it off whichever floor ends up lowest moves the whole building whenever\n * two storeys of different heights are reordered.\n */\nfunction reElevate(floors: PlanFloor[], base: number): PlanFloor[] {\n  let elevation = base;\n  return floors.map((floor) => {\n    const next = floor.elevation === elevation ? floor : { ...floor, elevation };\n    elevation += floor.height;\n    return next;\n  });\n}\n\nexport interface PlanBounds {\n  minX: number;\n  minY: number;\n  maxX: number;\n  maxY: number;\n  width: number;\n  height: number;\n}\n\n/**\n * Bounding box of a floor's geometry, padded by half the thickest wall so the outer\n * face of every wall is inside it rather than its centreline.\n */\nexport function floorBounds(floor: PlanFloor): PlanBounds | null {\n  const points: PlanPoint[] = Object.values(floor.nodes).map((n) => ({ x: n.x, y: n.y }));\n  const raw = boundsOf(points);\n  if (!raw) return null;\n\n  let maxThickness = 0;\n  for (const wall of Object.values(floor.walls)) {\n    if (wall.thickness > maxThickness) maxThickness = wall.thickness;\n  }\n  const pad = maxThickness / 2;\n\n  const minX = raw.minX - pad;\n  const minY = raw.minY - pad;\n  const maxX = raw.maxX + pad;\n  const maxY = raw.maxY + pad;\n  return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };\n}\n","/**\n * Planar face extraction: turns the wall graph into the enclosed rooms it forms.\n *\n * Each wall becomes two darts (directed half-edges), wall `i` owning darts `2i` and\n * `2i + 1`, so `twin(d) === d ^ 1` with no lookup. The darts leaving each node are\n * sorted by angle, and the face walk is:\n *\n *     next(d) = the cyclic PREDECESSOR of twin(d) in the sorted ring at head(d)\n *\n * Because the shoelace sum runs on the same raw coordinates as the angular sort, the\n * rule needs no orientation convention: bounded faces come out with POSITIVE signed\n * area and each connected component's outer boundary comes out negative, whichever way\n * you read the y axis.\n *\n * A dangling wall (one endpoint of degree 1) is handled by the rule itself: a\n * one-element ring's cyclic predecessor is itself, so the walk goes out along the spur\n * and straight back. Both of its darts land in the same cycle, no extra face appears,\n * and the face gains a zero-width slit that contributes nothing to area, fill or\n * point-in-polygon.\n */\n\nimport {\n  EPS_AREA,\n  MIN_WALL_LENGTH,\n  areaCentroid,\n  dist,\n  perimeter as ringPerimeter,\n  pointInRing,\n  representativePoint,\n  signedArea,\n} from './floorPlan.geometry';\nimport type { PlanFace, PlanFloor, PlanNode, PlanPoint, PlanWall } from './floorPlan.types';\n\ninterface Dart {\n  wallId: string;\n  from: string;\n  to: string;\n}\n\ninterface DartGraph {\n  darts: Dart[];\n  /** Node id to its outgoing dart indices, sorted by angle ascending. */\n  outgoing: Map<string, number[]>;\n  /** Dart index to its position within its own outgoing ring. */\n  ringIndex: Int32Array;\n  nodeOf: (id: string) => PlanNode;\n}\n\n/** Walls that are safe to traverse: real nodes at both ends, and long enough to have an angle. */\nfunction usableWalls(floor: PlanFloor): PlanWall[] {\n  return Object.values(floor.walls).filter((w) => {\n    if (w.a === w.b) return false;\n    const a = floor.nodes[w.a];\n    const b = floor.nodes[w.b];\n    if (!a || !b) return false;\n    return dist(a, b) >= MIN_WALL_LENGTH;\n  });\n}\n\nfunction buildDartGraph(floor: PlanFloor): DartGraph {\n  const walls = usableWalls(floor);\n  const darts: Dart[] = new Array(walls.length * 2);\n\n  for (let i = 0; i < walls.length; i++) {\n    const w = walls[i];\n    darts[2 * i] = { wallId: w.id, from: w.a, to: w.b };\n    darts[2 * i + 1] = { wallId: w.id, from: w.b, to: w.a };\n  }\n\n  const nodeOf = (id: string): PlanNode => floor.nodes[id];\n\n  const outgoing = new Map<string, number[]>();\n  for (let d = 0; d < darts.length; d++) {\n    const list = outgoing.get(darts[d].from);\n    if (list) list.push(d);\n    else outgoing.set(darts[d].from, [d]);\n  }\n\n  const angleOf = (d: number): number => {\n    const from = nodeOf(darts[d].from);\n    const to = nodeOf(darts[d].to);\n    return Math.atan2(to.y - from.y, to.x - from.x);\n  };\n\n  for (const ring of outgoing.values()) {\n    ring.sort((x, y) => {\n      const ax = angleOf(x);\n      const ay = angleOf(y);\n      if (ax !== ay) return ax - ay;\n      // Deterministic tie-break. A genuine tie means two walls leave this node in the\n      // same direction, which validatePlan reports as OverlappingWalls: the sort stays\n      // stable so the walk cannot hang, but the resulting face is still wrong.\n      const wx = darts[x].wallId;\n      const wy = darts[y].wallId;\n      if (wx !== wy) return wx < wy ? -1 : 1;\n      return x - y;\n    });\n  }\n\n  const ringIndex = new Int32Array(darts.length);\n  for (const ring of outgoing.values()) {\n    for (let i = 0; i < ring.length; i++) ringIndex[ring[i]] = i;\n  }\n\n  return { darts, outgoing, ringIndex, nodeOf };\n}\n\nfunction nextDart(g: DartGraph, d: number): number {\n  const ring = g.outgoing.get(g.darts[d].to);\n  /* c8 ignore next -- head() always has the twin in its ring by construction */\n  if (!ring || ring.length === 0) return d ^ 1;\n  const i = g.ringIndex[d ^ 1];\n  return ring[(i - 1 + ring.length) % ring.length];\n}\n\n/** Every dart belongs to exactly one cycle. */\nfunction walkCycles(g: DartGraph): number[][] {\n  const visited = new Uint8Array(g.darts.length);\n  const cycles: number[][] = [];\n\n  for (let d0 = 0; d0 < g.darts.length; d0++) {\n    if (visited[d0]) continue;\n    const cycle: number[] = [];\n    let d = d0;\n    let guard = 0;\n    do {\n      visited[d] = 1;\n      cycle.push(d);\n      d = nextDart(g, d);\n      // Only reachable if the dart permutation is corrupt (duplicate walls producing\n      // an exact angular tie). Bail rather than spin.\n      if (++guard > g.darts.length + 1) break;\n    } while (d !== d0 && !visited[d]);\n    cycles.push(cycle);\n  }\n\n  return cycles;\n}\n\n/** Union-find over node ids, joined by each wall. */\nfunction connectedComponents(g: DartGraph): Map<string, string> {\n  const parent = new Map<string, string>();\n  const find = (x: string): string => {\n    let root = x;\n    while (parent.get(root) !== root) root = parent.get(root) ?? root;\n    let cur = x;\n    while (parent.get(cur) !== root) {\n      const nxt = parent.get(cur) ?? root;\n      parent.set(cur, root);\n      cur = nxt;\n    }\n    return root;\n  };\n\n  for (const nodeId of g.outgoing.keys()) parent.set(nodeId, nodeId);\n  for (let d = 0; d < g.darts.length; d += 2) {\n    const ra = find(g.darts[d].from);\n    const rb = find(g.darts[d].to);\n    if (ra !== rb) parent.set(ra, rb);\n  }\n\n  const out = new Map<string, string>();\n  for (const nodeId of g.outgoing.keys()) out.set(nodeId, find(nodeId));\n  return out;\n}\n\nfunction cycleRing(g: DartGraph, cycle: number[]): PlanPoint[] {\n  return cycle.map((d) => {\n    const n = g.nodeOf(g.darts[d].from);\n    return { x: n.x, y: n.y };\n  });\n}\n\n/**\n * A face id derived from the boundary itself, so it stays stable across derives for as\n * long as the geometry is unchanged.\n */\nfunction faceId(g: DartGraph, cycle: number[]): string {\n  let best = '';\n  for (const d of cycle) {\n    const key = `${g.darts[d].wallId}>${g.darts[d].from}`;\n    if (best === '' || key < best) best = key;\n  }\n  return `f:${best}`;\n}\n\n/**\n * Extracts every enclosed face from the wall graph, with nested components attached as\n * holes.\n *\n * Returned faces are positively wound (visually clockwise in SVG space) and their\n * `area` already has holes subtracted.\n */\nexport function deriveFaces(floor: PlanFloor): PlanFace[] {\n  const g = buildDartGraph(floor);\n  if (g.darts.length === 0) return [];\n\n  const componentOf = connectedComponents(g);\n  const cycles = walkCycles(g);\n\n  interface Candidate {\n    face: PlanFace;\n    component: string;\n    outerArea: number;\n  }\n\n  const candidates: Candidate[] = [];\n  /** One outer (negatively wound) cycle per connected component. */\n  const outerRings = new Map<string, PlanPoint[]>();\n\n  for (const cycle of cycles) {\n    const ring = cycleRing(g, cycle);\n    const area = signedArea(ring);\n    const component = componentOf.get(g.darts[cycle[0]].from) ?? '';\n\n    if (area > EPS_AREA) {\n      candidates.push({\n        face: {\n          id: faceId(g, cycle),\n          outer: ring,\n          holes: [],\n          area,\n          perimeter: ringPerimeter(ring),\n          wallIds: cycle.map((d) => g.darts[d].wallId),\n          nodeIds: cycle.map((d) => g.darts[d].from),\n        },\n        component,\n        outerArea: area,\n      });\n    } else if (area < -EPS_AREA) {\n      // This component's outer boundary. Kept at its negative winding, which is what\n      // `PlanFace.holes` promises: opposite to the parent's positively wound `outer`.\n      outerRings.set(component, ring);\n    }\n    // |area| <= EPS_AREA encloses nothing: a tree component or an isolated stub.\n  }\n\n  // Attach each component's outer boundary as a hole in the smallest face from a\n  // different component that encloses it. Smallest-containing-area is what makes\n  // arbitrary nesting depth work: with A > B > C, C's ring goes to B, never to A.\n  for (const [component, ring] of outerRings) {\n    const ringArea = Math.abs(signedArea(ring));\n    if (ringArea <= EPS_AREA) continue;\n    // An interior probe rather than a vertex, so two components that happen to touch\n    // do not produce an on-boundary coin flip.\n    const probe = representativePoint(ring);\n    let parent: Candidate | null = null;\n    for (const cand of candidates) {\n      if (cand.component === component) continue;\n      // The probe test alone is not sufficient: with one component nested inside\n      // another, the OUTER component's probe also falls inside the inner component's\n      // face, which would attach the big room as a hole of the small one. A face can\n      // only enclose a ring smaller than itself.\n      if (cand.outerArea <= ringArea) continue;\n      if (!pointInRing(probe, cand.face.outer)) continue;\n      if (!parent || cand.outerArea < parent.outerArea) parent = cand;\n    }\n    // No parent simply means this is a top-level component.\n    if (parent) parent.face.holes.push(ring);\n  }\n\n  for (const cand of candidates) {\n    let area = cand.outerArea;\n    for (const hole of cand.face.holes) area -= Math.abs(signedArea(hole));\n    cand.face.area = area;\n  }\n\n  return candidates.map((c) => c.face);\n}\n\n/**\n * Euler's formula per connected component: `V - E + F === 2C`, counting every cycle as\n * a face, including each component's own outer boundary.\n *\n * Note it is `2C`, not the `1 + C` of a single connected embedding. The dart walk\n * cannot bridge between disconnected components, so the region between two of them is\n * produced once per component rather than once in total.\n *\n * Exported for tests and dev assertions: it is the cheapest whole-graph check that the\n * dart permutation is intact.\n */\nexport function eulerCheck(floor: PlanFloor): {\n  v: number;\n  e: number;\n  f: number;\n  c: number;\n  holds: boolean;\n} {\n  const g = buildDartGraph(floor);\n  const componentOf = connectedComponents(g);\n  const v = g.outgoing.size;\n  const e = g.darts.length / 2;\n  const f = walkCycles(g).length;\n  const c = new Set(componentOf.values()).size;\n  return { v, e, f, c, holds: v - e + f === 2 * c };\n}\n\n/** Test/diagnostic helper: how many cycles each dart appears in. Must be all 1s. */\nexport function dartCycleCounts(floor: PlanFloor): number[] {\n  const g = buildDartGraph(floor);\n  const counts = new Array<number>(g.darts.length).fill(0);\n  for (const cycle of walkCycles(g)) for (const d of cycle) counts[d] += 1;\n  return counts;\n}\n\n/** Degree of each node, by id. Used by wall-path chaining and by validation. */\nexport function nodeDegrees(floor: PlanFloor): Map<string, number> {\n  const degrees = new Map<string, number>();\n  for (const w of usableWalls(floor)) {\n    degrees.set(w.a, (degrees.get(w.a) ?? 0) + 1);\n    degrees.set(w.b, (degrees.get(w.b) ?? 0) + 1);\n  }\n  return degrees;\n}\n\nexport { areaCentroid };\n","/**\n * Room identity: attaching stored metadata (name, colour) to derived faces.\n *\n * Identity is primarily the SET OF BOUNDARY WALL IDS, with the anchor point as a\n * secondary signal. That ordering is the important design decision here. Anchors alone\n * look sufficient but fail in three ways: a whole-plan drag leaves every absolute\n * anchor behind, a merge gives two anchors one face with no tie-break, and dragging a\n * node can move a boundary past its own anchor. Wall ids survive every operation that\n * only moves geometry, so reshaping and translating match perfectly without anyone\n * having to remember to transform anchors.\n */\n\nimport { deriveFaces } from './floorPlan.faces';\nimport { pointInRings, representativePoint } from './floorPlan.geometry';\nimport { freshName, newId } from './floorPlan.graph';\nimport { PlanIssueKind } from './floorPlan.types';\nimport type { DerivedRoom, PlanDerivation, PlanFace, PlanFloor, PlanIssue, PlanRoom } from './floorPlan.types';\n\n/** Relative weights of the three identity signals. */\nexport const MATCH_WEIGHTS = {\n  /** Wall ids survive every geometry-only edit, so they carry the most weight. */\n  boundary: 2,\n  /** Decides a split, where both children share half the old boundary. */\n  anchor: 1,\n  /** Breaks a merge towards the larger of the two rooms. */\n  area: 0.25,\n} as const;\n\n/** Below this, a pair is not the same room at all. */\nexport const MATCH_THRESHOLD = 0.15;\n\nfunction jaccard(a: ReadonlySet<string>, b: ReadonlySet<string>): number {\n  if (a.size === 0 && b.size === 0) return 0;\n  let intersection = 0;\n  for (const id of a) if (b.has(id)) intersection += 1;\n  const union = a.size + b.size - intersection;\n  return union === 0 ? 0 : intersection / union;\n}\n\nfunction scorePair(room: PlanRoom, face: PlanFace, faceWalls: ReadonlySet<string>): number {\n  const boundary = jaccard(new Set(room.wallIds), faceWalls);\n  const inside = pointInRings(room.anchor, face.outer, face.holes) ? 1 : 0;\n  const areaRatio =\n    room.area > 0 && face.area > 0 ? Math.min(room.area, face.area) / Math.max(room.area, face.area) : 0;\n  return MATCH_WEIGHTS.boundary * boundary + MATCH_WEIGHTS.anchor * inside + MATCH_WEIGHTS.area * areaRatio;\n}\n\n/**\n * Extracts faces and attaches stored room metadata to them.\n *\n * Pure: returns a new floor whose room match caches and anchors have been refreshed.\n * It never creates or deletes a `PlanRoom`, so it is safe to call on every render.\n * Minting rooms for newly enclosed faces is `commitDerivation`'s job, called once per\n * committed edit.\n */\nexport function derivePlan(floor: PlanFloor): PlanDerivation {\n  const faces = deriveFaces(floor);\n  const issues: PlanIssue[] = [];\n  const rooms = Object.values(floor.rooms);\n\n  const faceWallSets = faces.map((f) => new Set(f.wallIds));\n\n  interface Pair {\n    roomIndex: number;\n    faceIndex: number;\n    score: number;\n  }\n\n  const pairs: Pair[] = [];\n  for (let r = 0; r < rooms.length; r++) {\n    for (let f = 0; f < faces.length; f++) {\n      const score = scorePair(rooms[r], faces[f], faceWallSets[f]);\n      if (score > MATCH_THRESHOLD) pairs.push({ roomIndex: r, faceIndex: f, score });\n    }\n  }\n\n  // Greedy one-to-one. The seq/id tie-breaks keep the result deterministic, which\n  // matters because a merge is decided here: two rooms both score around 0.5 against\n  // the merged face, and exactly one of them must win.\n  pairs.sort((x, y) => {\n    if (y.score !== x.score) return y.score - x.score;\n    const seqDiff = rooms[x.roomIndex].seq - rooms[y.roomIndex].seq;\n    if (seqDiff !== 0) return seqDiff;\n    return faces[x.faceIndex].id < faces[y.faceIndex].id ? -1 : 1;\n  });\n\n  const roomTaken = new Array<boolean>(rooms.length).fill(false);\n  const faceToRoom = new Array<number>(faces.length).fill(-1);\n  for (const pair of pairs) {\n    if (roomTaken[pair.roomIndex]) continue;\n    if (faceToRoom[pair.faceIndex] !== -1) continue;\n    roomTaken[pair.roomIndex] = true;\n    faceToRoom[pair.faceIndex] = pair.roomIndex;\n  }\n\n  const nextRooms: Record<string, PlanRoom> = { ...floor.rooms };\n  const derived: DerivedRoom[] = faces.map((face, f) => {\n    const anchorFallback = representativePoint(face.outer, face.holes);\n    const roomIndex = faceToRoom[f];\n    if (roomIndex === -1) {\n      return { ...face, roomId: null, meta: null, anchor: anchorFallback };\n    }\n\n    const room = rooms[roomIndex];\n    const anchor = room.anchorPinned ? room.anchor : anchorFallback;\n    const updated: PlanRoom = {\n      ...room,\n      anchor,\n      // Refreshing the cache is what lets the next edit match against where the room is\n      // now rather than where it was when it was created.\n      wallIds: [...new Set(face.wallIds)],\n      area: face.area,\n    };\n    nextRooms[room.id] = updated;\n    return { ...face, roomId: room.id, meta: updated, anchor };\n  });\n\n  for (let r = 0; r < rooms.length; r++) {\n    if (roomTaken[r]) continue;\n    // Kept, never deleted. Re-adding a deleted wall re-splits the face and this room\n    // matches its child again by anchor, so the name comes back by itself. Dropping\n    // orphans is the one way this model loses data irrecoverably.\n    issues.push({\n      kind: PlanIssueKind.UnmatchedRoom,\n      message: `\"${rooms[r].name}\" is not currently enclosed by any walls.`,\n      roomIds: [rooms[r].id],\n    });\n  }\n\n  return { floor: { ...floor, rooms: nextRooms }, rooms: derived, issues };\n}\n\nlet roomSeq = 0;\n\n/** Test-only: makes the room sequence counter reproducible. */\nexport function __resetRoomSeq(): void {\n  roomSeq = 0;\n}\n\nexport interface CommitDerivationOptions {\n  /**\n   * Names a new room. `n` is the floor's room count plus one, moved past any number whose\n   * name is taken, so `` (n) => `Raum ${n}` `` numbers rooms the way the default does.\n   * Default `` (n) => `Room ${n}` ``.\n   */\n  nameFor?: (n: number, face: DerivedRoom) => string;\n}\n\nconst defaultRoomName = (n: number) => `Room ${n}`;\n\n/**\n * Mints a `PlanRoom` for every face that matched nothing.\n *\n * Separate from `derivePlan` so that derivation stays free of id minting and can run on\n * every render without inflating the document. Call it once per committed edit.\n */\nexport function commitDerivation(\n  floor: PlanFloor,\n  options: CommitDerivationOptions = {},\n): { floor: PlanFloor; created: PlanRoom[] } {\n  const { rooms: derived, floor: refreshed } = derivePlan(floor);\n  const nextRooms = { ...refreshed.rooms };\n  const created: PlanRoom[] = [];\n\n  const nameFor = options.nameFor ?? defaultRoomName;\n  const existingNames = new Set(Object.values(nextRooms).map((r) => r.name));\n\n  for (const face of derived) {\n    if (face.roomId) continue;\n    roomSeq += 1;\n\n    const name = freshName((n) => nameFor(n, face), Object.keys(nextRooms).length + 1, existingNames);\n    existingNames.add(name);\n\n    const room: PlanRoom = {\n      id: newId('r'),\n      name,\n      anchor: face.anchor,\n      anchorPinned: false,\n      seq: roomSeq,\n      wallIds: [...new Set(face.wallIds)],\n      area: face.area,\n    };\n    nextRooms[room.id] = room;\n    created.push(room);\n  }\n\n  return { floor: { ...refreshed, rooms: nextRooms }, created };\n}\n\n/** Removes room metadata that matched no face. Never called automatically. */\nexport function pruneUnmatchedRooms(floor: PlanFloor): { floor: PlanFloor; removed: string[] } {\n  const { rooms: derived } = derivePlan(floor);\n  const matched = new Set(derived.map((d) => d.roomId).filter(Boolean) as string[]);\n  const nextRooms: Record<string, PlanRoom> = {};\n  const removed: string[] = [];\n  for (const room of Object.values(floor.rooms)) {\n    if (matched.has(room.id)) nextRooms[room.id] = room;\n    else removed.push(room.id);\n  }\n  return { floor: { ...floor, rooms: nextRooms }, removed };\n}\n\n/** Convenience: the derived room whose polygon contains `point`, if any. */\nexport function roomAtPoint(derived: DerivedRoom[], point: { x: number; y: number }): DerivedRoom | null {\n  let best: DerivedRoom | null = null;\n  for (const room of derived) {\n    if (!pointInRings(point, room.outer, room.holes)) continue;\n    // Smallest wins, so a room nested inside another is selectable.\n    if (!best || room.area < best.area) best = room;\n  }\n  return best;\n}\n","/**\n * Turns walls into SVG path data for the two-pass thick-wall rendering.\n *\n * The trick: both passes stroke the SAME path data, the outline pass at `thickness` and\n * the fill pass at `thickness - 2 * outlineWidth`. The visible outline is not drawn at\n * all, it is what is left of the outline pass after the fill pass covers its middle.\n * That is why the two must never diverge, and why every outline path has to be drawn\n * before every fill path (one `<g>` each, never interleaved pairs).\n *\n * Three things do not fall out of that trick and are handled explicitly:\n *\n * 1. Nothing is ever drawn across a butt end, because there both passes terminate at\n *    the same coordinate. Openings and free ends therefore need explicit cap lines.\n * 2. Two butt-capped walls meeting at an angle leave an uncovered notch at the outer\n *    corner. Walls are emitted as CHAINS through degree-2 nodes so `stroke-linejoin`\n *    miters the corner, and where a chain has to stop, `reflexCornerStubs` mitres the\n *    one corner a junction cannot close by itself.\n * 3. Where two walls of different thickness meet end to end, the drawing steps from\n *    thick fill straight to background with no edge, so that joint needs a cap too.\n */\n\nimport { nodeDegrees } from './floorPlan.faces';\nimport { dist } from './floorPlan.geometry';\nimport type { PlanFloor, PlanOpening, PlanPoint, PlanWall } from './floorPlan.types';\n\nexport interface WallPathGroup {\n  /** Millimetres. The stroke width for the outline pass. */\n  thickness: number;\n  /**\n   * Path data, to be handed VERBATIM to both passes. The outline pass strokes it at\n   * `thickness`, the fill pass at `thickness - 2 * outlineWidth`.\n   */\n  d: string;\n}\n\nexport type WallCapReason = 'jamb' | 'freeEnd' | 'thicknessStep';\n\nexport interface WallCapLine {\n  a: PlanPoint;\n  b: PlanPoint;\n  reason: WallCapReason;\n  wallId: string;\n}\n\nexport interface WallPathResult {\n  /** Render every group's outline first, then every group's fill. */\n  groups: WallPathGroup[];\n  /** Drawn last, in the outline colour, at the outline width. */\n  caps: WallCapLine[];\n}\n\nexport interface BuildWallPathsOptions {\n  /**\n   * Build paths for only these walls, as if the rest were not drawn.\n   *\n   * Node degrees still come from the WHOLE floor, so chains break and corners are filled\n   * exactly where they are for the real walls. That is what lets a subset be painted over\n   * the top of the full drawing and land on it precisely: a highlight of a room's\n   * boundary is the same geometry as the walls themselves, openings and all.\n   */\n  wallIds?: ReadonlySet<string>;\n  /** Cap the free end of a wall that stops in mid-air. Default true. */\n  capFreeEnds?: boolean;\n  /** Cap the joint where two walls of different thickness meet. Default true. */\n  capThicknessSteps?: boolean;\n}\n\nconst EPS = 1e-6;\n\nfunction lerpPoint(a: PlanPoint, b: PlanPoint, t: number): PlanPoint {\n  return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };\n}\n\nfunction fmt(n: number): string {\n  return Number.isInteger(n) ? String(n) : n.toFixed(3).replace(/\\.?0+$/, '');\n}\n\nfunction samePoint(a: PlanPoint, b: PlanPoint): boolean {\n  return Math.abs(a.x - b.x) < 0.001 && Math.abs(a.y - b.y) < 0.001;\n}\n\n/**\n * The parts of a wall's centreline that are actually wall, as `[start, end]` offsets in\n * millimetres from node `a`.\n *\n * Overlapping openings merge into one gap; an opening reaching an end removes that span\n * entirely. An empty result means the opening spans the whole wall.\n */\nexport function wallCentrelineSpans(floor: PlanFloor, wall: PlanWall): Array<[number, number]> {\n  const a = floor.nodes[wall.a];\n  const b = floor.nodes[wall.b];\n  if (!a || !b) return [];\n  const len = dist(a, b);\n  if (len < EPS) return [];\n\n  const gaps: Array<[number, number]> = [];\n  for (const opening of Object.values(floor.openings)) {\n    if (opening.wallId !== wall.id) continue;\n    const half = opening.width / 2;\n    const start = Math.max(0, opening.offset - half);\n    const end = Math.min(len, opening.offset + half);\n    if (end > start) gaps.push([start, end]);\n  }\n  if (gaps.length === 0) return [[0, len]];\n\n  gaps.sort((x, y) => x[0] - y[0]);\n  const merged: Array<[number, number]> = [gaps[0]];\n  for (let i = 1; i < gaps.length; i++) {\n    const last = merged[merged.length - 1];\n    if (gaps[i][0] <= last[1]) last[1] = Math.max(last[1], gaps[i][1]);\n    else merged.push(gaps[i]);\n  }\n\n  const spans: Array<[number, number]> = [];\n  let cursor = 0;\n  for (const [start, end] of merged) {\n    if (start - cursor > EPS) spans.push([cursor, start]);\n    cursor = end;\n  }\n  if (len - cursor > EPS) spans.push([cursor, len]);\n  return spans;\n}\n\n/** Everything a renderer needs to draw a door leaf, swing arc or window symbol. */\nexport function openingGeometry(\n  floor: PlanFloor,\n  opening: PlanOpening,\n): { centre: PlanPoint; dir: PlanPoint; normal: PlanPoint; p1: PlanPoint; p2: PlanPoint } | null {\n  const wall = floor.walls[opening.wallId];\n  if (!wall) return null;\n  const a = floor.nodes[wall.a];\n  const b = floor.nodes[wall.b];\n  if (!a || !b) return null;\n  const len = dist(a, b);\n  if (len < EPS) return null;\n\n  const dir = { x: (b.x - a.x) / len, y: (b.y - a.y) / len };\n  const normal = { x: -dir.y, y: dir.x };\n  const half = Math.min(opening.width / 2, len / 2);\n  const offset = Math.min(Math.max(opening.offset, half), len - half);\n  const centre = { x: a.x + dir.x * offset, y: a.y + dir.y * offset };\n\n  return {\n    centre,\n    dir,\n    normal,\n    p1: { x: centre.x - dir.x * half, y: centre.y - dir.y * half },\n    p2: { x: centre.x + dir.x * half, y: centre.y + dir.y * half },\n  };\n}\n\n/** One directed step along a chain. */\ninterface Step {\n  wall: PlanWall;\n  from: string;\n  to: string;\n}\n\n/**\n * Maximal runs of same-thickness walls joined through degree-2 nodes.\n *\n * A chain may only continue through a node whose TOTAL degree is 2 and whose two walls\n * share a thickness: a different stroke width means a different path, and a degree-3+\n * node is a junction rather than a corner.\n */\nfunction buildChains(floor: PlanFloor, walls: PlanWall[], degrees: Map<string, number>): Step[][] {\n  const byId = new Map(walls.map((w) => [w.id, w]));\n  const atNode = new Map<string, PlanWall[]>();\n  for (const wall of walls) {\n    for (const nodeId of [wall.a, wall.b]) {\n      const list = atNode.get(nodeId);\n      if (list) list.push(wall);\n      else atNode.set(nodeId, [wall]);\n    }\n  }\n\n  const continuesThrough = (nodeId: string): PlanWall[] | null => {\n    if ((degrees.get(nodeId) ?? 0) !== 2) return null;\n    const here = atNode.get(nodeId);\n    // Both walls must be in this thickness group, or the chain has to break here.\n    if (!here || here.length !== 2) return null;\n    return here;\n  };\n\n  const used = new Set<string>();\n  const chains: Step[][] = [];\n\n  const other = (wall: PlanWall, nodeId: string): string => (wall.a === nodeId ? wall.b : wall.a);\n\n  for (const wall of walls) {\n    if (used.has(wall.id)) continue;\n\n    // Walk backwards to the start of the run, unless it closes on itself.\n    let startWall = wall;\n    let startNode = wall.a;\n    const seenBack = new Set<string>([wall.id]);\n    for (;;) {\n      const pair = continuesThrough(startNode);\n      if (!pair) break;\n      const prev = pair[0].id === startWall.id ? pair[1] : pair[0];\n      if (seenBack.has(prev.id)) break; // closed loop\n      seenBack.add(prev.id);\n      startWall = prev;\n      startNode = other(prev, startNode);\n    }\n\n    // Walk forwards, collecting directed steps.\n    const steps: Step[] = [];\n    let current = startWall;\n    let from = startNode;\n    for (;;) {\n      if (used.has(current.id)) break;\n      used.add(current.id);\n      const to = other(current, from);\n      steps.push({ wall: current, from, to });\n      const pair = continuesThrough(to);\n      if (!pair) break;\n      const next = pair[0].id === current.id ? pair[1] : pair[0];\n      if (used.has(next.id)) break;\n      current = next;\n      from = to;\n      if (!byId.has(current.id)) break;\n    }\n\n    if (steps.length > 0) chains.push(steps);\n  }\n\n  return chains;\n}\n\n/**\n * The outer corner of the one REFLEX gap at a node, as a two-legged stub.\n *\n * A chain's `stroke-linejoin` mitres the corner wherever a wall bends, but a chain has\n * to stop at a junction, and at a thickness change, so something else has to close the\n * corner there. Most junction corners need nothing: where the angular gap between two\n * neighbouring walls is at most 180 degrees, the two bands reach across it and already\n * meet at the mitre point, because both start AT the node rather than short of it.\n *\n * A gap WIDER than 180 degrees is the exception, and it is the only one. There the two\n * faces bounding the gap diverge instead of crossing, so nothing spans the gap and what\n * is left between the two butt ends is an open wedge biting into the node. It shows up\n * whenever every wall at a node points into the same half-plane: a wall running east, a\n * wall running north, and a third splitting the two leaves a quarter-circle bitten out\n * of the south-west side. Because the gaps around a node sum to 360 degrees, at most\n * one can be reflex, so a node never needs more than one of these.\n *\n * The stub is two legs laid back along the two walls bounding that gap. Stroked at the\n * wall thickness it stays within their own bands, adding nothing anywhere else, and its\n * mitre join fills the wedge and bevels under the same `stroke-miterlimit` as every\n * other corner. Filling every gap this way instead of only the reflex one is what used\n * to fire spikes out of narrow junctions: those gaps had nothing to fill, so the join\n * ran outside the walls, the further the tighter the angle.\n */\nfunction reflexCornerStubs(\n  floor: PlanFloor,\n  walls: PlanWall[],\n  degrees: Map<string, number>,\n): Array<{ thickness: number; d: string }> {\n  interface Arm {\n    wall: PlanWall;\n    dir: PlanPoint;\n    angle: number;\n    /** Millimetres of wall between the node and the first opening, if any. */\n    reach: number;\n  }\n\n  const armsAt = new Map<string, Arm[]>();\n  const addArm = (nodeId: string, arm: Arm) => {\n    const list = armsAt.get(nodeId);\n    if (list) list.push(arm);\n    else armsAt.set(nodeId, [arm]);\n  };\n\n  for (const wall of walls) {\n    const a = floor.nodes[wall.a];\n    const b = floor.nodes[wall.b];\n    const len = dist(a, b);\n    if (len < EPS) continue;\n    const spans = wallCentrelineSpans(floor, wall);\n    if (spans.length === 0) continue;\n\n    const dir = { x: (b.x - a.x) / len, y: (b.y - a.y) / len };\n    const first = spans[0];\n    const last = spans[spans.length - 1];\n    addArm(wall.a, {\n      wall,\n      dir,\n      angle: Math.atan2(dir.y, dir.x),\n      reach: first[0] < EPS ? first[1] : 0,\n    });\n    addArm(wall.b, {\n      wall,\n      dir: { x: -dir.x, y: -dir.y },\n      angle: Math.atan2(-dir.y, -dir.x),\n      reach: last[1] > len - EPS ? len - last[0] : 0,\n    });\n  }\n\n  const stubs: Array<{ thickness: number; d: string }> = [];\n  const TAU = Math.PI * 2;\n\n  for (const [nodeId, arms] of armsAt) {\n    if (arms.length < 2) continue;\n    /*\n     * A chain runs straight through a plain corner, and its own join closes it. The test\n     * has to be the one `buildChains` uses, which reads the degree of the node in the\n     * WHOLE floor: drawing a subset of the walls, two arms at a T junction look like a\n     * corner but the chain still breaks there, and skipping the fill left a notch bitten\n     * out of the join.\n     */\n    const chainRunsThrough =\n      (degrees.get(nodeId) ?? 0) === 2 && arms.length === 2 && arms[0].wall.thickness === arms[1].wall.thickness;\n    if (chainRunsThrough) continue;\n\n    arms.sort((p, q) => p.angle - q.angle);\n\n    let widest = 0;\n    let widestGap = -1;\n    for (let i = 0; i < arms.length; i++) {\n      const next = arms[(i + 1) % arms.length];\n      const gap = i === arms.length - 1 ? next.angle + TAU - arms[i].angle : next.angle - arms[i].angle;\n      if (gap > widestGap) {\n        widestGap = gap;\n        widest = i;\n      }\n    }\n    if (widestGap <= Math.PI + EPS) continue;\n\n    const from = arms[widest];\n    const to = arms[(widest + 1) % arms.length];\n    // The thinner of the two, so the stub can never bulge out past the thinner wall.\n    const thickness = Math.min(from.wall.thickness, to.wall.thickness);\n    // Half a thickness of leg is enough to carry the join, and an opening close to the\n    // node shortens it so the stub cannot paint across the jamb.\n    const legFrom = Math.min(thickness / 2, from.reach);\n    const legTo = Math.min(thickness / 2, to.reach);\n    if (legFrom < EPS || legTo < EPS) continue;\n\n    const node = floor.nodes[nodeId];\n    stubs.push({\n      thickness,\n      d:\n        `M ${fmt(node.x + from.dir.x * legFrom)},${fmt(node.y + from.dir.y * legFrom)}` +\n        ` L ${fmt(node.x)},${fmt(node.y)}` +\n        ` L ${fmt(node.x + to.dir.x * legTo)},${fmt(node.y + to.dir.y * legTo)}`,\n    });\n  }\n\n  return stubs;\n}\n\n/**\n * Builds the stroke path data for every wall, grouped by thickness, plus the cap lines\n * the two-pass trick cannot produce on its own.\n */\nexport function buildWallPaths(floor: PlanFloor, options: BuildWallPathsOptions = {}): WallPathResult {\n  const { wallIds, capFreeEnds = true, capThicknessSteps = true } = options;\n\n  const walls = Object.values(floor.walls).filter((w) => {\n    if (wallIds && !wallIds.has(w.id)) return false;\n    const a = floor.nodes[w.a];\n    const b = floor.nodes[w.b];\n    return a && b && dist(a, b) > EPS && w.a !== w.b;\n  });\n  if (walls.length === 0) return { groups: [], caps: [] };\n\n  const degrees = nodeDegrees(floor);\n  const caps: WallCapLine[] = [];\n\n  // ── Group by thickness: one stroke width per path. ────────────────────────────\n  const byThickness = new Map<number, PlanWall[]>();\n  for (const wall of walls) {\n    const list = byThickness.get(wall.thickness);\n    if (list) list.push(wall);\n    else byThickness.set(wall.thickness, [wall]);\n  }\n\n  const subpathsByThickness = new Map<number, string[]>();\n  const pushSubpath = (thickness: number, d: string) => {\n    const list = subpathsByThickness.get(thickness);\n    if (list) list.push(d);\n    else subpathsByThickness.set(thickness, [d]);\n  };\n\n  for (const [thickness, groupWalls] of byThickness) {\n    for (const chain of buildChains(floor, groupWalls, degrees)) {\n      let current: PlanPoint[] = [];\n      const flush = () => {\n        if (current.length >= 2) {\n          pushSubpath(thickness, current.map((p, i) => `${i === 0 ? 'M' : 'L'} ${fmt(p.x)},${fmt(p.y)}`).join(' '));\n        }\n        current = [];\n      };\n\n      for (const step of chain) {\n        const a = floor.nodes[step.from];\n        const b = floor.nodes[step.to];\n        const len = dist(a, b);\n        if (len < EPS) continue;\n\n        // Spans come back in the wall's own a->b direction; flip them when the chain\n        // traverses the wall the other way.\n        let spans = wallCentrelineSpans(floor, step.wall);\n        if (step.from !== step.wall.a) {\n          spans = spans.map(([s, e]) => [len - e, len - s] as [number, number]).reverse();\n        }\n\n        for (const [s, e] of spans) {\n          const p1 = lerpPoint(a, b, s / len);\n          const p2 = lerpPoint(a, b, e / len);\n          if (current.length > 0 && samePoint(current[current.length - 1], p1)) {\n            current.push(p2);\n          } else {\n            flush();\n            current = [p1, p2];\n          }\n        }\n        // An opening reaching the far end leaves nothing to continue from.\n        if (spans.length === 0 || spans[spans.length - 1][1] < len - EPS) flush();\n      }\n\n      // A chain that returns to where it started closes properly, so the final corner\n      // gets a miter join like every other corner rather than two butt caps.\n      const first = chain[0];\n      const last = chain[chain.length - 1];\n      const closed = first.from === last.to;\n      if (closed && current.length >= 3 && samePoint(current[0], current[current.length - 1])) {\n        const ring = current.slice(0, -1);\n        pushSubpath(thickness, `${ring.map((p, i) => `${i === 0 ? 'M' : 'L'} ${fmt(p.x)},${fmt(p.y)}`).join(' ')} Z`);\n        current = [];\n      } else {\n        flush();\n      }\n    }\n  }\n\n  for (const stub of reflexCornerStubs(floor, walls, degrees)) pushSubpath(stub.thickness, stub.d);\n\n  // ── Caps. ─────────────────────────────────────────────────────────────────────\n  const capAt = (wall: PlanWall, offset: number, reason: WallCapReason) => {\n    const a = floor.nodes[wall.a];\n    const b = floor.nodes[wall.b];\n    const len = dist(a, b);\n    if (len < EPS) return;\n    const dir = { x: (b.x - a.x) / len, y: (b.y - a.y) / len };\n    const normal = { x: -dir.y, y: dir.x };\n    const centre = { x: a.x + dir.x * offset, y: a.y + dir.y * offset };\n    const half = wall.thickness / 2;\n    caps.push({\n      a: { x: centre.x - normal.x * half, y: centre.y - normal.y * half },\n      b: { x: centre.x + normal.x * half, y: centre.y + normal.y * half },\n      reason,\n      wallId: wall.id,\n    });\n  };\n\n  for (const wall of walls) {\n    const a = floor.nodes[wall.a];\n    const b = floor.nodes[wall.b];\n    const len = dist(a, b);\n    for (const [s, e] of wallCentrelineSpans(floor, wall)) {\n      if (s > EPS) capAt(wall, s, 'jamb');\n      else if (capFreeEnds && (degrees.get(wall.a) ?? 0) === 1) capAt(wall, 0, 'freeEnd');\n\n      if (e < len - EPS) capAt(wall, e, 'jamb');\n      else if (capFreeEnds && (degrees.get(wall.b) ?? 0) === 1) capAt(wall, len, 'freeEnd');\n    }\n  }\n\n  if (capThicknessSteps) {\n    const atNode = new Map<string, PlanWall[]>();\n    for (const wall of walls) {\n      for (const nodeId of [wall.a, wall.b]) {\n        const list = atNode.get(nodeId);\n        if (list) list.push(wall);\n        else atNode.set(nodeId, [wall]);\n      }\n    }\n    for (const [nodeId, incident] of atNode) {\n      if (incident.length !== 2) continue;\n      const [w1, w2] = incident;\n      if (w1.thickness === w2.thickness) continue;\n      // Draw the thicker wall's full cross-section. It overdraws a line across the\n      // thinner wall's interior, which is the usual CAD convention for a joint between\n      // two wall constructions, and far better than leaving no edge at all.\n      const thicker = w1.thickness >= w2.thickness ? w1 : w2;\n      const offset = thicker.a === nodeId ? 0 : dist(floor.nodes[thicker.a], floor.nodes[thicker.b]);\n      capAt(thicker, offset, 'thicknessStep');\n    }\n  }\n\n  const groups: WallPathGroup[] = [...subpathsByThickness.entries()]\n    .sort((x, y) => x[0] - y[0])\n    .map(([thickness, subpaths]) => ({ thickness, d: subpaths.join(' ') }));\n\n  return { groups, caps };\n}\n","/**\n * Hit testing against a derived plan.\n *\n * Tolerances arrive in WORLD units and are expected to have been converted from screen\n * pixels by the caller (`HIT_PX / view.zoom`), so a handle stays the same size on screen\n * at any zoom level.\n */\n\nimport { dist, pointToSegment } from './floorPlan.geometry';\nimport { openingGeometry } from './floorPlan.path';\nimport { roomAtPoint } from './floorPlan.rooms';\nimport type { DerivedRoom, PlanFloor, PlanPoint, PlanSelection } from './floorPlan.types';\n\nexport interface HitTolerances {\n  node: number;\n  opening: number;\n  wall: number;\n}\n\n/** Screen-pixel hit radii, divided by `view.zoom` to get world tolerances. */\nexport const HIT_PX: HitTolerances = { node: 10, opening: 9, wall: 8 };\n/** Wider targets for touch, where there is no pixel-accurate pointer. */\nexport const HIT_PX_COARSE: HitTolerances = { node: 18, opening: 16, wall: 14 };\n\nexport function worldTolerances(px: HitTolerances, zoom: number): HitTolerances {\n  const safeZoom = zoom > 0 ? zoom : 1;\n  return { node: px.node / safeZoom, opening: px.opening / safeZoom, wall: px.wall / safeZoom };\n}\n\nexport interface HitTestOptions {\n  /** Restricts what can be hit. Defaults to everything. */\n  accept?: Array<PlanSelection['type']>;\n  excludeIds?: ReadonlySet<string>;\n}\n\n/**\n * Finds what is under `point`, in priority order: node handle, opening, wall, then room\n * fill.\n *\n * The order is what makes editing feel right. A node sits on top of the walls that meet\n * there, and an opening sits on top of the wall it cuts, so the smaller, more specific\n * target always wins over the larger one underneath it.\n */\nexport function hitTest(\n  floor: PlanFloor,\n  rooms: DerivedRoom[],\n  point: PlanPoint,\n  tolerances: HitTolerances,\n  options: HitTestOptions = {},\n): PlanSelection | null {\n  const accepts = (type: PlanSelection['type']) => !options.accept || options.accept.includes(type);\n  const excluded = (id: string) => options.excludeIds?.has(id) ?? false;\n\n  if (accepts('node')) {\n    let best: { id: string; d: number } | null = null;\n    for (const node of Object.values(floor.nodes)) {\n      if (excluded(node.id)) continue;\n      const d = dist(point, node);\n      if (d <= tolerances.node && (!best || d < best.d)) best = { id: node.id, d };\n    }\n    if (best) return { type: 'node', id: best.id };\n  }\n\n  if (accepts('opening')) {\n    let best: { id: string; d: number } | null = null;\n    for (const opening of Object.values(floor.openings)) {\n      if (excluded(opening.id)) continue;\n      const geo = openingGeometry(floor, opening);\n      if (!geo) continue;\n      const d = pointToSegment(point, geo.p1, geo.p2).dist;\n      if (d <= tolerances.opening && (!best || d < best.d)) best = { id: opening.id, d };\n    }\n    if (best) return { type: 'opening', id: best.id };\n  }\n\n  if (accepts('wall')) {\n    let best: { id: string; d: number } | null = null;\n    for (const wall of Object.values(floor.walls)) {\n      if (excluded(wall.id)) continue;\n      const a = floor.nodes[wall.a];\n      const b = floor.nodes[wall.b];\n      if (!a || !b) continue;\n      // Half the wall's own thickness, so a thick wall is grabbable across its body\n      // rather than only along its centreline.\n      const reach = Math.max(tolerances.wall, wall.thickness / 2);\n      const d = pointToSegment(point, a, b).dist;\n      if (d <= reach && (!best || d < best.d)) best = { id: wall.id, d };\n    }\n    if (best) return { type: 'wall', id: best.id };\n  }\n\n  if (accepts('room')) {\n    const room = roomAtPoint(rooms, point);\n    if (room?.roomId && !excluded(room.roomId)) return { type: 'room', id: room.roomId };\n  }\n\n  return null;\n}\n\nexport function selectionsEqual(a: PlanSelection | null, b: PlanSelection | null): boolean {\n  if (a === b) return true;\n  if (!a || !b) return false;\n  return a.type === b.type && a.id === b.id;\n}\n","/**\n * Snapping, and the screen-to-world conversion it depends on.\n *\n * Two things here are deliberately different from the previous implementation:\n *\n * 1. Conversion and snapping are separate. `clientToWorld` only converts; `resolveSnap`\n *    is the only thing that decides. The old `clientToSVG` called `snapPt` internally,\n *    which made grid snap unconditional and left no way to hold Alt to disable it.\n *\n * 2. Every tolerance is in SCREEN PIXELS, converted through the current zoom at the\n *    point of use. A fixed world-unit tolerance (the old `WALL_TOLERANCE = 8`) means\n *    snapping covers a smaller and smaller part of the screen as you zoom in, until it\n *    stops working.\n */\n\nimport { dist, pointToSegment, projectOnSegment, segmentIntersection } from './floorPlan.geometry';\nimport type { PlanFloor, PlanPoint } from './floorPlan.types';\n\nexport enum SnapKind {\n  None = 'none',\n  Node = 'node',\n  /** A point projected onto a wall's centreline. */\n  Wall = 'wall',\n  /** The crossing of two walls, or of a wall with the active angle ray. */\n  Intersection = 'intersection',\n  /** On the infinite line continuing an existing wall. */\n  Extension = 'extension',\n  /** Shares an x or y coordinate with an existing node. */\n  Alignment = 'alignment',\n  /** On a ray at a multiple of `angleStepDeg` from `origin`. */\n  Angle = 'angle',\n  Grid = 'grid',\n}\n\nexport interface SnapGuide {\n  kind: SnapKind;\n  a: PlanPoint;\n  b: PlanPoint;\n  /** Render as an unbounded construction line rather than a segment. */\n  infinite?: boolean;\n}\n\nexport interface SnapOptions {\n  /** Which snaps are active. Pass `[]` (Alt held) to disable snapping entirely. */\n  enabled: SnapKind[];\n  /** Millimetres. 0 disables grid snap. */\n  gridSize: number;\n  /** Degrees. 45 gives 0/45/90. 0 disables angle lock. */\n  angleStepDeg: number;\n  toleranceScreenPx: number;\n  /** Nodes are the most valuable snap, so they get a little more room. */\n  nodeToleranceScreenPx?: number;\n}\n\nexport const DEFAULT_SNAP_OPTIONS: SnapOptions = {\n  enabled: [\n    SnapKind.Node,\n    SnapKind.Intersection,\n    SnapKind.Wall,\n    SnapKind.Angle,\n    SnapKind.Extension,\n    SnapKind.Alignment,\n    SnapKind.Grid,\n  ],\n  gridSize: 100,\n  angleStepDeg: 0,\n  toleranceScreenPx: 10,\n  nodeToleranceScreenPx: 12,\n};\n\nexport interface SnapContext {\n  floor: PlanFloor;\n  /** World units per screen pixel at the current zoom: `1 / view.zoom`. */\n  pxToWorld: number;\n  /** The anchored point. Required for angle lock; also enables the length readout. */\n  origin?: PlanPoint;\n  /**\n   * A direction in radians that angle lock measures from, in ADDITION to the world axes.\n   *\n   * Pass the direction of the wall being continued and the lock offers perpendicular and\n   * parallel to that wall, not just to the world. Without it, angle lock is only useful\n   * on a building that happens to be aligned to the page.\n   */\n  referenceAngle?: number;\n  excludeWallIds?: ReadonlySet<string>;\n  excludeNodeIds?: ReadonlySet<string>;\n  options?: Partial<SnapOptions>;\n}\n\nexport interface SnapResult {\n  point: PlanPoint;\n  kind: SnapKind;\n  nodeId?: string;\n  wallId?: string;\n  /** Parameter along `wallId`, for `SnapKind.Wall`. */\n  t?: number;\n  /** Everything an overlay needs to draw to explain the snap. */\n  guides: SnapGuide[];\n  /** Radians from `origin` to `point`, when an origin was given. */\n  angle?: number;\n  /** Distance from `origin` to `point`, when an origin was given. */\n  distance?: number;\n  /**\n   * Set when angle lock produced the result, saying what it locked against: the world\n   * axes, or the reference direction that was passed in.\n   */\n  angleRelativeTo?: 'world' | 'reference';\n}\n\n/** An infinite line as a point and a unit direction. */\ninterface Line {\n  kind: SnapKind;\n  point: PlanPoint;\n  dir: PlanPoint;\n  residual: number;\n  /** Angle lock only: which basis the ray was measured from. */\n  relativeTo?: 'world' | 'reference';\n}\n\n/**\n * Client (screen) coordinates to world coordinates. Pure conversion, no snapping.\n *\n * Uses `getBoundingClientRect` arithmetic against an explicit viewport rather than\n * `getScreenCTM`, which returns `null` in jsdom and so makes anything built on it\n * untestable.\n */\nexport function clientToWorld(\n  clientX: number,\n  clientY: number,\n  rect: { left: number; top: number },\n  view: { x: number; y: number; zoom: number },\n): PlanPoint {\n  return {\n    x: view.x + (clientX - rect.left) / view.zoom,\n    y: view.y + (clientY - rect.top) / view.zoom,\n  };\n}\n\nfunction perpendicularDistanceToLine(p: PlanPoint, line: { point: PlanPoint; dir: PlanPoint }): number {\n  const vx = p.x - line.point.x;\n  const vy = p.y - line.point.y;\n  return Math.abs(vx * line.dir.y - vy * line.dir.x);\n}\n\nfunction projectOnLine(p: PlanPoint, line: { point: PlanPoint; dir: PlanPoint }): PlanPoint {\n  const vx = p.x - line.point.x;\n  const vy = p.y - line.point.y;\n  const along = vx * line.dir.x + vy * line.dir.y;\n  return { x: line.point.x + line.dir.x * along, y: line.point.y + line.dir.y * along };\n}\n\nfunction intersectLines(l1: Line, l2: Line): PlanPoint | null {\n  const denom = l1.dir.x * l2.dir.y - l1.dir.y * l2.dir.x;\n  if (Math.abs(denom) < 1e-9) return null;\n  const dx = l2.point.x - l1.point.x;\n  const dy = l2.point.y - l1.point.y;\n  const t = (dx * l2.dir.y - dy * l2.dir.x) / denom;\n  return { x: l1.point.x + l1.dir.x * t, y: l1.point.y + l1.dir.y * t };\n}\n\nfunction angleBetween(l1: Line, l2: Line): number {\n  const dot = Math.abs(l1.dir.x * l2.dir.x + l1.dir.y * l2.dir.y);\n  return Math.acos(Math.min(1, dot));\n}\n\nfunction roundTo(value: number, step: number): number {\n  return step > 0 ? Math.round(value / step) * step : value;\n}\n\n/**\n * Resolves a raw pointer position to the point the user meant.\n *\n * Two stages, because half of these snaps are constraints rather than points. A hard\n * snap (node, intersection, wall) fixes both degrees of freedom, so the first match\n * wins. A constraint (angle lock, extension, alignment) removes only one, so a flat\n * \"first match wins\" list would kick the point off the angle ray the moment an\n * alignment also matched. Two constraints intersect; one constraint is projected onto\n * and then slid ALONG to the nearest grid position, which is what lets angle lock and\n * grid coexist instead of fighting.\n */\nexport function resolveSnap(raw: PlanPoint, ctx: SnapContext): SnapResult {\n  const options: SnapOptions = { ...DEFAULT_SNAP_OPTIONS, ...ctx.options };\n  const enabled = new Set(options.enabled);\n\n  /**\n   * Measures the resolved point back against the origin, so every branch reports the\n   * same angle and distance without each one having to compute them. Taking them from\n   * the final point rather than from whichever branch produced it means they always\n   * describe what the user actually gets.\n   */\n  const finish = (result: SnapResult): SnapResult => {\n    if (!ctx.origin) return result;\n    const dx = result.point.x - ctx.origin.x;\n    const dy = result.point.y - ctx.origin.y;\n    return { ...result, angle: Math.atan2(dy, dx), distance: Math.hypot(dx, dy) };\n  };\n\n  if (enabled.size === 0) return finish({ point: raw, kind: SnapKind.None, guides: [] });\n\n  const tol = options.toleranceScreenPx * ctx.pxToWorld;\n  const nodeTol = (options.nodeToleranceScreenPx ?? options.toleranceScreenPx) * ctx.pxToWorld;\n  const { floor } = ctx;\n\n  /**\n   * Angle lock, when the caller has asked for it, is a HARD constraint rather than\n   * another proximity snap: `angleStepDeg > 0` only happens because the user latched the\n   * lock or held the modifier. Gating it by tolerance like the ambient snaps would mean\n   * it silently stopped applying as soon as you dragged more than a few pixels off the\n   * ray, which is the opposite of what a lock is for.\n   */\n  const lockedRay: Line | null = (() => {\n    if (!enabled.has(SnapKind.Angle) || !ctx.origin || options.angleStepDeg <= 0) return null;\n    const step = (options.angleStepDeg * Math.PI) / 180;\n    const rawAngle = Math.atan2(raw.y - ctx.origin.y, raw.x - ctx.origin.x);\n\n    // Measure from the world axes, and also from the reference direction when one was\n    // given, so \"square to the wall I am continuing\" is available as well as \"square to\n    // the page\". Whichever ray the pointer is closest to wins.\n    const bases: Array<{ angle: number; relativeTo: 'world' | 'reference' }> = [{ angle: 0, relativeTo: 'world' }];\n    if (ctx.referenceAngle !== undefined) {\n      bases.push({ angle: ctx.referenceAngle, relativeTo: 'reference' });\n    }\n\n    let best: Line | null = null;\n    for (const base of bases) {\n      const snapped = base.angle + Math.round((rawAngle - base.angle) / step) * step;\n      const dir = { x: Math.cos(snapped), y: Math.sin(snapped) };\n      const line: Line = {\n        kind: SnapKind.Angle,\n        point: ctx.origin,\n        dir,\n        residual: 0,\n        relativeTo: base.relativeTo,\n      };\n      line.residual = perpendicularDistanceToLine(raw, line);\n      if (!best || line.residual < best.residual) best = line;\n    }\n    return best;\n  })();\n\n  /**\n   * While the lock is on, a hard snap is only allowed to win if it sits ON the ray.\n   * Otherwise \"hold the angle\" would be quietly overridden by any nearby corner, which\n   * is exactly the case the lock exists to prevent.\n   */\n  const allowedByLock = (p: PlanPoint): boolean => !lockedRay || perpendicularDistanceToLine(p, lockedRay) <= tol;\n\n  // ── Stage A: hard snaps ──────────────────────────────────────────────────────\n\n  if (enabled.has(SnapKind.Node)) {\n    let best: { id: string; point: PlanPoint; d: number } | null = null;\n    for (const node of Object.values(floor.nodes)) {\n      if (ctx.excludeNodeIds?.has(node.id)) continue;\n      const d = dist(raw, node);\n      if (d <= nodeTol && allowedByLock(node) && (!best || d < best.d)) {\n        best = { id: node.id, point: { x: node.x, y: node.y }, d };\n      }\n    }\n    if (best) {\n      return finish({ point: best.point, kind: SnapKind.Node, nodeId: best.id, guides: [] });\n    }\n  }\n\n  const walls = Object.values(floor.walls).filter((w) => !ctx.excludeWallIds?.has(w.id));\n\n  if (enabled.has(SnapKind.Intersection)) {\n    let best: { point: PlanPoint; d: number } | null = null;\n    for (let i = 0; i < walls.length; i++) {\n      for (let j = i + 1; j < walls.length; j++) {\n        const a = floor.nodes[walls[i].a];\n        const b = floor.nodes[walls[i].b];\n        const c = floor.nodes[walls[j].a];\n        const d2 = floor.nodes[walls[j].b];\n        if (!a || !b || !c || !d2) continue;\n        const hit = segmentIntersection(a, b, c, d2);\n        if (!hit.point) continue;\n        const d = dist(raw, hit.point);\n        if (d <= tol && allowedByLock(hit.point) && (!best || d < best.d)) {\n          best = { point: hit.point, d };\n        }\n      }\n    }\n    if (best) return finish({ point: best.point, kind: SnapKind.Intersection, guides: [] });\n  }\n\n  if (enabled.has(SnapKind.Wall)) {\n    let best: { wallId: string; point: PlanPoint; t: number; d: number } | null = null;\n    for (const wall of walls) {\n      const a = floor.nodes[wall.a];\n      const b = floor.nodes[wall.b];\n      if (!a || !b) continue;\n      const r = pointToSegment(raw, a, b);\n      if (r.dist <= tol && allowedByLock(r.point) && (!best || r.dist < best.d)) {\n        // Project exactly: intent is allowed to be approximate, the model is not.\n        best = { wallId: wall.id, point: projectOnSegment(raw, a, b), t: r.t, d: r.dist };\n      }\n    }\n    if (best) {\n      return finish({\n        point: best.point,\n        kind: SnapKind.Wall,\n        wallId: best.wallId,\n        t: best.t,\n        guides: [],\n      });\n    }\n  }\n\n  // ── Stage B: constraints ─────────────────────────────────────────────────────\n\n  const constraints: Line[] = [];\n\n  if (enabled.has(SnapKind.Extension)) {\n    let best: Line | null = null;\n    for (const wall of walls) {\n      const a = floor.nodes[wall.a];\n      const b = floor.nodes[wall.b];\n      if (!a || !b) continue;\n      const len = dist(a, b);\n      if (len < 1e-6) continue;\n      const dir = { x: (b.x - a.x) / len, y: (b.y - a.y) / len };\n      const line: Line = { kind: SnapKind.Extension, point: { x: a.x, y: a.y }, dir, residual: 0 };\n      line.residual = perpendicularDistanceToLine(raw, line);\n      // Only beyond the segment itself; on the segment, the wall snap already applies.\n      const onSegment = pointToSegment(raw, a, b).dist <= tol;\n      if (line.residual <= tol && !onSegment && (!best || line.residual < best.residual)) {\n        best = line;\n      }\n    }\n    if (best) constraints.push(best);\n  }\n\n  if (enabled.has(SnapKind.Alignment)) {\n    let bestV: Line | null = null;\n    let bestH: Line | null = null;\n    for (const node of Object.values(floor.nodes)) {\n      if (ctx.excludeNodeIds?.has(node.id)) continue;\n      const dx = Math.abs(raw.x - node.x);\n      if (dx <= tol && (!bestV || dx < bestV.residual)) {\n        bestV = {\n          kind: SnapKind.Alignment,\n          point: { x: node.x, y: node.y },\n          dir: { x: 0, y: 1 },\n          residual: dx,\n        };\n      }\n      const dy = Math.abs(raw.y - node.y);\n      if (dy <= tol && (!bestH || dy < bestH.residual)) {\n        bestH = {\n          kind: SnapKind.Alignment,\n          point: { x: node.x, y: node.y },\n          dir: { x: 1, y: 0 },\n          residual: dy,\n        };\n      }\n    }\n    if (bestV) constraints.push(bestV);\n    if (bestH) constraints.push(bestH);\n  }\n\n  const guideFor = (line: Line, through: PlanPoint): SnapGuide => ({\n    kind: line.kind,\n    a: { x: through.x - line.dir.x * 1e5, y: through.y - line.dir.y * 1e5 },\n    b: { x: through.x + line.dir.x * 1e5, y: through.y + line.dir.y * 1e5 },\n    infinite: true,\n  });\n\n  if (lockedRay) {\n    // The ray is fixed; the only freedom left is where along it the point sits. Another\n    // constraint that genuinely crosses it pins that down, which is the useful\n    // \"45 degrees AND level with that corner\" case.\n    const crossing = constraints\n      .filter((c) => c.residual <= tol && angleBetween(lockedRay, c) > (15 * Math.PI) / 180)\n      .sort((a, b) => a.residual - b.residual)[0];\n\n    if (crossing) {\n      const point = intersectLines(lockedRay, crossing);\n      if (point && dist(point, raw) <= tol * 4) {\n        return finish({\n          point,\n          kind: SnapKind.Angle,\n          guides: [guideFor(lockedRay, point), guideFor(crossing, point)],\n          angleRelativeTo: lockedRay.relativeTo,\n        });\n      }\n    }\n\n    let point = projectOnLine(raw, lockedRay);\n    if (enabled.has(SnapKind.Grid) && options.gridSize > 0) {\n      // Slide along the ray to a round length, rather than rounding both coordinates,\n      // which would take the point straight back off it.\n      const along = (point.x - lockedRay.point.x) * lockedRay.dir.x + (point.y - lockedRay.point.y) * lockedRay.dir.y;\n      const snappedAlong = roundTo(along, options.gridSize);\n      point = {\n        x: lockedRay.point.x + lockedRay.dir.x * snappedAlong,\n        y: lockedRay.point.y + lockedRay.dir.y * snappedAlong,\n      };\n    }\n    return finish({\n      point,\n      kind: SnapKind.Angle,\n      guides: [guideFor(lockedRay, point)],\n      angleRelativeTo: lockedRay.relativeTo,\n    });\n  }\n\n  if (constraints.length >= 2) {\n    const sorted = [...constraints].sort((a, b) => a.residual - b.residual);\n    const [l1, l2] = sorted;\n    const point = intersectLines(l1, l2);\n    // Two nearly parallel constraints intersect somewhere useless and far away.\n    if (point && angleBetween(l1, l2) > (15 * Math.PI) / 180 && dist(point, raw) <= tol * 2) {\n      return finish({\n        point,\n        kind: SnapKind.Intersection,\n        guides: [guideFor(l1, point), guideFor(l2, point)],\n      });\n    }\n  }\n\n  if (constraints.length >= 1) {\n    const line = [...constraints].sort((a, b) => a.residual - b.residual)[0];\n    let point = projectOnLine(raw, line);\n    if (enabled.has(SnapKind.Grid) && options.gridSize > 0) {\n      // Slide along the constraint to a round distance, rather than rounding both\n      // coordinates, which would take the point back off the line.\n      const along = (point.x - line.point.x) * line.dir.x + (point.y - line.point.y) * line.dir.y;\n      const snappedAlong = roundTo(along, options.gridSize);\n      point = {\n        x: line.point.x + line.dir.x * snappedAlong,\n        y: line.point.y + line.dir.y * snappedAlong,\n      };\n    }\n    return finish({\n      point,\n      kind: line.kind,\n      guides: [guideFor(line, point)],\n      angleRelativeTo: line.relativeTo,\n    });\n  }\n\n  if (enabled.has(SnapKind.Grid) && options.gridSize > 0) {\n    return finish({\n      point: { x: roundTo(raw.x, options.gridSize), y: roundTo(raw.y, options.gridSize) },\n      kind: SnapKind.Grid,\n      guides: [],\n    });\n  }\n\n  return finish({ point: raw, kind: SnapKind.None, guides: [] });\n}\n","/**\n * Reading and writing a plan as JSON, which is the interchange format.\n *\n * Writing is trivial. Reading is not: the text comes off a disk, so it is untrusted, and\n * the component that receives the result renders it immediately. `planFromJson` therefore\n * rebuilds the plan field by field rather than casting the parsed object, so nothing that\n * was not checked can reach the renderer, and returns everything it had to fix so a\n * caller can tell the difference between a clean file and a salvaged one.\n *\n * It repairs rather than rejects wherever a repair is unambiguous: a wall whose node is\n * missing is dropped, a thickness of `\"200\"` is read as 200, an unknown wall type becomes\n * `Interior`. A file is only refused when there is nothing to salvage, because a plan a\n * user spent an hour on should not be lost to one bad field.\n */\n\nimport { cleanupPlan, newId } from './floorPlan.graph';\nimport { emptyFloor } from './floorPlan.plan';\nimport { DoorSwing, OpeningKind, PlanUnitSystem, WallType } from './floorPlan.types';\nimport type { FloorPlan, PlanFloor, PlanNode, PlanOpening, PlanPoint, PlanRoom, PlanWall } from './floorPlan.types';\n\n/** The only `version` a file may carry. Bump only for a breaking shape change. */\nexport const PLAN_FORMAT_VERSION = 1;\n\nexport interface PlanParseResult {\n  /** `null` only when the file could not be read as a plan at all. */\n  plan: FloorPlan | null;\n  /**\n   * Everything that had to be repaired or dropped, in plain language. Empty means the\n   * file was clean. A non-empty list with a plan means it was salvaged.\n   */\n  issues: string[];\n}\n\n/** A plan as JSON text, ready to write to a file. Indented unless told otherwise. */\nexport function planToJson(plan: FloorPlan, options: { pretty?: boolean } = {}): string {\n  return JSON.stringify(plan, null, options.pretty === false ? undefined : 2);\n}\n\n// ─── Field readers ─────────────────────────────────────────────────────────────\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A finite number, also accepting the numeric strings a hand-edited file tends to grow. */\nfunction num(value: unknown, fallback: number): number {\n  const n = typeof value === 'string' ? Number(value) : value;\n  return typeof n === 'number' && Number.isFinite(n) ? n : fallback;\n}\n\nfunction str(value: unknown, fallback: string): string {\n  return typeof value === 'string' ? value : fallback;\n}\n\nfunction bool(value: unknown, fallback: boolean): boolean {\n  return typeof value === 'boolean' ? value : fallback;\n}\n\nfunction oneOf<T extends string>(value: unknown, allowed: readonly T[], fallback: T): T {\n  return typeof value === 'string' && (allowed as readonly string[]).includes(value) ? (value as T) : fallback;\n}\n\nfunction point(value: unknown, fallback: PlanPoint): PlanPoint {\n  if (!isRecord(value)) return fallback;\n  return { x: num(value.x, fallback.x), y: num(value.y, fallback.y) };\n}\n\n/**\n * The entries of a `Record<string, T>`, tolerating the array a different exporter might\n * have written. Entries without a usable `id` are dropped by the caller.\n */\nfunction entries(value: unknown): unknown[] {\n  if (Array.isArray(value)) return value;\n  if (isRecord(value)) return Object.values(value);\n  return [];\n}\n\nfunction byId<T extends { id: string }>(items: T[]): Record<string, T> {\n  const out: Record<string, T> = {};\n  for (const item of items) out[item.id] = item;\n  return out;\n}\n\n// ─── Floor ─────────────────────────────────────────────────────────────────────\n\nfunction readFloor(raw: unknown, index: number, issues: string[]): PlanFloor {\n  const where = `floor ${index + 1}`;\n  const source = isRecord(raw) ? raw : {};\n  if (!isRecord(raw)) issues.push(`${where} was not an object, so it was read as empty`);\n\n  const floor = emptyFloor();\n  floor.id = str(source.id, floor.id);\n  floor.label = str(source.label, floor.label);\n  floor.elevation = num(source.elevation, 0);\n  floor.height = num(source.height, floor.height);\n\n  const nodes: PlanNode[] = [];\n  for (const raw of entries(source.nodes)) {\n    if (!isRecord(raw) || typeof raw.id !== 'string') continue;\n    nodes.push({ id: raw.id, x: num(raw.x, 0), y: num(raw.y, 0) });\n  }\n  floor.nodes = byId(nodes);\n\n  const walls: PlanWall[] = [];\n  let orphanWalls = 0;\n  for (const raw of entries(source.walls)) {\n    if (!isRecord(raw) || typeof raw.id !== 'string') continue;\n    const a = str(raw.a, '');\n    const b = str(raw.b, '');\n    // A wall with no node to stand on would break face extraction, which assumes every\n    // wall resolves. There is no way to guess where it was meant to go.\n    if (!floor.nodes[a] || !floor.nodes[b] || a === b) {\n      orphanWalls += 1;\n      continue;\n    }\n    walls.push({\n      id: raw.id,\n      a,\n      b,\n      thickness: Math.max(1, num(raw.thickness, 100)),\n      height: raw.height === undefined ? undefined : num(raw.height, 0),\n      baseHeight: raw.baseHeight === undefined ? undefined : num(raw.baseHeight, 0),\n      type: oneOf(raw.type, Object.values(WallType), WallType.Interior),\n    });\n  }\n  floor.walls = byId(walls);\n  if (orphanWalls > 0) {\n    issues.push(`${where}: dropped ${orphanWalls} wall(s) whose corners were missing`);\n  }\n\n  const openings: PlanOpening[] = [];\n  let orphanOpenings = 0;\n  for (const raw of entries(source.openings)) {\n    if (!isRecord(raw) || typeof raw.id !== 'string') continue;\n    const wallId = str(raw.wallId, '');\n    if (!floor.walls[wallId]) {\n      orphanOpenings += 1;\n      continue;\n    }\n    openings.push({\n      id: raw.id,\n      wallId,\n      kind: oneOf(raw.kind, Object.values(OpeningKind), OpeningKind.Door),\n      offset: num(raw.offset, 0),\n      width: Math.max(1, num(raw.width, 800)),\n      sill: num(raw.sill, 0),\n      head: num(raw.head, 2100),\n      swing: raw.swing === undefined ? undefined : oneOf(raw.swing, Object.values(DoorSwing), DoorSwing.StartLeft),\n    });\n  }\n  floor.openings = byId(openings);\n  if (orphanOpenings > 0) {\n    issues.push(`${where}: dropped ${orphanOpenings} opening(s) with no wall to sit in`);\n  }\n\n  const rooms: PlanRoom[] = [];\n  for (const raw of entries(source.rooms)) {\n    if (!isRecord(raw) || typeof raw.id !== 'string') continue;\n    // A room is only a name attached to a face, and the face is recomputed on load, so a\n    // room whose walls have gone is kept rather than dropped: re-adding the wall brings\n    // its name back, and dropping it is the one way this model loses data for good.\n    rooms.push({\n      id: raw.id,\n      name: str(raw.name, ''),\n      colour: typeof raw.colour === 'string' ? raw.colour : undefined,\n      usage: typeof raw.usage === 'string' ? raw.usage : undefined,\n      anchor: point(raw.anchor, { x: 0, y: 0 }),\n      anchorPinned: bool(raw.anchorPinned, false),\n      seq: num(raw.seq, rooms.length),\n      wallIds: Array.isArray(raw.wallIds)\n        ? raw.wallIds.filter((id): id is string => typeof id === 'string' && !!floor.walls[id])\n        : [],\n      area: num(raw.area, 0),\n    });\n  }\n  floor.rooms = byId(rooms);\n\n  const cleaned = cleanupPlan(floor);\n  if (cleaned.result.length > 0) {\n    issues.push(`${where}: repaired ${cleaned.result.length} inconsistency(ies) in the geometry`);\n  }\n  return cleaned.floor;\n}\n\n// ─── Document ──────────────────────────────────────────────────────────────────\n\n/**\n * Reads JSON text as a plan, repairing what it can and reporting what it could not.\n *\n * `plan` is `null` only when the text is not JSON, is not an object, or carries a\n * `version` this build does not know. Everything else comes back as a usable plan.\n */\nexport function planFromJson(text: string): PlanParseResult {\n  const issues: string[] = [];\n\n  let raw: unknown;\n  try {\n    raw = JSON.parse(text);\n  } catch (error) {\n    return { plan: null, issues: [`Not valid JSON: ${(error as Error).message}`] };\n  }\n\n  if (!isRecord(raw)) return { plan: null, issues: ['Not a floor plan: expected an object'] };\n\n  if (raw.version !== undefined && raw.version !== PLAN_FORMAT_VERSION) {\n    // Refusing beats guessing: a future version may mean something different by the same\n    // field names, and silently misreading it loses the file's real contents.\n    return {\n      plan: null,\n      issues: [\n        `Plan format version ${String(raw.version)} is not supported ` +\n          `(this build reads version ${PLAN_FORMAT_VERSION})`,\n      ],\n    };\n  }\n  if (raw.version === undefined) issues.push('No format version, read as version 1');\n\n  const rawFloors = Array.isArray(raw.floors) ? raw.floors : [];\n  if (rawFloors.length === 0) {\n    if (raw.floors !== undefined) issues.push('No floors in the file, started an empty one');\n    else return { plan: null, issues: ['Not a floor plan: no `floors`'] };\n  }\n\n  const floors = (rawFloors.length > 0 ? rawFloors : [undefined]).map((floor, i) => readFloor(floor, i, issues));\n\n  return {\n    plan: {\n      id: str(raw.id, newId('plan')),\n      name: typeof raw.name === 'string' ? raw.name : undefined,\n      version: PLAN_FORMAT_VERSION,\n      unitSystem: oneOf(raw.unitSystem, Object.values(PlanUnitSystem), PlanUnitSystem.Metric),\n      floors,\n      // Bumped, not carried over: the plan in hand is not the one the file described if\n      // anything was repaired, and `derivePlan` memoises on this.\n      revision: num(raw.revision, 0) + 1,\n    },\n    issues,\n  };\n}\n","/**\n * Ear clipping for simple polygons, with holes bridged into the outer ring first.\n *\n * Written out rather than pulled in: a floor slab is the only thing in this package that\n * needs a triangulator, and a dependency is a poor trade for one file.\n *\n * The rooms this runs on come from face extraction, so they are already closed, already\n * consistently wound, and free of self-intersections. That is what makes plain ear\n * clipping enough: it is O(n²) and it cannot cope with a self-intersecting ring, and\n * neither limit matters for a room with a few dozen corners.\n */\n\nimport { signedArea, stripSpurs } from './floorPlan.geometry';\nimport type { PlanPoint } from './floorPlan.types';\n\nconst EPS = 1e-9;\n\nfunction area2(a: PlanPoint, b: PlanPoint, c: PlanPoint): number {\n  return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);\n}\n\nfunction samePoint(a: PlanPoint, b: PlanPoint): boolean {\n  return Math.abs(a.x - b.x) < EPS && Math.abs(a.y - b.y) < EPS;\n}\n\n/** Inclusive of the edges: a vertex lying on one would still leave a sliver behind. */\nfunction inTriangle(p: PlanPoint, a: PlanPoint, b: PlanPoint, c: PlanPoint): boolean {\n  const d1 = area2(p, a, b);\n  const d2 = area2(p, b, c);\n  const d3 = area2(p, c, a);\n  const neg = d1 < -EPS || d2 < -EPS || d3 < -EPS;\n  const pos = d1 > EPS || d2 > EPS || d3 > EPS;\n  return !(neg && pos);\n}\n\n/**\n * Joins each hole to the outer ring with a pair of coincident edges, leaving one ring\n * that ear clipping can take. The bridge runs from the hole's rightmost vertex to a\n * visible vertex of the outer ring, which is the standard construction: going rightwards\n * from the extreme point of a hole must leave it, so the first outer edge hit is visible\n * from it.\n */\nfunction bridgeHoles(outer: PlanPoint[], holes: PlanPoint[][]): PlanPoint[] {\n  let ring = outer.slice();\n\n  // Rightmost hole first: bridging it cannot cross a hole further right.\n  const queue = holes\n    .filter((h) => h.length >= 3)\n    .map((h) => ({ ring: h, index: h.reduce((best, p, i) => (p.x > h[best].x ? i : best), 0) }))\n    .sort((a, b) => b.ring[b.index].x - a.ring[a.index].x);\n\n  for (const hole of queue) {\n    const from = hole.ring[hole.index];\n\n    // The outer vertex to bridge to: the one that is visible and, among those, closest.\n    let best = -1;\n    let bestDist = Infinity;\n    for (let i = 0; i < ring.length; i++) {\n      if (ring[i].x < from.x) continue; // must lie to the right, or the bridge doubles back\n      const d = (ring[i].x - from.x) ** 2 + (ring[i].y - from.y) ** 2;\n      if (d < bestDist && visible(from, ring[i], ring, hole.ring)) {\n        bestDist = d;\n        best = i;\n      }\n    }\n    // Nothing to the right was visible, so fall back to the nearest vertex anywhere. The\n    // ring stays closed either way; at worst the slab gains a seam nobody can see.\n    if (best < 0) {\n      for (let i = 0; i < ring.length; i++) {\n        const d = (ring[i].x - from.x) ** 2 + (ring[i].y - from.y) ** 2;\n        if (d < bestDist) {\n          bestDist = d;\n          best = i;\n        }\n      }\n    }\n\n    // Walk the hole from its bridge vertex, then come back along the bridge.\n    const woven: PlanPoint[] = [];\n    for (let k = 0; k <= hole.ring.length; k++) {\n      woven.push(hole.ring[(hole.index + k) % hole.ring.length]);\n    }\n    ring = [...ring.slice(0, best + 1), ...woven, ...ring.slice(best)];\n  }\n\n  return ring;\n}\n\n/** Whether the segment from `a` to `b` crosses any edge of the rings it has to avoid. */\nfunction visible(a: PlanPoint, b: PlanPoint, ring: PlanPoint[], hole: PlanPoint[]): boolean {\n  for (const poly of [ring, hole]) {\n    for (let i = 0; i < poly.length; i++) {\n      const p = poly[i];\n      const q = poly[(i + 1) % poly.length];\n      if (p === a || q === a || p === b || q === b) continue;\n      const d1 = area2(a, b, p);\n      const d2 = area2(a, b, q);\n      const d3 = area2(p, q, a);\n      const d4 = area2(p, q, b);\n      if (d1 * d2 < 0 && d3 * d4 < 0) return false;\n    }\n  }\n  return true;\n}\n\n/**\n * Triangulates a ring and its holes into index triples over the returned vertex list.\n *\n * The vertices come back as well as the indices because bridging a hole duplicates the\n * two vertices it joins, so the caller cannot simply index into what it passed in.\n */\nexport function triangulate(\n  outer: readonly PlanPoint[],\n  holes: readonly (readonly PlanPoint[])[] = [],\n): { vertices: PlanPoint[]; indices: number[] } {\n  // A dangling wall inside a room puts the same vertex in the ring twice, which is not a\n  // simple polygon and would stall ear clipping on a zero-area ear.\n  const cleanOuter = stripSpurs(outer);\n  if (cleanOuter.length < 3) return { vertices: [], indices: [] };\n\n  const ring = bridgeHoles(\n    cleanOuter,\n    holes.map((h) => stripSpurs(h)).filter((h) => h.length >= 3),\n  );\n\n  // Ear clipping wants a counter-clockwise ring under the usual maths convention.\n  const vertices = signedArea(ring) < 0 ? ring.slice().reverse() : ring.slice();\n\n  const live = vertices.map((_, i) => i);\n  const indices: number[] = [];\n  let guard = live.length * live.length;\n\n  while (live.length > 3 && guard-- > 0) {\n    let clipped = false;\n    for (let i = 0; i < live.length; i++) {\n      const ai = live[(i + live.length - 1) % live.length];\n      const bi = live[i];\n      const ci = live[(i + 1) % live.length];\n      const a = vertices[ai];\n      const b = vertices[bi];\n      const c = vertices[ci];\n      if (area2(a, b, c) <= EPS) continue; // reflex or degenerate: not an ear\n\n      let blocked = false;\n      for (const vi of live) {\n        if (vi === ai || vi === bi || vi === ci) continue;\n        const v = vertices[vi];\n        // Bridging a hole duplicates the two vertices it joins, so a candidate can sit\n        // exactly on a corner of the ear. Counting that as \"inside\" blocks every ear in\n        // the ring and the triangulation comes back empty.\n        if (samePoint(v, a) || samePoint(v, b) || samePoint(v, c)) continue;\n        if (inTriangle(v, a, b, c)) {\n          blocked = true;\n          break;\n        }\n      }\n      if (blocked) continue;\n\n      indices.push(ai, bi, ci);\n      live.splice(i, 1);\n      clipped = true;\n      break;\n    }\n    // No ear found: the ring is degenerate in a way this cannot reduce further. Stopping\n    // beats spinning, and the caller gets whatever was triangulated so far.\n    if (!clipped) break;\n  }\n  if (live.length === 3) indices.push(live[0], live[1], live[2]);\n\n  return { vertices, indices };\n}\n","/**\n * Turns a floor into triangles: wall solids with their openings cut out, and a floor\n * slab under them.\n *\n * Plain arrays, no renderer and no dependency. `positions`/`normals`/`indices` is the\n * shape every engine takes, so the result feeds three.js, Babylon, a glTF writer or a\n * software rasteriser without this package having an opinion about which.\n *\n * AXES: plan x becomes world x, plan y becomes world z, and world y is up. A plan is\n * drawn in SVG's y-down space, so that mapping is a handedness flip, which is why\n * winding is fixed per face here rather than inherited from the 2D rings.\n *\n * Openings are cut by SPLITTING the wall, not by subtracting solids. Every opening is an\n * axis-aligned rectangle in its wall's own plane, so the wall falls into at most three\n * prisms — under the sill, over the head, and the full-height stretches between openings.\n * That is exact, and it needs no CSG.\n */\n\nimport { dist } from './floorPlan.geometry';\nimport { derivePlan } from './floorPlan.rooms';\nimport { wallCentrelineSpans } from './floorPlan.path';\nimport { triangulate } from './floorPlan.triangulate';\nimport type { PlanFloor, PlanOpening, PlanPoint, PlanWall } from './floorPlan.types';\n\nconst EPS = 1e-6;\n\n/**\n * Millimetres the walls drop past the slab, so the two solids never share a face.\n *\n * A wall overlaps the slab it stands over, because the slab reaches the wall centrelines\n * and the wall straddles them. Land their undersides on the same plane and the two\n * downward faces fight for the same pixels, which speckles the whole underside of the\n * building. One millimetre on a slab of a couple of hundred puts the wall's face clear\n * below and the slab's inside the wall, so each height has exactly one surface at it.\n */\nconst Z_BIAS = 1;\n\nexport type PlanMeshPart = 'wall' | 'floor';\n\nexport interface PlanMeshGroup {\n  part: PlanMeshPart;\n  /** Offset into `indices`, and how many of them. Draw ranges, or assign materials. */\n  start: number;\n  count: number;\n}\n\nexport interface PlanMesh {\n  /** World-space xyz triples, in millimetres. */\n  positions: Float32Array;\n  /** Unit face normals, one per vertex. */\n  normals: Float32Array;\n  indices: Uint32Array;\n  groups: PlanMeshGroup[];\n}\n\nexport interface BuildPlanMeshOptions {\n  /** Storey height for walls that do not carry their own. Defaults to the floor's. */\n  wallHeight?: number;\n  /** Lay a slab under each room. Default true. */\n  includeFloorSlab?: boolean;\n  /** Millimetres. The slab hangs below the floor level. Default 200. */\n  slabThickness?: number;\n  /**\n   * Lift everything by the floor's own elevation, so several storeys stack into one\n   * scene. Default true.\n   */\n  applyElevation?: boolean;\n}\n\n/** Accumulates triangles as flat arrays, with a face normal per triangle. */\nclass MeshBuilder {\n  readonly positions: number[] = [];\n  readonly normals: number[] = [];\n  readonly indices: number[] = [];\n\n  /**\n   * One triangle, with its vertices duplicated rather than shared.\n   *\n   * Walls are flat-shaded: sharing a vertex between two faces would average their\n   * normals and round off a corner that is physically square.\n   */\n  triangle(a: number[], b: number[], c: number[]): void {\n    const ux = b[0] - a[0];\n    const uy = b[1] - a[1];\n    const uz = b[2] - a[2];\n    const vx = c[0] - a[0];\n    const vy = c[1] - a[1];\n    const vz = c[2] - a[2];\n    let nx = uy * vz - uz * vy;\n    let ny = uz * vx - ux * vz;\n    let nz = ux * vy - uy * vx;\n    const len = Math.hypot(nx, ny, nz);\n    if (len < EPS) return; // degenerate: contributes nothing and has no normal\n    nx /= len;\n    ny /= len;\n    nz /= len;\n\n    const base = this.positions.length / 3;\n    for (const p of [a, b, c]) this.positions.push(p[0], p[1], p[2]);\n    for (let i = 0; i < 3; i++) this.normals.push(nx, ny, nz);\n    this.indices.push(base, base + 1, base + 2);\n  }\n\n  /** A planar quad as two triangles, wound so its normal follows a -> b -> c. */\n  quad(a: number[], b: number[], c: number[], d: number[]): void {\n    this.triangle(a, b, c);\n    this.triangle(a, c, d);\n  }\n}\n\n/**\n * A wall as a box: its footprint corners in plan, and the heights it spans.\n *\n * The footprint is the centreline offset by half the thickness each way. Walls that meet\n * at a corner overlap slightly inside the joint rather than mitring into each other; for\n * an opaque solid that is invisible from any angle, and it keeps each wall a closed box\n * instead of a shape that depends on its neighbours.\n */\n/** The walls meeting at each node, for mitring a corner. */\nfunction wallsByNode(floor: PlanFloor): Map<string, PlanWall[]> {\n  const at = new Map<string, PlanWall[]>();\n  for (const wall of Object.values(floor.walls)) {\n    for (const id of [wall.a, wall.b]) {\n      const list = at.get(id);\n      if (list) list.push(wall);\n      else at.set(id, [wall]);\n    }\n  }\n  return at;\n}\n\n/** How far a mitre may run before the corner is left square, as multiples of half a wall. */\nconst MITRE_LIMIT = 4;\n\n/**\n * How far the footprint's two edges reach past a node, as a vector to be scaled by half\n * the thickness and added on the wall's left, subtracted on its right.\n *\n * Two walls meeting at a corner both STOP at the shared node, so between them they leave\n * a square of half a thickness uncovered on the outside of the turn — the same notch the\n * 2D renderer fills with a mitre join, cut here through the full height of the wall.\n * Extending both walls to where their edges actually cross closes it.\n *\n * `null` leaves the end square: at a junction there is no single corner to mitre to, at a\n * change of thickness the two edges are offset by different amounts, and past the mitre\n * limit an acute corner would run away into a spike.\n */\nfunction mitreVector(floor: PlanFloor, wall: PlanWall, nodeId: string, at: Map<string, PlanWall[]>): PlanPoint | null {\n  const here = at.get(nodeId);\n  if (!here || here.length !== 2) return null;\n  const other = here[0].id === wall.id ? here[1] : here[0];\n  if (other.thickness !== wall.thickness) return null;\n\n  const node = floor.nodes[nodeId];\n  const far = floor.nodes[wall.a === nodeId ? wall.b : wall.a];\n  const onward = floor.nodes[other.a === nodeId ? other.b : other.a];\n  if (!node || !far || !onward) return null;\n\n  // Into the corner along this wall, then out along its neighbour.\n  const u = unit(far, node);\n  const v = unit(node, onward);\n  if (!u || !v) return null;\n\n  const along = u.x * v.x + u.y * v.y;\n  if (along < -1 + EPS) return null; // doubles back on itself: no corner to speak of\n  const nu = { x: -u.y, y: u.x };\n  const nv = { x: -v.y, y: v.x };\n  const scale = 1 + along;\n  const mitre = { x: (nu.x + nv.x) / scale, y: (nu.y + nv.y) / scale };\n  if (Math.hypot(mitre.x, mitre.y) > MITRE_LIMIT) return null;\n\n  // Returned on the wall's own left, which is `u`'s left at this end.\n  return wall.b === nodeId ? mitre : { x: -mitre.x, y: -mitre.y };\n}\n\nfunction unit(from: PlanPoint, to: PlanPoint): PlanPoint | null {\n  const len = dist(from, to);\n  if (len < EPS) return null;\n  return { x: (to.x - from.x) / len, y: (to.y - from.y) / len };\n}\n\n/**\n * The stretch of a wall between two offsets along it, as a footprint quad.\n *\n * Ends that are the wall's true ends are mitred into their neighbour; ends created by an\n * opening are left square, because a jamb is a square face.\n */\nfunction footprintBetween(\n  floor: PlanFloor,\n  wall: PlanWall,\n  from: number,\n  to: number,\n  at: Map<string, PlanWall[]>,\n): [PlanPoint, PlanPoint, PlanPoint, PlanPoint] | null {\n  const a = floor.nodes[wall.a];\n  const b = floor.nodes[wall.b];\n  if (!a || !b) return null;\n  const len = dist(a, b);\n  if (len < EPS || to - from < EPS) return null;\n\n  const d = unit(a, b)!;\n  const half = wall.thickness / 2;\n  const n = { x: -d.y * half, y: d.x * half };\n\n  const start = { x: a.x + d.x * from, y: a.y + d.y * from };\n  const end = { x: a.x + d.x * to, y: a.y + d.y * to };\n\n  const mitreA = from < EPS ? mitreVector(floor, wall, wall.a, at) : null;\n  const mitreB = to > len - EPS ? mitreVector(floor, wall, wall.b, at) : null;\n\n  const offset = (p: PlanPoint, m: PlanPoint | null, sign: 1 | -1) =>\n    m ? { x: p.x + m.x * half * sign, y: p.y + m.y * half * sign } : { x: p.x + n.x * sign, y: p.y + n.y * sign };\n\n  return [offset(start, mitreA, 1), offset(end, mitreB, 1), offset(end, mitreB, -1), offset(start, mitreA, -1)];\n}\n\n/** A closed box over a footprint, between two heights. */\nfunction box(mesh: MeshBuilder, foot: readonly PlanPoint[], bottom: number, top: number): void {\n  if (top - bottom < EPS) return;\n  const lo = foot.map((p) => [p.x, bottom, p.y]);\n  const hi = foot.map((p) => [p.x, top, p.y]);\n\n  /*\n   * Which way round the footprint runs decides which way the faces look. Plan y becomes\n   * world z with y up, which is a handedness flip, so a footprint that reads\n   * counter-clockwise in plan comes out clockwise seen from above and its faces would\n   * point inwards. Hence the inversion rather than taking the plan winding at its word.\n   */\n  let twice = 0;\n  for (let i = 0; i < foot.length; i++) {\n    const p = foot[i];\n    const q = foot[(i + 1) % foot.length];\n    twice += p.x * q.y - q.x * p.y;\n  }\n  const ccw = twice < 0;\n\n  for (let i = 0; i < foot.length; i++) {\n    const j = (i + 1) % foot.length;\n    if (ccw) mesh.quad(lo[i], lo[j], hi[j], hi[i]);\n    else mesh.quad(lo[j], lo[i], hi[i], hi[j]);\n  }\n\n  // Caps. A footprint is always convex here — it is a rectangle — so a fan is enough.\n  for (let i = 1; i + 1 < foot.length; i++) {\n    if (ccw) {\n      mesh.triangle(hi[0], hi[i], hi[i + 1]);\n      mesh.triangle(lo[0], lo[i + 1], lo[i]);\n    } else {\n      mesh.triangle(hi[0], hi[i + 1], hi[i]);\n      mesh.triangle(lo[0], lo[i], lo[i + 1]);\n    }\n  }\n}\n\n/** The openings on one wall, clamped to it and sorted along it. */\nfunction openingsOn(floor: PlanFloor, wall: PlanWall, length: number): PlanOpening[] {\n  return Object.values(floor.openings)\n    .filter((o) => o.wallId === wall.id)\n    .filter((o) => o.offset + o.width / 2 > EPS && o.offset - o.width / 2 < length - EPS)\n    .sort((a, b) => a.offset - b.offset);\n}\n\n/**\n * Triangles for one floor: every wall, with its openings cut out, and a slab per room.\n *\n * Call it per storey and concatenate to build a whole building; each floor already knows\n * its own elevation.\n */\nexport function buildPlanMesh(floor: PlanFloor, options: BuildPlanMeshOptions = {}): PlanMesh {\n  const { wallHeight, includeFloorSlab = true, slabThickness = 200, applyElevation = true } = options;\n\n  const mesh = new MeshBuilder();\n  const groups: PlanMeshGroup[] = [];\n  const base = applyElevation ? floor.elevation : 0;\n\n  /*\n   * `elevation` is the FINISHED FLOOR LEVEL, so a storey's slab hangs below it. Walls\n   * start at the slab's underside rather than at the level itself, which is to say they\n   * clothe the floor plate the way a facade does instead of stopping short of it.\n   *\n   * That keeps a wall exactly `floor.height` tall, and makes each storey's walls meet the\n   * ones below face to face: this storey starts at `elevation - slab`, and the storey\n   * below ends there too. No band of exposed slab between them, and nothing overlapping\n   * along the facade.\n   */\n  const slabDrop = includeFloorSlab ? slabThickness + Z_BIAS : 0;\n\n  // ── Walls ──────────────────────────────────────────────────────────────────────\n  const atNode = wallsByNode(floor);\n  const wallStart = mesh.indices.length;\n  for (const wall of Object.values(floor.walls)) {\n    const a = floor.nodes[wall.a];\n    const b = floor.nodes[wall.b];\n    if (!a || !b) continue;\n    const length = dist(a, b);\n    if (length < EPS) continue;\n\n    const bottom = base + (wall.baseHeight ?? 0) - slabDrop;\n    const top = bottom + (wall.height ?? wallHeight ?? floor.height);\n    if (top - bottom < EPS) continue;\n\n    const openings = openingsOn(floor, wall, length);\n    if (openings.length === 0) {\n      const foot = footprintBetween(floor, wall, 0, length, atNode);\n      if (foot) box(mesh, foot, bottom, top);\n      continue;\n    }\n\n    // Full-height stretches between the openings, from `wallCentrelineSpans` so the\n    // solid is cut exactly where the drawing is.\n    for (const [from, to] of wallCentrelineSpans(floor, wall)) {\n      const foot = footprintBetween(floor, wall, from, to, atNode);\n      if (foot) box(mesh, foot, bottom, top);\n    }\n\n    // Then the lintel over each opening and the upstand under it.\n    for (const opening of openings) {\n      const from = Math.max(0, opening.offset - opening.width / 2);\n      const to = Math.min(length, opening.offset + opening.width / 2);\n      const foot = footprintBetween(floor, wall, from, to, atNode);\n      if (!foot) continue;\n      const sill = bottom + Math.max(0, opening.sill);\n      const head = bottom + Math.max(0, opening.head);\n      if (sill > bottom + EPS) box(mesh, foot, bottom, Math.min(sill, top));\n      if (head < top - EPS) box(mesh, foot, Math.max(head, bottom), top);\n    }\n  }\n  if (mesh.indices.length > wallStart) {\n    groups.push({ part: 'wall', start: wallStart, count: mesh.indices.length - wallStart });\n  }\n\n  // ── Floor slabs ────────────────────────────────────────────────────────────────\n  if (includeFloorSlab) {\n    const slabStart = mesh.indices.length;\n    for (const room of derivePlan(floor).rooms) {\n      const { vertices, indices } = triangulate(room.outer, room.holes);\n      if (indices.length === 0) continue;\n      const top = base;\n      const bottom = base - slabThickness;\n\n      for (let i = 0; i < indices.length; i += 3) {\n        const t = [vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]];\n        const up = t.map((q) => [q.x, top, q.y]);\n        const down = t.map((q) => [q.x, bottom, q.y]);\n        // `triangulate` returns counter-clockwise triangles in plan space; the y-down to\n        // y-up flip reverses that, so the top face is wound the other way here.\n        mesh.triangle(up[0], up[2], up[1]);\n        mesh.triangle(down[0], down[1], down[2]);\n      }\n\n      // The slab's edge, so it is a solid rather than two sheets.\n      for (const ring of [room.outer, ...room.holes]) {\n        for (let i = 0; i < ring.length; i++) {\n          const q = ring[i];\n          const r = ring[(i + 1) % ring.length];\n          // Wound so the face looks away from the solid. Holes come back wound opposite\n          // to the outer ring, so walking each ring as it is gives both the right way out.\n          mesh.quad([q.x, bottom, q.y], [q.x, top, q.y], [r.x, top, r.y], [r.x, bottom, r.y]);\n        }\n      }\n    }\n    if (mesh.indices.length > slabStart) {\n      groups.push({ part: 'floor', start: slabStart, count: mesh.indices.length - slabStart });\n    }\n  }\n\n  return {\n    positions: new Float32Array(mesh.positions),\n    normals: new Float32Array(mesh.normals),\n    indices: new Uint32Array(mesh.indices),\n    groups,\n  };\n}\n"],"mappings":"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"}