{"version":3,"file":"floorPlan.graph.mjs","names":[],"sources":["../../../../package/common/floorPlan/floorPlan.graph.ts"],"sourcesContent":["/**\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"],"mappings":"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"}