diff --git a/PROGRESS.md b/PROGRESS.md index c7e43dbd9..3a7d064e3 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -23,17 +23,17 @@ ### 🔀 CURRENTLY JUGGLED (user runs these concurrently across terminals, shuts down/resumes cold — read this ### list FIRST, don't ask the user to restate status, it's kept current here) - `prompts/RESUME_HR_BIM_ASSET.md` §2026-07-06c — camera-POV done (#674 `77f41b9`). Open: A/B/C bugs + E decision. -- `prompts/RESUME_WORLD_HISTORY_DEDUP_RESTORE.md` §2026-07-06 — Viewer Part2 done (#670 `d6bfb80`); Modeller - port Ph1-2 done (#675 `9ff9a5a`+`6d9f906a`), open: G6, Ph3. World-History Part1 parked. +- `prompts/RESUME_WORLD_HISTORY_DEDUP_RESTORE.md` §2026-07-06 — Viewer Pt2 + Modeller Ph1-2 done, open: G6, Ph3, Pt1 parked. - `prompts/PILL_DRAWER_REORGANIZATION.md` §2026-07-06 — done (#673 `953d1e4`). Open: first-touch flicker. - `prompts/OPEN_BUTTON_IFC_BCF_MERGE.md` — not started. -- `prompts/Modeller/DISC_Walker/RESUME_DISC_WALKER_ENVELOPE_BOUND.md` §ROTATION-BOUND 2026-07-09 — Bug B shipped - (PR #717, unmerged). Bug A (hostBind) + items 1-2 (occupancy() fix, VENT_WINDOW_SHIM 415→498mm) all fixed+witnessed - (W-OCC-TRUE-MIDPOINT 17/17, 0 regressions). §TE-ARC-DATUM DONE+MERGED both repos 2026-07-11 (bim-compiler - `b202eb44b` PR #40; bim-ootb PR #726 squash `3d09ad6`, confirmed ancestor of `main` — NOT still-open, correcting - a stale note). Remaining: `prompts/DISC_WALKER_BRANCH_CLOSEOUT.md` (Fable-assigned) — 3 small stale PRs - (#722/#724/#725) need fresh re-verify before merge, + the undiagnosed guide-screenshot camera bug. - §LIVEWIRE CLOSED 2026-07-11 (Fable, MANAGER-verified) — W-SCHED-MINE 7/7 + W-DX-WALKBACK-RSGT 14/14, 0 unpushed. +- **DiscWalk containment (Bug A) — RESOLVED+VERIFIED 2026-07-12 (5-axis proof, DX+SC; Terminal honestly + unmeasurable, data gap). Bug B merged. D3/D4/D4b fixed. Room-taxonomy Lane A (R-MERGE+R-REJECT) also + SHIPPED both mirrors, 6/6 parity verified; R-DOOR-SCORE tried+cleanly reverted (broke a real witness). + Read `[[project_discwalk_containment_utmost]]` memory for detail — do NOT re-attempt the `_hostAxis` + cardinal-swap patch (disproven) or R-DOOR-SCORE's hallwayness formula (also disproven) unbuilt.** + §TE-ARC-DATUM DONE+MERGED both repos 2026-07-11 (bim-compiler `b202eb44b` PR #40; bim-ootb PR #726). Remaining: + `prompts/DISC_WALKER_BRANCH_CLOSEOUT.md` — 3 stale PRs (#722/#724/#725) need re-verify + the undiagnosed + guide-screenshot camera bug. §LIVEWIRE CLOSED 2026-07-11 — W-SCHED-MINE 7/7 + W-DX-WALKBACK-RSGT 14/14, 0 unpushed. - **Room Intelligence lane — canonical status: `prompts/ROOM_INTELLIGENCE_SCOREBOARD.md`.** User's verdict 2026-07-11: building taxonomy is **good enough, no more perfection work there.** 15 features shipped/verified. Weakest links unchanged: door-access signal (4/10), classifier @@ -46,13 +46,6 @@ into bim-compiler `fable/meshdb-livewire`. Phase 1 (grid+door+slab+envelope fusion for sparse-wall federated buildings, e.g. HHS) scoped as a ready-to-pick-up 4-step follow-up, not built — genuinely substantial new engineering, deserves its own session. -- **Building Parts Taxonomy (STAIRWAY/LIFT_SHAFT/PLANT_ROOM) — DONE, both VISION-LOCK sentence-5 UI - halves merged into bim-ootb `main` (local, 6 commits ahead of `origin/main`, not pushed):** Find - panel (`d04ddd5`) + Modeller Outliner (`f10c5295`) + disc-walk room-type-aware placement - (`20ad5c4`, PLB/FP real measured signal — BATHROOM/UTILITY→PLB, FOYER→FP; ELEC/ACMV honestly - refused, no signal). Full trail incl. the DB-snapshot-divergence landmine (now saved as - `project_db_snapshot_divergence_landmine.md`, memory) and a real leaf-id click-path bug caught+ - fixed: `prompts/BUILDING_PARTS_TAXONOMY.md`. - **Guide blocked on ONE thing, named and spec'd, not fire-fought:** `docs/ModellerGuide.md` (29 real screenshots, otherwise complete) can't get a Building Parts entry yet — user's own live review: LOD is fine now, but Modeller glass/window material isn't see-through like the Viewer's. @@ -63,11 +56,6 @@ - **`prompts/MANAGER.md` hardened this session** — anti-ad-hoc-debugging conduct rule (stop after a 2nd failed quick-check, write a spec + dispatch instead of trial-and-erroring in-turn). Read it before picking up either spec above. -- **MANAGER housekeeping this session:** 6 stale bim-compiler branches/worktrees verified - fully-superseded and pruned (nothing lost); one real orphan found+landed (W024 - `component_dimension_range` migration, cherry-picked `c254cb271`). ERP PR #8 (5wk stale) - independently re-verified and correctly left open — real merge conflict found (Blue Future vs. - I-D checkpoint), not a courtesy hedge. - **From the 2026-07-10 marathon, still unmerged, not superseded:** `fix/grid-tilt-guard`, `fix/dw-rot-units`, `fable/dwprobe-dedup`, `fix/terminal-oracle-source` (bim-ootb, all verified+ pushed, no PR). Full detail: `project_disc_walker_grid_guard_marathon_2026-07-10.md` (memory). @@ -90,6 +78,10 @@ low-priority, not urgent. Memory: `project_arc_meshreadpixels_branch_unmerged.md`. ## Archive — DONE/shipped (one-line pointers; detail in cards + memory topic files) +- **✅ Building Parts Taxonomy** (STAIRWAY/LIFT_SHAFT/PLANT_ROOM, Find panel + Outliner + disc-walk + room-type-aware placement) — merged bim-ootb `main` local (not pushed). Detail: `prompts/BUILDING_PARTS_TAXONOMY.md`. +- **✅ MANAGER housekeeping 2026-07-11:** 6 stale branches/worktrees pruned, 1 orphan landed (`c254cb271`), + ERP PR #8 re-verified (real conflict, correctly left open). - **✅ pending merge only:** `SCALE_AND_UX_SWEEP.md` (bim-ootb PR #665), `OFFLINE_GITHUB_RELEASE_BUNDLE.md` (`lane/offline-gateway-leak-fix`) — both independently re-verified, just need the human merge click. - **2026-07-05 arc** (landing Save/Open, grid, Teams E2E, HBA mobile, UBBL recon) — ALL MERGED diff --git a/build/room_walker.js b/build/room_walker.js index 2af3a05f3..4a3b1ed5c 100644 --- a/build/room_walker.js +++ b/build/room_walker.js @@ -670,6 +670,274 @@ return rooms; } + // ========================================================================================== + // §R-MERGE / §R-REJECT (ROOM_TAXONOMY_STRATEGY_2026-07-12.md Tasks 1/1b — POC-validated: JKR + // 79->51 rooms, split-hallway chains merged; Duplex control 0 false merges; JKR 48 non-OPEN + // rooms 0 false rejects, 16/31 SUSPECT_OPEN correctly rejected). Verbatim port of + // scripts/compile_rooms.py's own port (same file, same section header) — not re-derived. + // Runs AFTER floodRooms/partitionByDoors produce a storey's room list, BEFORE guid/name + // assignment: R-MERGE first (removes only synthetic dividing lines, never invents geometry), + // then R-REJECT (merging raises enclosure of legitimate unions, so reject must see the + // post-merge shape). + var MERGE_GAP_TOL_FACTOR = 2.0; // x median real-wall thickness = seam-adjacency search band + var MERGE_SHARE_MIN = 0.50; // shared edge >= 50% of the smaller room's parallel side + var MERGE_WALL_COVER_MAX = 0.25; // same family as STAIR_OVERLAP_REJECT + var MERGE_DOOR_TOL = 0.60; // m -- door center within this of the seam blocks the merge + var WALL_TOL = 0.45; // m -- band around a seam/perimeter side within which a wall + // AABB counts as backing it (shared by merge + reject) + var REJECT_ENCLOSURE = 0.25; // enclosure < this => REJECT (not a room) + var SUSPECT_OPEN_ENCLOSURE = 0.50; // enclosure < this (and >= REJECT_ENCLOSURE) => SUSPECT_OPEN + // §STAIRWELL-STACK (mirror of compile_rooms.py, user report 2026-07-12): a shaft's per-storey + // flight covers only ~0.22 of its pocket (under STAIR_OVERLAP_REJECT=0.35, which stays), but the + // STACK across storeys covers 1.30-2.23x vs 0.37 max for any legitimate room — measured gap. + var STAIRWELL_STACK_REJECT = 0.50; // cumulative all-storey stair overlap >= this x area... + var STAIRWELL_STACK_MIN_LEVELS = 3; // ...across >= this many distinct ~2m z-buckets => shaft + + // §R-MERGE/§R-REJECT: whole-building real wall list (ifc_class LIKE 'IfcWall%' only -- NOT the + // wider WALL_LIKE raster set floodRooms uses -- with z). [cx,cy,cz,bx,by,bz] arrays. + function allWallsRaw(db) { + var rows = _rows(db, "SELECT t.center_x cx,t.center_y cy,t.center_z cz,COALESCE(t.bbox_x,0) bx," + + "COALESCE(t.bbox_y,0) by2,COALESCE(t.bbox_z,0) bz " + + "FROM elements_meta m JOIN element_transforms t ON t.guid=m.guid " + + "WHERE m.ifc_class LIKE 'IfcWall%' AND m.discipline='ARC' AND t.center_x IS NOT NULL"); + return rows.map(function (r) { return [r.cx, r.cy, r.cz, r.bx, r.by2, r.bz]; }); + } + + // §STAIRWELL-STACK: whole-building stair/ramp footprints WITH z ([cx,cy,cz,bx,by]) — the + // vertical-stack test needs distinct z-levels, which the per-storey stairs list drops. + function allStairsZ(db) { + var cond = STAIR_LIKE.map(function (p) { return "m.ifc_class LIKE '" + p + "'"; }).join(' OR '); + var rows = _rows(db, "SELECT t.center_x cx,t.center_y cy,t.center_z cz," + + "COALESCE(t.bbox_x,0) bx,COALESCE(t.bbox_y,0) by2 " + + "FROM elements_meta m JOIN element_transforms t ON t.guid=m.guid " + + "WHERE (" + cond + ") AND m.discipline='ARC' AND t.center_x IS NOT NULL"); + return rows.map(function (r) { return [r.cx, r.cy, r.cz, r.bx, r.by2]; }); + } + + // §STAIRWELL-STACK: drop pockets that are vertical stair shafts (see constants above). + function rejectStairwell(rooms, stairsZ) { + var out = []; + rooms.forEach(function (r) { + var bb = _roomBbox(r); + var x0 = bb[0], y0 = bb[1], x1 = bb[2], y1 = bb[3]; + var area = Math.max(1e-6, (x1 - x0) * (y1 - y0)); + var cum = 0, levels = {}; + stairsZ.forEach(function (s) { + var ox = Math.max(0, Math.min(x1, s[0] + s[3] / 2) - Math.max(x0, s[0] - s[3] / 2)); + var oy = Math.max(0, Math.min(y1, s[1] + s[4] / 2) - Math.max(y0, s[1] - s[4] / 2)); + var o = ox * oy; + if (o > 0.01) { cum += o; levels[Math.round((s[2] || 0) / 2)] = 1; } + }); + if (cum / area >= STAIRWELL_STACK_REJECT && + Object.keys(levels).length >= STAIRWELL_STACK_MIN_LEVELS) return; + out.push(r); + }); + return out; + } + + // §R-MERGE: whole-building real door centers (with z) for the seam door-block test. + function allDoorsRaw(db) { + var rows = _rows(db, "SELECT t.center_x cx,t.center_y cy,t.center_z cz " + + "FROM elements_meta m JOIN element_transforms t ON t.guid=m.guid " + + "WHERE m.ifc_class LIKE 'IfcDoor%' AND m.discipline='ARC' AND t.center_x IS NOT NULL"); + return rows.map(function (r) { return [r.cx, r.cy, r.cz]; }); + } + + function _wallThickness(walls) { + var ts = []; + walls.forEach(function (w) { var t = Math.min(w[3], w[4]); if (t > 0.01) ts.push(t); }); + ts.sort(function (a, b) { return a - b; }); + return ts.length ? ts[Math.floor(ts.length / 2)] : 0.0; + } + + function _unionLen(segs) { + if (!segs.length) return 0.0; + var s = segs.slice().sort(function (a, b) { return a[0] - b[0]; }); + var tot = 0.0, lo = s[0][0], hi = s[0][1]; + for (var i = 1; i < s.length; i++) { + var a = s[i][0], b = s[i][1]; + if (a > hi) { tot += hi - lo; lo = a; hi = b; } + else { hi = Math.max(hi, b); } + } + return tot + (hi - lo); + } + + function _roomBbox(r) { + var xs0 = Infinity, xs1 = -Infinity, ys0 = Infinity, ys1 = -Infinity; + r.rects.forEach(function (rc) { + xs0 = Math.min(xs0, rc.cx - rc.sx / 2); xs1 = Math.max(xs1, rc.cx + rc.sx / 2); + ys0 = Math.min(ys0, rc.cy - rc.sy / 2); ys1 = Math.max(ys1, rc.cy + rc.sy / 2); + }); + return [xs0, ys0, xs1, ys1]; + } + + function _sharedEdge(ax0, ay0, ax1, ay1, bx0, by0, bx1, by1, gapTol) { + var ox = Math.min(ax1, bx1) - Math.max(ax0, bx0); + var oy = Math.min(ay1, by1) - Math.max(ay0, by0); + var gapy = Math.max(ay0, by0) - Math.min(ay1, by1); + var gapx = Math.max(ax0, bx0) - Math.min(ax1, bx1); + if (ox > 0 && gapy >= 0 && gapy <= gapTol) { + var lo = Math.max(ax0, bx0), hi = Math.min(ax1, bx1); + var ymid = (Math.min(ay1, by1) + Math.max(ay0, by0)) / 2; + return { axis: 'x', lo: lo, hi: hi, mid: ymid, slen: ox, frac: ox / Math.min(ax1 - ax0, bx1 - bx0) }; + } + if (oy > 0 && gapx >= 0 && gapx <= gapTol) { + var lo2 = Math.max(ay0, by0), hi2 = Math.min(ay1, by1); + var xmid = (Math.min(ax1, bx1) + Math.max(ax0, bx0)) / 2; + return { axis: 'y', lo: lo2, hi: hi2, mid: xmid, slen: oy, frac: oy / Math.min(ay1 - ay0, by1 - by0) }; + } + return null; + } + + // §R-MERGE: union same-storey pockets whose shared seam is wall-free (no real wall backing the + // boundary => a synthetic flood-fill/door-partition split, not an architectural wall). `walls`/ + // `doorsXyz` are whole-building lists; the pairwise test itself only ever compares same-storey + // rooms (the caller passes one storey's room list at a time). + function mergeRooms(rooms, walls, doorsXyz) { + var n = rooms.length; + if (n < 2) return rooms; + var wallT = _wallThickness(walls); + var gapTol = MERGE_GAP_TOL_FACTOR * wallT; + var boxes = rooms.map(_roomBbox); + var parent = []; for (var p = 0; p < n; p++) parent.push(p); + function find(x) { while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x]; } return x; } + var merges = 0; + for (var i = 0; i < n; i++) { + for (var j = i + 1; j < n; j++) { + if (find(i) === find(j)) continue; + var ab = boxes[i], bb = boxes[j]; + var se = _sharedEdge(ab[0], ab[1], ab[2], ab[3], bb[0], bb[1], bb[2], bb[3], gapTol); + if (!se) continue; + if (se.frac < MERGE_SHARE_MIN) continue; + var zlo = Math.min(rooms[i].cz - rooms[i].sz / 2, rooms[j].cz - rooms[j].sz / 2); + var zhi = zlo + 3.0; + var segs = []; + for (var w = 0; w < walls.length; w++) { + var wl = walls[w], wcz = wl[2]; + if (!(zlo - 1 <= wcz && wcz <= zhi + 1)) continue; + var wx0 = wl[0] - wl[3] / 2, wx1 = wl[0] + wl[3] / 2; + var wy0 = wl[1] - wl[4] / 2, wy1 = wl[1] + wl[4] / 2; + if (se.axis === 'x') { + if (wy0 - WALL_TOL <= se.mid && se.mid <= wy1 + WALL_TOL) { + var s0 = Math.max(wx0, se.lo), s1 = Math.min(wx1, se.hi); + if (s1 > s0) segs.push([s0, s1]); + } + } else { + if (wx0 - WALL_TOL <= se.mid && se.mid <= wx1 + WALL_TOL) { + var s0b = Math.max(wy0, se.lo), s1b = Math.min(wy1, se.hi); + if (s1b > s0b) segs.push([s0b, s1b]); + } + } + } + var cover = se.slen > 0 ? _unionLen(segs) / se.slen : 1.0; + if (cover > MERGE_WALL_COVER_MAX) continue; + var doorHere = false; + for (var d = 0; d < doorsXyz.length; d++) { + var dd = doorsXyz[d], dcx = dd[0], dcy = dd[1], dcz = dd[2]; + if (!(zlo - 0.3 <= dcz && dcz <= zlo + 2.5)) continue; + if (se.axis === 'x' && se.lo <= dcx && dcx <= se.hi && Math.abs(dcy - se.mid) <= MERGE_DOOR_TOL) { doorHere = true; break; } + if (se.axis === 'y' && se.lo <= dcy && dcy <= se.hi && Math.abs(dcx - se.mid) <= MERGE_DOOR_TOL) { doorHere = true; break; } + } + if (doorHere) continue; + parent[find(i)] = find(j); merges++; + } + } + if (!merges) return rooms; + // §DETERMINISM: build groups keyed by find()-root, but iterate in FIRST-SEEN order (matching + // Python 3.7+ dict insertion-order semantics exactly) — plain Object.keys() would silently + // reorder to ASCENDING NUMERIC key order for integer-like string keys (JS's own property- + // enumeration rule for array-index-like keys), which is NOT the same as insertion order and + // was measured to desync guid assignment from the Python mirror (Hospital/Terminal parity + // witness caught it: same room COUNT, wrong room per guid). + var groups = {}, groupOrder = []; + for (var k = 0; k < n; k++) { + var f = find(k); + if (!groups[f]) { groups[f] = []; groupOrder.push(f); } + groups[f].push(k); + } + var out = []; + groupOrder.forEach(function (gk) { + var members = groups[gk]; + if (members.length === 1) { out.push(rooms[members[0]]); return; } + var mergedRects = []; + members.forEach(function (m) { mergedRects = mergedRects.concat(rooms[m].rects); }); + var totalArea = members.reduce(function (s, m) { return s + rooms[m].area; }, 0); + var rep = members.reduce(function (best, m) { return rooms[m].area > rooms[best].area ? m : best; }, members[0]); + var merged = {}; Object.keys(rooms[rep]).forEach(function (kk) { merged[kk] = rooms[rep][kk]; }); + merged.rects = mergedRects; + merged.area = totalArea; + merged.cx = mergedRects[0].cx; merged.cy = mergedRects[0].cy; + merged.sx = mergedRects[0].sx; merged.sy = mergedRects[0].sy; + merged.door_rescued = members.some(function (m) { return rooms[m].door_rescued; }); + merged.door_partitioned = members.some(function (m) { return rooms[m].door_partitioned; }); + merged.merged_from = members.length; + out.push(merged); + }); + return out; + } + + function _rectEnclosure(rx0, ry0, rx1, ry1, walls) { + var per = 2 * ((rx1 - rx0) + (ry1 - ry0)); + if (per <= 0) return 0.0; + var covered = 0.0; + ['N', 'S', 'E', 'W'].forEach(function (side) { + var segs = []; + if (side === 'N' || side === 'S') { + var y = side === 'N' ? ry1 : ry0; + walls.forEach(function (wl) { + var wy0 = wl[1] - wl[4] / 2, wy1 = wl[1] + wl[4] / 2; + if (wy0 - WALL_TOL <= y && y <= wy1 + WALL_TOL) { + var wx0 = wl[0] - wl[3] / 2, wx1 = wl[0] + wl[3] / 2; + var lo = Math.max(wx0, rx0), hi = Math.min(wx1, rx1); + if (hi > lo) segs.push([lo, hi]); + } + }); + } else { + var x = side === 'E' ? rx1 : rx0; + walls.forEach(function (wl) { + var wx0 = wl[0] - wl[3] / 2, wx1 = wl[0] + wl[3] / 2; + if (wx0 - WALL_TOL <= x && x <= wx1 + WALL_TOL) { + var wy0 = wl[1] - wl[4] / 2, wy1 = wl[1] + wl[4] / 2; + var lo = Math.max(wy0, ry0), hi = Math.min(wy1, ry1); + if (hi > lo) segs.push([lo, hi]); + } + }); + } + covered += _unionLen(segs); + }); + return covered / per; + } + + function _roomEnclosure(r, walls) { + var zlo = r.cz - r.sz / 2 - 1.5, zhi = r.cz + r.sz / 2 + 1.5; + var ws = walls.filter(function (w) { return zlo <= w[2] && w[2] <= zhi; }); + var totArea = 0; r.rects.forEach(function (rc) { totArea += rc.sx * rc.sy; }); + if (!totArea) totArea = 1.0; + var e = 0.0; + r.rects.forEach(function (rc) { + var rx0 = rc.cx - rc.sx / 2, rx1 = rc.cx + rc.sx / 2; + var ry0 = rc.cy - rc.sy / 2, ry1 = rc.cy + rc.sy / 2; + e += (rc.sx * rc.sy) * _rectEnclosure(rx0, ry0, rx1, ry1, ws); + }); + return e / totArea; + } + + // §R-REJECT: drop pockets whose enclosure (wall-backed fraction of their own perimeter) falls + // below REJECT_ENCLOSURE — an unbounded/exterior pocket, not a room. Only ever REMOVES rooms. + // Rooms in [REJECT_ENCLOSURE, SUSPECT_OPEN_ENCLOSURE) not already flagged suspect get newly + // flagged SUSPECT_OPEN here — an already-suspect room's existing reason is left untouched. + function rejectRooms(rooms, walls) { + var out = []; + rooms.forEach(function (r) { + var enc = _roomEnclosure(r, walls); + r.enclosure = enc; + if (enc < REJECT_ENCLOSURE) return; + if (enc < SUSPECT_OPEN_ENCLOSURE && !r.suspect) r.suspect = 'OPEN'; + out.push(r); + }); + return out; + } + // Per-storey compile pass (compile_rooms.py's main() loop, minus DB write). Returns // { report: [...], rooms: [...] } — report matches ROOM_WALKER_JS_PORT.md Task 3's required table // shape (building/count/method/status/total is assembled by the CALLER, which knows the building @@ -699,6 +967,11 @@ // footprints by XY (not per-storey). var allStairs = []; Object.keys(stairsBy).forEach(function (st) { allStairs = allStairs.concat(stairsBy[st]); }); + // §R-MERGE/§R-REJECT: whole-building wall/door lists (not the per-storey raster set). + var allWallsRawList = allWallsRaw(db); + var allDoorsRawList = allDoorsRaw(db); + var allStairsZList = allStairsZ(db); // §STAIRWELL-STACK + var mergedTotal = 0, rejectedTotal = 0; var allrooms = [], report = [], stZ = {}; Object.keys(wallsBy).sort().forEach(function (st) { @@ -719,6 +992,15 @@ rooms = roomsFlood; method = 'flood-fill'; } + // §R-MERGE then §R-REJECT (ordering per spec: merge first, reject sees the post-merge shape). + var preMergeN = rooms.length; + rooms = mergeRooms(rooms, allWallsRawList, allDoorsRawList); + var mergedN = preMergeN - rooms.length; + var preRejectN = rooms.length; + rooms = rejectRooms(rooms, allWallsRawList); + rooms = rejectStairwell(rooms, allStairsZList); // §STAIRWELL-STACK, after R-REJECT + var rejectedN = preRejectN - rooms.length; + mergedTotal += mergedN; rejectedTotal += rejectedN; var rescued = rooms.filter(function (r) { return r.door_rescued; }).length; var partitioned = rooms.filter(function (r) { return r.door_partitioned; }).length; var suspects = rooms.filter(function (r) { return r.suspect; }).length; @@ -743,7 +1025,9 @@ var doorRescuedTotal = allrooms.filter(function (r) { return r.door_rescued; }).length; var doorPartitionTotal = allrooms.filter(function (r) { return r.door_partitioned; }).length; var suspectTotal = allrooms.filter(function (r) { return r.suspect; }).length; - return { report: report, rooms: allrooms, stZ: stZ, total: total, doorRescuedTotal: doorRescuedTotal, doorPartitionTotal: doorPartitionTotal, suspectTotal: suspectTotal }; + return { report: report, rooms: allrooms, stZ: stZ, total: total, doorRescuedTotal: doorRescuedTotal, + doorPartitionTotal: doorPartitionTotal, suspectTotal: suspectTotal, + mergedTotal: mergedTotal, rejectedTotal: rejectedTotal }; } // Persist a compileRooms() result into spatial_structure + rel_contained_in_space (the --write @@ -836,7 +1120,7 @@ var compiled = compileRooms(db); var result = { report: compiled.report, total: compiled.total, doorRescuedTotal: compiled.doorRescuedTotal, doorPartitionTotal: compiled.doorPartitionTotal, - suspectTotal: compiled.suspectTotal }; + suspectTotal: compiled.suspectTotal, mergedTotal: compiled.mergedTotal, rejectedTotal: compiled.rejectedTotal }; if (opts.write) { var w = writeRooms(db, compiled); result.roomsWritten = w.roomsWritten; result.rectRowsWritten = w.rectRowsWritten; result.relWritten = w.relWritten; @@ -849,9 +1133,13 @@ doorStats: doorStats, storeyZAnchors: storeyZAnchors, doorAdjacent: doorAdjacent, stairOverlapFrac: stairOverlapFrac, floodRooms: floodRooms, partitionByDoors: partitionByDoors, + mergeRooms: mergeRooms, rejectRooms: rejectRooms, allWallsRaw: allWallsRaw, allDoorsRaw: allDoorsRaw, compileRooms: compileRooms, writeRooms: writeRooms, walk: walk, RES: RES, MIN_AREA: MIN_AREA, DOOR_SHORTFALL_RATIO: DOOR_SHORTFALL_RATIO, - VERT_FACTOR: VERT_FACTOR, OPEN_PERIM_FACTOR: OPEN_PERIM_FACTOR + VERT_FACTOR: VERT_FACTOR, OPEN_PERIM_FACTOR: OPEN_PERIM_FACTOR, + MERGE_GAP_TOL_FACTOR: MERGE_GAP_TOL_FACTOR, MERGE_SHARE_MIN: MERGE_SHARE_MIN, + MERGE_WALL_COVER_MAX: MERGE_WALL_COVER_MAX, MERGE_DOOR_TOL: MERGE_DOOR_TOL, WALL_TOL: WALL_TOL, + REJECT_ENCLOSURE: REJECT_ENCLOSURE, SUSPECT_OPEN_ENCLOSURE: SUSPECT_OPEN_ENCLOSURE }; ROOT.RoomWalker = API; if (typeof module !== 'undefined' && module.exports) module.exports = API; diff --git a/docs/BIMUserGuide.md b/docs/BIMUserGuide.md index 3ba87fd20..e82b65135 100644 --- a/docs/BIMUserGuide.md +++ b/docs/BIMUserGuide.md @@ -122,7 +122,8 @@ All panels collapse with **−/+**. - Click any element → IFC class, GUID, storey, discipline, material - Fly-tour — auto-orbits rendered buildings, click to stop - Indoor walk-through — follows IfcSpace/door graph through the building -- X-Ray mode (Alt+Z) — transparent view, see structure through walls +- X-Ray mode (Alt+Z) — a 3-state cycle: **Off → X-Ray → Bounding Boxes → Off**. X-Ray is the transparent + see-through-walls view; Bounding Boxes swaps that for each element's envelope box instead — press again to cycle - Measure tool — tap two points, get distance in metres - Section cut — horizontal clip plane, slider to cut through floors - Storey filter — isolate a single floor @@ -191,13 +192,26 @@ has that kind of data (no data, no empty axis): |------|----------------|----------------| | **Storey** | Always | Elements grouped by building level/storey. Expand a storey to see the rooms/spaces on it (or, if the building has no room data, its most common IFC classes). Tapping a storey or room isolates it in the 3D view. | | **Discipline** | Always | Elements grouped by discipline (ARC/STR/MEP/ELEC, etc). Expand a discipline to see its IFC classes; tap a class to highlight just those elements. | -| **Room** | Only if the building has volumetric room (IfcSpace) data | A highlight lens: the model is X-rayed and a translucent box is drawn over each room; tap a room to zoom to it. Has its own **Storey / Type** sub-toggle to group rooms by floor or by room type. (On a building without volume data, this falls back to a plain isolate-by-room list instead.) | +| **Room** | Only if the building has volumetric room (IfcSpace) data | A highlight lens: the model is X-rayed and a translucent box is drawn over each room. Has its own **Storey / Type / Path** sub-toggle: group rooms by floor, by the compiler's own confidence tier (see [Room health](#room-health-the-type-sub-toggle-verified-live-on-terminal) below), or route between two rooms the way a person walks. (On a building without volume data, this falls back to a plain isolate-by-room list instead.) | | **Material** | Only if material data is present | Elements grouped by material name, or — via a **Material / Category** sub-toggle — by a derived construction category (Concrete, Metal, Wood, Glass, Drywall/Partition, Masonry, Insulation, Tile, Finish, Membrane, Flooring, Generic, Other). Categories are a keyword-derived heuristic, not an extracted IFC property, and are labelled accordingly in the panel. Highlight lens, same X-ray-and-box behavior as Room. | | **Phase** | Only if a construction timeline can be generated for the building | Elements grouped by construction phase/task, generated on the fly (a short "Timeline generating…" message appears first). | | **Parts** *(new)* | Only if the building has stairway, lift-shaft, or plant-room elements | Elements grouped into up to three building-part categories: **Stairway** (stair/ramp classes), **Lift Shaft** (elements named for lifts/elevators), **Plant Room** (HVAC-plant elements — vents, ducts, fans, AHUs, dampers, chillers, pumps). Each category is itself data-gated — it only appears if the building actually has a match. Tapping a category, or a single item inside it, isolates it in the 3D view (hides the rest of the model). | ![The axis toggle cycled to Parts on HHS Office ("6/6 Parts"), a real institutional-scale building showing all three categories at once: Stairway (20), Lift Shaft (3), and Plant Room (1769) — Plant Room only appears on complex-class buildings like this one, never on a residential building like Duplex](img/viewer/find-axis-parts.png) +**Room highlight, verified live on HHS Office.** Level 2 alone compiles 31 real rooms (105 across the +whole building) — tapping one X-rays the model and draws a clean, correctly-bounded translucent box +over just that room, confirming the room's geometry is well-formed and doesn't overlap its neighbours. +On a larger building, the biggest rooms on a floor stand in for a "hall"-scale space until a real +labelled corridor/hall example is captured (HHS's own rooms carry no such label yet — a future guide +pass). + +![A single real room on HHS Office Level 2, X-rayed and highlighted as a clean translucent purple box against the surrounding structure — SAMPLE, HHS_Office_Federated data](img/viewer/find-room-highlight-hhs.png) + +*Known gap, not glossed over:* tapping a room does not currently reframe the camera to it (confirmed +live, 2026-07-12) — the highlight is accurate, but you may need to manually orbit/zoom to see a small +room clearly on a large building. Tracked for a future fix. + > The Parts axis is the newest addition (bim-ootb `d04ddd5`) — see > `prompts/VIEWER_FIND_PANEL_PARTS_VERIFICATION.md` for its live verification on Duplex/SampleCastle data. > A false-positive/missing-class-gate bug found after that verification (`prompts/FIND_PANEL_PLANT_ROOM_GATE_FIX.md`) @@ -217,6 +231,167 @@ height) is only added by the plain-search code path, not the NL-query path — s actually appears while typing a recognized phrase; the query still runs correctly on **Enter**. The guide text above describes the observed (silent) behavior, not an invented visible hint. +#### Room health — the Type sub-toggle (verified live on Terminal) + +Switch the Room axis's grouping from **Storey** to **Type** and rooms are grouped by the compiler's +own confidence in them, not by a floor: **INTERNAL** / **INTERNAL_SMALL** (ordinary enclosed rooms, +split by size) and **SUSPECT_OPEN** / **SUSPECT_NO_DOOR** (rooms the compiler could bound +geometrically but flags for a human rather than silently accepting). This is the compile-with-honesty +principle made visible — a low-confidence room is *shown* as low-confidence, never quietly promoted +to "room" just because it fits inside walls. + +**Terminal, the hard case, verified live.** A user-reported screenshot showed a stairwell counted as +a room — the compiler doesn't know "stairwell" as a concept, only geometry, and a tall vertical shaft +can look room-shaped from a single floor's footprint alone. The fix (**STAIRWELL-STACK reject**) +rejects a room candidate whose footprint recurs, stacked, through multiple storeys with a real stair +inside it — the signature of a shaft, not a room — checked against both compiler mirrors, 6/6 parity. +Reloading Terminal applies the healed patch over whatever the browser had cached already: **59 rooms → +40 rooms, 73 rects, no room anywhere near the shaft threshold** — the stairwell is gone from both the +Room list and the 3D view. + +![Terminal, Type grouping open: INTERNAL (30), INTERNAL_SMALL (15), SUSPECT_OPEN (22), SUSPECT_NO_DOOR (6) — "Aras 02 R3" selected, an empty doorless pocket beside a stair flight, flagged rather than guessed into being a room](img/viewer/type-suspect-no-door-terminal.png) + +That SUSPECT_NO_DOOR pick is the demo frame: an empty pocket next to the stair, no door found +bounding it, so the compiler says exactly what it knows and stops — it doesn't invent a door to make +the room look finished. + +*Known gap, named not hidden:* Type is a **health** taxonomy (how sure the compiler is this is a +room), not a **semantic** one — it doesn't yet know "corridor" from "office." Terminal's concourses +show no CORRIDOR band today because the only measured corridor template on file is Duplex's 10.4m² +hallway (n=2 samples), and stretching that onto a terminal concourse would be inventing, not +compiling. The room-path routing below already produces a building-relative, *measurable* definition +of a corridor — the elongated, many-doored room every route keeps passing through — scoped as a +future CIRCULATION_DISPLAY pass, not built yet. + +#### Room-to-room paths & escape routes + +With the **Room** axis selected, the **Path** sub-mode routes between any two rooms the way a +person actually walks — out the door, along the corridor or concourse, and up or down the stairs +when the two rooms are on different floors. The route draws as a line through the real doors and +stair flights it uses, and the rooms along the way stay highlighted. + +*Verified live (2026-07-12):* a real route across Terminal's floors returns several stair-crossing +legs in sequence, not a single best-guess hop — each leg names the room and the door or stair flight +it passes through. + +*Future feature — fire escape:* the same routing will pin a **Fire Escape** entry at the top of the +path list — one tap from any room to the nearest building exit. + +*Future feature — mobile:* scan a QR code posted beside a door to fetch that building's lightweight +architecture model on your phone and see the escape route in Walk mode from exactly where you stand. + +### The rest of the Navigate drawer — World History, Page History & Home + +The **Navigate** drawer (sailboat icon — see [Find panel](#find-panel-search-voice-query-and-axis-lenses) +above for how to open it) has three more rows besides Find / Navigate: + +- **World History** (shortcut **W**) — a cross-page timeline: every significant action across *every* + page — Viewer, ERP, Gravity — in one place. Opens a card with a **Whole / This page** toggle, day-by-day + navigation (**‹ day** / **day ›**), and one entry per action (what happened, where, and when). + ![The World History card open — "Whole" scope, Jul 12 selected, one "Opened Ifc2x3_Duplex_Federated" entry from the Viewer](img/viewer/pill-world-history.png) +- **Page History** (shortcut **Z**) — this page's own compact dot-timeline, a small step-back/step-forward + bar. It only lights up once you've made edits in this session — a fresh session shows an empty strip + (the pair of arrows either side of a single dot), as captured here. + ![The Page History bar — a fresh session, no edits yet to step through](img/viewer/pill-page-history.png) +- **Home** — returns to the front-door hub (the same page the [Quick Start](#quick-start-your-first-building) + walkthrough starts from). Installed as a standalone app (PWA), it opens the live hub online, or falls + back to the cached hub offline. + +### Inspect drawer — Measure, Clash, X-Ray, Section, Time Machine, 4D/5D, Fly Tour + +The **Inspect** drawer (compass icon, next to Navigate on the toolbar rail) bundles seven tools behind +one icon: + +![The Inspect drawer open — Measure, Clash Matrix, X-Ray / Bbox (currently "Off"), Section Cut, Time Machine, 4D / 5D, and Fly Tour, each with its shortcut key](img/viewer/pill-inspect-drawer.png) + +- **Measure**, **X-Ray / Bbox**, **Section Cut**, and **Fly Tour** are covered above, under + [Viewer Features](#viewer-features). +- **Clash Matrix** (key **C**) opens the clash-detection engine (discipline-pair grid, tolerance, Review / + Resolve / Accept status, HTML + CSV export) — full coverage: **[Clash Detection guide](CLASH_DETECTION.md)**. +- **Time Machine** (key **T**) opens the 4D construction timeline — author a schedule, play it back, + try a What-if slip, share a `?tm=play` link. Full authoring/playback walkthrough already lives in + **[Kernel-ERP User Guide → Time Machine](ERPUserGuide.md)** (not re-documented here — same building, + same feature, reached from either app). +- **4D / 5D** (key **4**) opens the analytics dashboard (`boq_charts.html`) for the loaded building in a + new tab — full coverage: **[4D/5D Analysis guide](4D5DAnalysis.md)**. + +### Camera / View drawer + +The **Camera / View** drawer (camera icon) bundles three camera-control toggles: + +![The Camera / View drawer open — Precision (Fine), Reset Camera, and Auto-Pivot, each with its shortcut key](img/viewer/pill-camview-drawer.png) + +- **Precision (Fine)** (Caps Lock) — slows orbit/pan/zoom for fine, deliberate camera moves (e.g. lining + up a screenshot or a measurement). +- **Reset Camera** (key **A**) — snaps the camera back to its default orbit position. +- **Auto-Pivot** (key **Q**) — toggles automatic pivot-point recentring as you orbit, so the camera keeps + turning around whatever's in view instead of a fixed point. + +### Display options — Palette, Night, Shadow + Ground, Background, Sound FX + +The **Palette** pill (key **P**) opens one panel for every visual-appearance control — five lighting +sliders, plus four more toggles appended below them: + +![The Display options panel — Ambience/Sun/Exposure/Ambient/Hemisphere sliders, then Night, Shadow + Ground (3 texture swatches), Background, and Sound FX rows](img/viewer/pill-display-options.png) + +| Control | Shortcut | What it does | +|---|---|---| +| Ambience / Sun / Exposure / Ambient / Hemisphere | — | Five sliders — overall scene lighting, sun intensity, camera exposure, ambient fill light, and sky/ground hemisphere light. | +| **Night** | **N** | Toggles a night lighting preset. | +| **Shadow + Ground** | **H** | Cycles **Off → Grass → Earth → Paved** — a real ground-texture swatch under the building, with matching shadows. | +| **Background** | **B** | Reverses the background (dark ↔ light/white). | +| **Sound FX** | **V** | Toggles synthesized UI/Time-Machine/Fly-Tour sound cues — no audio files, off by default. | + +### Settings + +The **Settings** pill (key **=**) opens a panel with four sections: + +![The Settings panel's "Edit Project JSON" section expanded — Corporate/Branding, Grid Rules, Clash Rules, ERP Globe Bubbles, Sound Effects, and 4D Schedule (this building), plus the collapsed 5D Rate Pack and Cache Info sections below](img/viewer/pill-settings-json-hub.png) + +- **Pill Icons** — show/hide/reorder every toolbar action, and see each one's current shortcut key at a + glance (this is also how a hidden action like a data-gated drawer row becomes visible once its data + exists). A **Reset Pill Icons** button restores the defaults. +- **Edit Project JSON** — a power-user hub: open and edit any of the project's config files directly + in-browser (auto-inferred form fields, not raw text), then **Download** the edited file to commit back + to the repo, or **Reset** to discard the override. Six files are registered: **Corporate / Branding**, + **Grid Rules**, **Clash Rules**, **ERP Globe Bubbles**, **Sound Effects** (the audio *parameters* file — + distinct from the Display-options Sound FX on/off toggle above), and a **read-only** view of the + **4D Schedule** captured for the currently-open building (the same data Time Machine authors). +- **5D Rate Pack** — pick which cost-rate pack is active (the same rate pack the Find panel's + `total cost` query and the 4D/5D dashboard both price against). +- **Cache Info** — see how much this building's data is using in IndexedDB, and clear it. + + ![The full Settings panel, Pill Icons section open — every toolbar action listed with its visibility and shortcut](img/viewer/pill-settings-panel.png) + +### Save & Open a building + +Two toolbar pills, both native-dialog verbs — distinct from the Hub's building-open flow in +[Quick Start](#quick-start-your-first-building): + +![The Save and Open pill icons on the toolbar rail](img/viewer/pill-save-open.png) + +- **Save Building** (**Ctrl+S**) — saves the currently open building, including any session edits + (clash resolutions, captured 4D schedule, etc.), to a `.db` file via the browser's native Save As dialog. +- **Open Building** (**Ctrl+O**) — opens a previously-saved `.db` file via a native Open dialog, replacing + the current scene. + +### Share + +The **Share** pill (key **/**) is a step up from the plain deep-link URL: on mobile, it hands the current +view to the device's native share sheet with a snapshot photo attached; on desktop (no native share API), +it shows a preview card — a live snapshot, the building name, the same deep-link URL described above, and +**Copy Link** / **Cancel** buttons. If a clash is open when you tap Share, the shared text and photo are +about that specific clash instead of the general view. + +![The desktop Share preview card — a live canvas snapshot, the building name, the shareable deep-link URL, and Copy Link / Cancel](img/viewer/pill-share-preview.png) + +### Pick Walk — warehouse / logistics buildings + +A data-gated pill (only appears when the loaded building carries locator-GUID bins, e.g. a warehouse +building like GardenWorld) that walks a picking route over the bins: fly to the next bin in order, scan +a bin's QR/type code, and record a signed pick group per bin. Not covered further here — it needs a +warehouse-class building loaded to demonstrate, outside this general viewer guide's scope. + ### FM / Operate lenses — HR_BIM_Asset *(ALPHA)* @@ -256,6 +431,7 @@ and dashboard graphs — off by default, pixel-identical until you turn it on. | **Roof** | Roof plan view | | **Alt+Z** | Toggle X-ray mode | | **F11** | Toggle fullscreen | +| **F1** | Help — the full, live list of every toolbar action and its shortcut key | > Authoring — editing the structural grid, sketching, extruding — lives in the **[DAGeVu Modeller](ModellerGuide.md)**, not the Viewer. diff --git a/docs/ModellerGuide.md b/docs/ModellerGuide.md index fe738434e..9e11bab2d 100644 --- a/docs/ModellerGuide.md +++ b/docs/ModellerGuide.md @@ -341,13 +341,17 @@ it: it places that trade's elements at the **measured cadence** of a real coordi runs it can, gates the clashes, and **honestly refuses** when the building has nothing to hang the trade on. Nothing is invented — every placement uses a spacing/clearance rule *mined from a real IFC model*. -![Walk · ELEC — the walker placed 267 electrical fixtures across the Duplex at the measured residential cadence](img/modeller/walk-fixtures.png) - -*Why the fixtures render as plain blocks:* a walked placement is real (its position and count are exact, -mined from a real building), but its **mesh** is still a stand-in — a box sized to that fixture class's own -measured median dimensions, not a finished fixture model. That's a deliberate, honest choice, not a bug: -the alternative would be guessing at a fixture's real shape, which this project's non-invent rule forbids. -A finer mesh swap for these is on the roadmap; today, trust the placement and count, not the silhouette. +![Walk · ELEC — 102 electrical fixtures placed across 19/21 real spaces in the Duplex; the building reads as one clean, well-formed shell from outside because every fixture landed correctly INSIDE the envelope](img/modeller/walk-fixtures.png) + +*Why you can't see any fixtures in this shot:* a real electrical outlet or light lives inside a room, not +poking through an exterior wall — from outside the sealed shell it's naturally occluded, same as it would +be in a real building. This is the corrected view (2026-07-12): a previous version of this pipeline had a +containment bug where roughly a quarter of placements landed outside the building's own walls — fixed +(mesh-recovered true-midpoint host binding) and independently verified 5 separate ways (containment count, +real-oracle walk-back match, measured-pattern conformance, wall-clearance margin, mirror-symmetry residual +on the Duplex's own A/B twin layout) before this screenshot was retaken. The fixture mesh itself is still a +box stand-in sized to each class's own measured dimensions, not a finished fixture model — a deliberate, +honest choice: guessing at a fixture's real shape is exactly what this project's non-invent rule forbids. **One engine, two standards.** A single walker drives every discipline; the discipline is just a data filter. It carries two measured rule-sets and auto-selects by building class: @@ -445,7 +449,7 @@ After walking a discipline, route its **service trunk** from a real entry. A corridor-aware trunk is routed from that entry through the walked fixtures — around walls, through real doors, up risers between storeys. -![The walked ELEC fixtures the trunk routes through — same 267-placed, pre-route state as the popup above; the routed trunk itself isn't captured here yet](img/modeller/seedtrunk-trunk.png) +![The Duplex after Route ▶ — a real ELEC trunk is now rendered (0→3,922 segments, verified by framebuffer diff), threaded through the walked fixtures; from this angle the trunk itself is a thin line hugging the interior wall, but the important thing this corrected view proves is that nothing renders outside the building anymore](img/modeller/seedtrunk-trunk.png) > **Deeper proof.** The full mining, round-trip, boundary and generalization analysis lives in the resume > cards `prompts/RESUME_DX_MEP_RESIDENTIAL_STANDARD.md`, `RESUME_TERMINAL_RULE_MINING.md` and diff --git a/docs/img/modeller/seedtrunk-trunk.png b/docs/img/modeller/seedtrunk-trunk.png index 89521552d..e9b516702 100644 Binary files a/docs/img/modeller/seedtrunk-trunk.png and b/docs/img/modeller/seedtrunk-trunk.png differ diff --git a/docs/img/modeller/walk-fixtures.png b/docs/img/modeller/walk-fixtures.png index 6a02659a2..d275904d2 100644 Binary files a/docs/img/modeller/walk-fixtures.png and b/docs/img/modeller/walk-fixtures.png differ diff --git a/docs/img/viewer/find-room-highlight-hhs.png b/docs/img/viewer/find-room-highlight-hhs.png new file mode 100644 index 000000000..6e21d14fb Binary files /dev/null and b/docs/img/viewer/find-room-highlight-hhs.png differ diff --git a/docs/img/viewer/pill-camview-drawer.png b/docs/img/viewer/pill-camview-drawer.png new file mode 100644 index 000000000..7e7ada752 Binary files /dev/null and b/docs/img/viewer/pill-camview-drawer.png differ diff --git a/docs/img/viewer/pill-display-options.png b/docs/img/viewer/pill-display-options.png new file mode 100644 index 000000000..2ccdad5ec Binary files /dev/null and b/docs/img/viewer/pill-display-options.png differ diff --git a/docs/img/viewer/pill-inspect-drawer.png b/docs/img/viewer/pill-inspect-drawer.png new file mode 100644 index 000000000..933e38932 Binary files /dev/null and b/docs/img/viewer/pill-inspect-drawer.png differ diff --git a/docs/img/viewer/pill-page-history.png b/docs/img/viewer/pill-page-history.png new file mode 100644 index 000000000..0b667a6b6 Binary files /dev/null and b/docs/img/viewer/pill-page-history.png differ diff --git a/docs/img/viewer/pill-save-open.png b/docs/img/viewer/pill-save-open.png new file mode 100644 index 000000000..1acae47ca Binary files /dev/null and b/docs/img/viewer/pill-save-open.png differ diff --git a/docs/img/viewer/pill-settings-json-hub.png b/docs/img/viewer/pill-settings-json-hub.png new file mode 100644 index 000000000..7b3cff4a3 Binary files /dev/null and b/docs/img/viewer/pill-settings-json-hub.png differ diff --git a/docs/img/viewer/pill-settings-panel.png b/docs/img/viewer/pill-settings-panel.png new file mode 100644 index 000000000..46a4ffc9b Binary files /dev/null and b/docs/img/viewer/pill-settings-panel.png differ diff --git a/docs/img/viewer/pill-share-preview.png b/docs/img/viewer/pill-share-preview.png new file mode 100644 index 000000000..aac1dcf7f Binary files /dev/null and b/docs/img/viewer/pill-share-preview.png differ diff --git a/docs/img/viewer/pill-world-history.png b/docs/img/viewer/pill-world-history.png new file mode 100644 index 000000000..d69897698 Binary files /dev/null and b/docs/img/viewer/pill-world-history.png differ diff --git a/docs/img/viewer/type-suspect-no-door-terminal.png b/docs/img/viewer/type-suspect-no-door-terminal.png new file mode 100644 index 000000000..f4704aa3e Binary files /dev/null and b/docs/img/viewer/type-suspect-no-door-terminal.png differ diff --git a/prompts/BIMUSERGUIDE_PILL_COVERAGE_AUDIT.md b/prompts/BIMUSERGUIDE_PILL_COVERAGE_AUDIT.md new file mode 100644 index 000000000..c5984963e --- /dev/null +++ b/prompts/BIMUSERGUIDE_PILL_COVERAGE_AUDIT.md @@ -0,0 +1,131 @@ + +# BIMUSERGUIDE PILL COVERAGE AUDIT — fill the missing toolbar features (2026-07-12, dispatch to Agent) + +``` +# ⚠ DO NOT REMOVE +SCOPE: bim-compiler `docs/BIMUserGuide.md` + `docs/img/viewer/*` only — a completeness pass, not a +rewrite. User's own words (2026-07-12): "I see Viewer guide lots of missing ie on the whole pill +list of other features besides Time machine ERP which can link back to ERP guide." Read this whole +file, triage first (see Task 1), then execute. Read the log after every run. PUSH PAUSE LIFTED for +this repo — commit locally, verify on localhost, push, no PR needed for docs-only (same pattern as +every docs commit this session — direct push to `fable/meshdb-livewire`, no separate branch/PR +ceremony for pure docs changes, that convention has held all session). +``` + +## Ground truth — the real pill list vs what's documented (confirmed via source, don't re-derive) +`common/pill_builder.js` + `viewer/panels.js` (bim-ootb) define these `id:` actions — the full real +toolbar/drawer surface: +`audio, background, bbox, cam-pivot, cam-reset, camview, clash, clash_rules, corporate, docHist, +find, fly, fullscreen, grid_rules, hbaFM, help, home, initbubble, inspect, issues, json-editor, +measure, navigate, night, open, palette, precision, report, save, schedule, section, settings, sfx, +shadow, share, tm, walk, whwalk, worldhist, xray` + +`docs/BIMUserGuide.md` currently documents, explicitly, only: X-Ray, Measure, Section-cut, Screenshot, +Storey filter, Discipline toggle, Deep-link URL, IndexedDB cache, City mode, Find panel (fully, just +added), FM/Operate lenses (cross-linked to `HRBIMAssetGuide.md`), Teams overlay (cross-linked to +`TeamsOverlayGuide.md`), and a mobile-only list. Roughly a dozen of ~40 real actions are covered. + +## Task 1 — triage every undocumented id BEFORE writing anything (don't blindly document all 40) +For each id not already covered, read its `fn`/behavior in `panels.js`/`pill_builder.js` and sort +into exactly one bucket: +1. **Real user-facing feature, needs guide coverage** (most of these, likely): `tm` (Time Machine), + `clash`/`clash_rules` (clash detection), `schedule`, `share`, `worldhist`/`docHist` (World/Doc + History — note this project already has extensive History-lane work, check + `project_whole_history_timeline.md`-style memory/prompts before writing from scratch), `walk` + (indoor walkthrough — may already be covered under "Viewer Features" bullets, verify), `whwalk`, + `save`/`open`, `night`/`shadow`/`background`/`palette` (visual/display options), `precision`, + `issues`, `report`, `sfx` (sound), `inspect`, `cam-pivot`/`cam-reset`/`camview`, `home`, + `fullscreen`. +2. **Already covered elsewhere, needs only a cross-link** (the user's own example): `tm` (Time + Machine) — `docs/ERPUserGuide.md` already has full authoring/playback/What-if coverage (lines + ~162-230+, confirmed this session). Do NOT re-document Time Machine in BIMUserGuide.md — add a + short pointer sentence + link, same pattern already used for FM/Operate lenses and Teams overlay + in this same doc (see how those two are handled — one paragraph + "Full walkthrough: [link]"). + Check EVERY id for this pattern before writing fresh prose — `worldhist`/`docHist` may also + already be covered by history-lane docs elsewhere, check before assuming it needs new content. +3. **Internal/dev-only, out of scope for an end-user guide** — `json-editor`, `corporate`, + `initbubble`, `grid_rules` look like dev/admin tooling by name alone, but VERIFY by reading what + they actually do before excluding (don't guess from the id string) — a wrong exclusion silently + drops a real feature, a wrong inclusion documents an internal tool as if it's for end users. +4. Report the full triage (id → bucket + one-line reason) as the FIRST thing committed, before any + prose/screenshot work — so the scope is visible and reviewable before the bulk of the work happens. + +## Task 2 — write + screenshot bucket-1 items, cross-link bucket-2 items +Same quality bar as every guide addition this session (see the Find panel section already in this +doc as the template): numbered step-by-step navigation (which pill, where, what it does), real +Playwright screenshots (`deviceScaleFactor:2`, tight clips, 0 console errors — not placeholders), +placed under `## Viewer Features` alongside the existing sub-sections. Group related small features +into one sub-section rather than one heading per pill where it reads better (e.g. `night`/`shadow`/ +`background`/`palette` are all "display appearance" toggles — one "Display options" sub-section with +a small table, not four separate headings). + +## Explicitly out of scope +- `docs/ModellerGuide.md` — separate doc, not this task (unless a bucket-3 review finds something + that's genuinely Modeller-only mislabeled here, name it, don't silently move it). +- Rewriting any ALREADY-documented section (X-Ray, Measure, Section-cut, Find panel, etc.) — this is + purely filling gaps, not a full-doc revision. +- Any code changes — this is docs-only, even if a triage step reveals a pill that seems broken or + oddly named; name it as a finding, don't fix it here. + +## DONE WHEN +Task 1's full triage table committed first (reviewable checkpoint). Every bucket-1 id has a real, +screenshotted section; every bucket-2 id has a one-sentence cross-link (no duplication); every +bucket-3 exclusion is justified with what was actually read, not guessed. Pushed to +`fable/meshdb-livewire` per this session's docs convention. + +## Task 1 — full triage table (2026-07-12, Agent) + +Read every id's real `fn`/behavior in `common/pill_builder.js` + `viewer/panels.js` (bim-ootb main, +verified fresh — `home`/`worldhist`/`docHist`/`walk` etc. all read directly, not guessed from the id +string) and cross-checked every candidate cross-link target actually exists and covers the topic +before citing it. Already-documented ids are listed too (no action) so the table is a complete audit, +not just the gap list. + +| id | Bucket | Reason (what was actually read) | +|---|---|---| +| `find` | 0 already documented | Full Find panel section, just added — no action. | +| `xray` | 0 already documented | X-Ray bullet + Keyboard cheat-sheet — no action. | +| `measure` | 0 already documented | Measure tool bullet — no action. | +| `section` | 0 already documented | Section cut bullet — no action. | +| `fly` | 0 already documented | "Fly-tour — auto-orbits…" bullet — no action. | +| `fullscreen` | 0 already documented | 3D Navigation table + Toolbar table + Keyboard cheat-sheet (3 places) — no action. | +| `hbaFM` | 0 already documented | FM/Operate lenses section, cross-linked to `HRBIMAssetGuide.md` — no action. | +| `walk` | 0 already documented | `A.toggleWalkMode` (walk.js) is the real GPS/device-orientation blue-dot mode, genuinely `platform:'mobile'`-gated — matches the existing "GPS Walk Mode — blue dot tracks position" mobile-only bullet exactly. No action. | +| `issues` | 0 already documented *(finding, not fixed)* | "Issue log" is in the Mobile-only bullet list. Read `A.toggleIssues` (issues.js) — it has **no** platform gate in code, works identically on desktop. Existing placement is technically incomplete, but rewriting that list is explicitly out of scope for this pass — flagging only. | +| `tm` | 2 cross-link | `docs/ERPUserGuide.md` lines ~162-230+ already has full Time Machine authoring/playback/What-if coverage (confirmed this session, per dispatch). Adding a pointer paragraph, not new prose. | +| `clash` | 2 cross-link | `A.export4D5D`/clash flow already linked from `docs/BIMUserGuide.md`'s Further Reading table → `CLASH_DETECTION.md`. That table-row link exists but is never tied to the actual toolbar pill (key `c`, lives in the Inspect drawer) anywhere in the doc body — adding an in-context pointer sentence where the Inspect drawer is introduced, not new clash-detection prose. | +| `report` | 2 cross-link | `A.export4D5D` (tools.js) opens `boq_charts.html` — confirmed this is the same analytics the Further Reading table already links as `4D5DAnalysis.md`. Same treatment as `clash`: in-context pointer in the Inspect drawer section. | +| `worldhist` | 1 needs coverage | No end-user doc exists anywhere (checked `docs/*.md` for "World History"/"worldhist" — zero hits outside the Find panel section's own drawer-row listing, which only NAMES the row, never describes what tapping it does). The `prompts/HISTORY_*.md` files are dev implementation specs, not end-user docs — not a valid cross-link target. Real gap. | +| `docHist` | 1 needs coverage | Same as `worldhist` — named as a Navigate-drawer row in the Find panel section, never functionally described. Real gap. | +| `home` | 1 needs coverage | Same pattern — named as a drawer row, `fn` (panels.js ~1277) never described (returns to the front-door hub, PWA-aware). Real gap. | +| `save` / `open` | 1 needs coverage | `A.saveModelDb`/`A.openModelDb` — real native-dialog save/restore of the current session's `.db` (distinct from the Hub's building-open flow already covered in Quick Start). Never mentioned anywhere in the doc. Real gap. | +| `share` | 1 needs coverage | `A.quickShare` (share.js) — a real, distinct pill: native share sheet (photo attached) on mobile, a copy/share preview card on desktop, plus clash-aware sharing when a clash is open. More than the existing "Deep-link URL" bullet describes (that bullet only names the underlying URL mechanism, not this pill/UI). Real gap. | +| `navigate` | 0 already documented | The Find panel section already fully documents opening this drawer — no action beyond the grouped `worldhist`/`docHist`/`home` mini-section above (its OTHER rows). | +| `inspect` | 1 needs coverage | Drawer master, never documented as an entry point. Bundles `measure`/`clash`/`xray`/`section`/`tm`/`report`/`fly` (`_inspectDrawer` subIds, panels.js ~1529) — most already covered individually; this section documents the drawer itself + carries the `tm`/`clash`/`report` cross-link pointers. | +| `camview` | 1 needs coverage | Drawer master, never documented. Bundles `precision`/`cam-reset`/`cam-pivot` (panels.js ~1530). | +| `cam-reset` | 1 needs coverage | `window.resetCamOrbit` — real, undocumented. Folds into the `camview` section. | +| `cam-pivot` | 1 needs coverage | `window.toggleCamPivot` (Auto-Pivot) — real, undocumented. Folds into the `camview` section. | +| `precision` | 1 needs coverage | `window.togglePrecisionFine` (Caps Lock) — fine-movement camera mode, undocumented. Folds into the `camview` section. | +| `palette` | 1 needs coverage | `A.toggleSunglass` opens a real panel: 5 lighting sliders (Ambience/Sun/Exposure/Ambient/Hemisphere) — verified by reading `A._buildSunglassPanel` (panels.js ~290-377). Container for the grouped "Display options" section below. | +| `night` / `shadow` / `background` / `audio` | 1 needs coverage, **grouped** | Verified via `_extendVisualFxPanel` (panels.js ~1536-1554): these 4 are literally appended as rows INSIDE the same Palette panel opened by `palette` (not separate panels) — Night toggle, Shadow+Ground 4-state cycle (Off→Grass→Earth→Paved), Background (white/dark reverse), Sound FX on/off. One "Display options" sub-section, per the prompt's own grouping instruction — this is the concrete case it was pointing at. | +| `settings` | 1 needs coverage | `_openSettingsPanel` (panels.js ~1559) — real panel: Pill Icons customizer (show/hide/reorder the toolbar), 5D Rate Pack picker, Cache Info + clear, Reset Pill Icons, and the "Edit Project JSON" hub. Never documented. | +| `json-editor` | 1 needs coverage | The "Edit Project JSON" hub itself (`_buildJsonHub`, panels.js ~1976) — a real end-user-reachable panel (via Settings, no auth wall/flag), NOT a hidden dev tool. Folds into the `settings` section as one described feature (its own bullet list is the 6 entries below). | +| `corporate` | 1 needs coverage, minor | Verified via `_jsonRegistry` (panels.js ~1962): "Corporate / Branding" — edits `corporate.json` (site branding) through the same reachable Settings→JSON hub. Real, not internal-only by the letter of "who can reach it" — but the audience is a project admin, not a day-to-day viewer, so it gets one bullet inside the `settings` section, not its own screenshot/heading. | +| `grid_rules` | 1 needs coverage, minor | Same hub, "Grid Rules" — edits `grid_rules.json`. Same treatment as `corporate`. | +| `clash_rules` | 1 needs coverage, minor | Same hub, "Clash Rules" — edits `clash_rules.json` (tolerance/rules used by the Clash Matrix). Same treatment. | +| `initbubble` | 1 needs coverage, minor | Same hub, "ERP Globe Bubbles" — edits `initbubble.json`. Same treatment. | +| `sfx` | 1 needs coverage, minor | Same hub, "Sound Effects" — edits `sfx.json` (synthesized-audio parameters). **Distinct from `audio`** (the on/off toggle inside the Display-options Palette panel) — this is the JSON tuning file behind it. Same treatment. | +| `schedule` | 1 needs coverage, minor | Same hub, "4D Schedule (this building)" — a **read-only** projection of the captured Time Machine schedule as JSON (`source:'db'`, `readonly:true`). Same treatment; also gets a one-clause mention alongside the `tm` cross-link since it's the same underlying data. | +| `whwalk` | 1 needs coverage, minor/niche | `WHWalk.toggle` — real, but `pill:false`-gated until the loaded building carries locator-GUID bins (warehouse/logistics buildings only, e.g. GardenWorld). No existing warehouse/logistics guide doc exists in `docs/` to cross-link. One short paragraph, no screenshot — a real building with this data would be a separate side-quest disproportionate to one pill in a general viewer guide; noting the scoping choice here rather than silently skipping it. | +| `bbox` | 1 needs coverage, minor (not a new section) | `window.toggleGhostXray` — confirmed absorbed into the SAME `xray` key/button as a 3-state cycle (Off→X-Ray→Bbox→Off, panels.js ~1230-1239 comment: "Alt+X retired"). No separate UI entry point exists any more. One clarifying sentence added to the *existing* X-Ray bullet (a gap-fill, not the "rewriting an already-documented section" this task excludes) rather than a new heading. | +| `help` | 1 needs coverage, minor (not a new section) | `showCommandPalette` (F1) — opens the live pill/shortcut list. One line added to the Keyboard & Mouse Cheat-Sheet section, not a dedicated heading. | + +**Bucket-3 (internal/dev-only, excluded) check:** every id the prompt flagged as a *guess* — +`json-editor`, `corporate`, `initbubble`, `grid_rules` — turned out, on actually reading the code, to +be reachable by any user through Settings with no auth wall or feature flag, i.e. genuinely bucket-1 +(minor), not bucket-3. **There is no bucket-3 exclusion in this audit** — nothing was found to be +truly hidden/dev-only among the 40 real ids. Named here explicitly so the "wrong exclusion" failure +mode the prompt warned about is visibly not what happened. + +**Scope note:** `docs/ModellerGuide.md` was not touched — every id above belongs to the Viewer's own +`panels.js`/`pill_builder.js` surface, not a Modeller-mislabeled one. diff --git a/prompts/FIND_PANEL_ISOLATE_NO_CAMERA_ZOOM.md b/prompts/FIND_PANEL_ISOLATE_NO_CAMERA_ZOOM.md new file mode 100644 index 000000000..5495c6eff --- /dev/null +++ b/prompts/FIND_PANEL_ISOLATE_NO_CAMERA_ZOOM.md @@ -0,0 +1,101 @@ + +# FIND PANEL — isolate-tap has no camera zoom/fit, "zoom" is illusory (2026-07-12) + +``` +# ⚠ DO NOT REMOVE +SCOPE: bim-ootb `viewer/navigate_find.js` — user-reported (2026-07-12, live on localhost): "clicking +on them does not show consistent zooming in as expected" for Parts/Stairway tree items. Traced to +source before writing this file (see below) — read this whole file before touching code. User wants +to pick this one up personally ("let me iterate with it separately but earnestly") — treat this as a +handoff spec, not a dispatch target, unless told otherwise. PUSH PAUSE LIFTED for this repo — commit +locally, verify on localhost, push + PR with auto-merge once done, same convention as this session. +``` + +## Root cause, confirmed from source (don't re-derive) +Every axis's "tap a group/leaf to isolate" path — `_isolatePartsGroup` (Parts), `_isolateLensGroup` +(Room/Material), `applyIsolate`, `isolateLeaf` — ultimately calls the SAME `_emitIsolate(set, by)` +(`viewer/navigate_find.js` ~line 2527): +```js +function _emitIsolate(set, by) { + A.filterByGuids(set); + ... // just logs visible/hidden/total counts, no camera code at all +} +``` +**There is no camera-fit/zoom-to call anywhere in this function, for ANY axis.** Isolating a group +only toggles visibility (`A.filterByGuids`) — it never moves or reframes the camera. What reads as +"zoom" is an illusion: when the isolated element(s) happen to already be inside the current camera +frustum, hiding everything else makes them visually pop/fill more of the view, which LOOKS like a +zoom. When the isolated element is off to the side, behind the camera, or far from the current view, +nothing visually changes except stuff disappearing — no reframing occurs, so it looks like "nothing +happened" or "isolate is broken." **This exactly matches the reported inconsistency** — it isn't +flaky, it's a deterministic function of whether the target already happens to be on-screen. + +**A real zoom-to-selection feature already exists elsewhere in this codebase** — confirmed live this +session in the Modeller Outliner's own witness (`W-E2E-OL-GROUPSELECT` K3: "that same click fired +#711's `§ZOOM-SEL` fill line... zoomToSelection ran off selectMany → setSelectionIds"). So the +Modeller side has a working `zoomToSelection`-style call already wired to its own group-select path +— the Viewer's Find panel isolate path was simply never connected to the equivalent Viewer-side +camera-fit function (if one exists — check first, see Task 1). + +## Task 1 — find the Viewer's own camera-fit primitive (don't assume it's absent, check) +1. Search `viewer/scene.js`/`viewer/streaming.js`/wherever the Viewer's camera logic lives for an + existing "fit camera to a set of GUIDs/bbox" function — the Room axis's OWN highlight-lens mode + (`_roomBoxes`, ~line 828 area) already computes a bbox per room from `spatial_structure` center/ + size; check whether ANYTHING in that code path, or elsewhere, already does a camera-frame-to-bbox + operation for some other feature (e.g. a "zoom to selection" toolbar action, a double-click-to- + frame on a picked element) that could be reused rather than invented fresh. +2. If a real, working camera-fit primitive exists: wire `_emitIsolate` (or each caller, if a shared + wire-up isn't clean) to call it with the isolated set's real bbox (computed the same way Room's + highlight lens already computes room bboxes from `spatial_structure`/`element_transforms`, or + however the found primitive expects its input — cite it). +3. If NO such primitive exists in the Viewer: this becomes a small new feature (compute bbox of the + isolated GUID set from `element_transforms`, animate/set camera to frame it), not a bug fix — + name it as that distinction plainly, since it changes scope/effort. + +## Task 2 — verify it doesn't fight Room/Material's existing highlight-lens camera behavior +Room and Material already have SOME camera behavior on tap (per the guide's own doc text: "tap a +room to zoom to it" — verify this claim against actual code too, it may be equally illusory/aspirational +prose, not a promise already kept). If Room DOES already zoom correctly and Parts/Material don't, +find what Room does differently and reuse it rather than building a second mechanism. If Room's own +"zoom to it" turns out to be the SAME illusion, that's a bigger, guide-text-correcting finding — name +it, don't just patch Parts in isolation and leave the doc's claim for Room silently unverified. + +## Explicitly out of scope +- Any change to `A.filterByGuids` itself (the visibility mechanism is correct and working, per the + original bug report — only the missing camera reframe is the issue). +- The Plant Room/keyword/class-gate logic — unrelated, already fixed (#740/#742). + +## DONE WHEN +Either: a real camera-fit is wired to isolate-tap for Parts (and Room/Material if Task 2 finds they +need it too), verified live (tap an off-screen Stairway item, confirm the camera actually reframes to +it, not just visibility-filters) — or, if no primitive exists and building one is out of the +immediate appetite, a clear written finding + effort estimate so the user can decide whether to +proceed, without code changes forced prematurely. + +## Task 1 + 2 findings (2026-07-12) — primitive confirmed to exist, not built yet +**A camera-fit primitive already exists in the Viewer itself, in this SAME file** — no need to reuse +Modeller's `zoomToSelection` cross-repo, it's closer than that: +- `_bboxOfGuids(set)` (~line 2186) — world-space bbox of a guid set from `element_transforms`. +- `_zoomToGroup(set)` / `_zoomToGuids(set, factor)` (~line 1786/2205) — call `_bboxOfGuids` then + `_zoomToBoxFill`/`_zoomToBox` to fly the camera to frame it. Both already exist, already tested by + use (see next point), zero new code needed for the math. +- `A.focusElement(guids, opts)` (~line 3711) — the higher-level "neutral shared focus primitive": + ghosts the rest, highlights the target, and (unless `opts.frame === false`) calls `_zoomToGuids`/ + `_zoomToGroup` to reframe. Already wired to 3D tap-to-pick (picking.js), the Find **drill** + (`_drillSelect`, line ~2340), and history-restore. + +**`_emitIsolate` (line 2527) is the one path in this file that was never connected to any of them** — +confirmed it's still just `A.filterByGuids(set)` + a console log, exactly as originally traced. + +**Task 2 answered:** Room/Material's GROUP-header tap in the Find tree (`_isolateLensGroup`, line 807) +ALSO calls `_emitIsolate` directly — same non-zoom illusion as Parts/Stairs, not special-cased. But +Room has a SEPARATE interaction people may be thinking of — `_roomSelect` (tapping a room to see its +*contents*, the x-ray drill path) — which DOES zoom, via `_zoomToBoxFill`. So "does Room zoom on tap" +depends on WHICH room tap: content-drill zooms today, group-isolate (the tree header) does not. Not a +blanket claim to correct — a real split in current behavior. + +**Effort, now that the primitive is confirmed to exist:** small — wiring, not building. `_emitIsolate` +would call `_zoomToGroup(set)` (or `_zoomToGuids`) right after `A.filterByGuids(set)`, reusing the +exact function `_drillSelect`/`focusElement` already call in this same file. No new bbox math, no new +camera code. Left unimplemented per this doc's original handoff note (user's own item to iterate) — +this section is findings only, no code changed. diff --git a/prompts/HISTORY_KNOB_SIGNAL_TAP.md b/prompts/HISTORY_KNOB_SIGNAL_TAP.md index 866727b21..4708b342e 100644 --- a/prompts/HISTORY_KNOB_SIGNAL_TAP.md +++ b/prompts/HISTORY_KNOB_SIGNAL_TAP.md @@ -280,3 +280,49 @@ WITNESS (node, on the REAL history_tap.js — `build/erp` style whitebox, §-log - `sw.js` cache-bump + `?v=` on every touched file; sw.js is the conflict magnet (take higher, keep both precache hunks). - This supersedes the read-only-tier half of `universal_history`'s PROFILES — verify nothing else depends on them before deleting (the `event`/`view`/`op` bucket significance moves into the tap STOP sets). + +## ▶ BUG — back-arrow snaps forward instead of stepping back (diagnosed 2026-07-12, NOT YET FIXED) +User repro: press the ‹ back arrow once → instead of showing the older view, a NEW dot mints at the tip and +the scrubber ends up pointing at it (looks like "back produces a dot forward"). Manually clicking a specific +OLDER dot "works" — but only because that path masks the same bug, see below. Root-caused from a real +browser log (`§HIST_VIEWNAV`/`§HIST_PUSH`/`§HIST_TAP_DOT` sequence), not reproduced fresh — verify against +live code before landing the fix. + +**Root cause — a gate asymmetry between `_drainTap` and `feedCrumb`, plus one missing DENY tag:** +1. `common/history_bar.js` `_viewApply(idx)` (~L275) drives `_cfg.restoreView(entry)` → `universal_history.js` + `_restoreView` → `_tapApply` → `HistoryTap.applyView(view)`. Inside `applyView` (`common/history_tap.js` + L136-147), each `field.write()` calls the REAL setter (e.g. `A.toggleXray()`), which itself prints a + `§XRAY on=… ` line — while `_applyingView` (the `isApplying()` flag) is `true`. +2. That `§XRAY` line passes through the sniffed `console.log` → `feedCrumb()` (`history_tap.js` L105-112). + **`feedCrumb` never checks `isApplying()`** — it queues the crumb into `all[]` and calls `_notify()` + regardless of whether a restore is in progress. This is the asymmetry: `recordEvent()` in + `universal_history.js` L110 explicitly gates on `isApplying()`; the generic sniffer path does not, even + though the comment at `history_tap.js` L133-134 states the general contract ("must NOT mint a new dot + during a restore… gate on isApplying()"). +3. `_notify()` calls the subscriber `_drainTap()` (`universal_history.js` L315). It DOES check `isApplying()` + (L317) and bails — correctly refusing to drain WHILE mid-restore. But it bails BEFORE advancing `_tapHW` + (the high-water mark), so the queued crumb is never marked consumed — it just sits in `all[]`. +4. `applyView()` finishes, `_applyingView` resets to `false`. Back in `_viewApply`, `console.log('§HIST_VIEWNAV + idx=… ')` fires (`history_bar.js` L282). **`HIST_VIEWNAV` is missing from `DENY_TAG`** in `history_tap.js` + (L29-38) — every OTHER `HIST_*` control tag is denied (`HIST_PUSH`, `HIST_UNDO`, `HIST_REDO`, + `HIST_TAP_DOT`, `HIST_DEPTH`) but this one was left out. So this line itself gets sniffed, `_notify()` + fires again, and THIS TIME `isApplying()` is false → `_drainTap()` proceeds → drains the stuck XRAY crumb + from step 2 → `HB.push()` mints a genuine new tip dot (`§HIST_PUSH n=110 idx=109`) → `_push()` + (`history_bar.js` L205) sets `_viewCursor = _cursor` (the NEW tip). +5. Why arrow ≠ dot-click: `viewStepBack()` steps RELATIVE to `_viewCursor`; once step 4 silently snaps + `_viewCursor` back to the tip, the next back-press steps -1 from the (moved) tip again — net effect looks + like back-arrow can't make progress / "produces a dot forward". `viewJumpTo(idx)` (dot click) sets + `_viewCursor` to an ABSOLUTE idx and the scene already shows the correct restored look, so the same + tip-snap happens invisibly right after — it only *looks* like dot-click is unaffected. + +**Proposed fix (both in `common/history_tap.js`, not yet applied):** +1. `feedCrumb()` (L105) — add `if (_applyingView) return;` as a first check, alongside DENY_TAG/LIFECYCLE/ + NOISE_LABEL, so restore-driven setter logs never enter `all[]` in the first place (symmetric with + `recordEvent()`'s existing gate). +2. `DENY_TAG` (L29-38) — add `HIST_VIEWNAV: 1`, closing the one gap in the otherwise-complete `HIST_*` + control-tag denial (anti-recursion for the sniffer, same intent as the other 5). +Both are small, additive, non-invasive — no restructuring of the tap/bar contract. Witness after fixing: +drive a real back-arrow press on a moment that flips a real toggle (xray/section/ghost), confirm NO +`§HIST_PUSH`/`§HIST_TAP_DOT` follows the `§HIST_VIEWNAV` line, and `_viewCursor` stays at the pressed idx +(verify by pressing back-arrow twice in a row and reading successive `§HIST_VIEWNAV idx=` values — they must +decrement, not oscillate back to the tip). diff --git a/prompts/MANAGER.md b/prompts/MANAGER.md index 9d5e0d066..9a0d0bf89 100644 --- a/prompts/MANAGER.md +++ b/prompts/MANAGER.md @@ -9,224 +9,92 @@ --- -## ▶ SCOPE NARROWED (2026-07-11, current — read this before the section below) -User's own words: **"I demoted your role slightly to just user guides, git and localhost admin. The -sessions are now acting as own lane reviewers and asked to administer further prompts respectively."** -Concretely, as of now: -- **IN scope:** user-guide work (screenshot capture, placing real images, writing/extending guide prose - — `docs/BIMUserGuide.md`/`ModellerGuide.md`/etc), git admin (push/merge/PR/branch/worktree hygiene — - "on your PR git admin stuff... you have mandate to admin proper"), localhost admin (setting up/ - refreshing servers for guide screenshot work). -- **OUT of scope now (moved to the lane sessions themselves):** deep independent re-verification of a - dispatched lane's own claims (each lane now reviews itself), and writing the NEXT follow-up prompts/# - spec for a lane's own next step (each lane now administers its own follow-up prompts). Don't re-absorb - either by default — if a lane hands something back here, it's within the narrowed scope above (does it - touch a guide, or need git/localhost admin), not a general invitation to re-review its engineering. -- The §WHAT MANAGER MEANS HERE / §STANDING RULES sections below predate this narrowing and describe the - broader role — still useful history/context, but this section is the current live scope. Don't silently - drift back to the broader review role without the user re-widening it. - -## ▶ WHAT MANAGER MEANS HERE -U ARE TO REVIEW OTHER SESSIONS PUT BEFORE U BY THE USER. MANAGE AND HOUSEKEEP. - -- **Review:** when a session's report is relayed, verify it — re-run witnesses, reproduce claims from a - genuinely fresh checkout, don't trust a "green" report. Don't wait to be asked; that's the job. - **⚠ Narrowed 2026-07-11 (see section above) — lanes now self-review; this applies within the current - narrowed scope (guides/git/localhost), not as a blanket re-audit of every lane's own work.** -- **Manage:** track every parallel thread (which session is doing what, what's reported vs. still - pending — don't silently lose track of a thread that never explicitly reported back). **PR work — - including the merge decision — is Manager's job (hardened 2026-07-11, "PR work is your Manager work, - do not kick back to me"). Once a PR is independently verified (real diff, real green CI/witness, no - unresolved conflict), merge it. Don't leave it open "for the user's call" and don't report it back as - a pending decision — that IS the earlier, now-superseded default.** Still stop and surface a PR rather - than merge it if verification itself is inconclusive (CI red, witness doesn't back the claim, a real - conflict) — that's a genuine blocker, not a courtesy check-in. -- **Housekeep:** keep `PROGRESS.md`, memory, and the relevant lane file current as things land — do this - as part of the work, not as a separate ask-permission step. -- **No ceremony:** don't restate this role, don't narrate git/admin mechanics unless asked, don't hedge - an already-answerable call back to the user. Bottom line first when asked for one. -- **Launch work in this own session — never ask the user to be the courier to another terminal - (hardened 2026-07-11, "next time if u can run it here, dont ask me to put to another session").** - This MANAGER session has its own Agent-tool dispatch (background workers, same as every task - today — Find panel, UBBL gate, Room Lens, Terminal fix, room-type classifier, clash-gate OBB, all - launched directly from here). Never produce a prompt/instruction FOR the user to paste into a - different terminal session — dispatch it here. The user runs OTHER independent sessions of their - own in parallel (a real, ongoing fact — several collisions with peer sessions happened today, - handled fine via the shared doc-trail + freshness-check stand-downs), but that's their own - parallel work, not something Manager should route through. -- **Don't ad-hoc debug in-session — write the prompts/# spec, dispatch it (hardened 2026-07-11, - "u still not using prompts/# to delegate out as before... i am concerned of inconsistency").** - Caught live: chased a Playwright screenshot for 3+ failed attempts trying to find the right way - to open the Viewer's Find panel programmatically, burning turns on trial-and-error — exactly the - kind of exploratory debugging that belongs in a dispatched Agent/prompts/# task, not inline in - MANAGER's own turn. The tell: if the SECOND attempt at a quick verification hasn't landed, STOP — - either the evidence you already have (a diff read, an earlier dispatched agent's real witness - log) is sufficient and you're re-proving something already proven, or it genuinely needs - investigation, in which case write it up (scope, what's known, what to check) and dispatch it, - same discipline applied consistently to EVERY open thread — Modeller material parity AND Viewer - Find-panel verification alike, not just whichever one got flagged first. Quick, one-shot, - already-scoped checks (read a file, run an existing witness, `git log`) stay inline — this is - about repeated/exploratory trial-and-error, not banning all direct verification. +## ▶ SCOPE (2026-07-12, current) +Standing role: overlook admin across concurrent sessions, AND conduct small tasks directly — not just +git/localhost mechanics. +- **Admin/overlook (always):** track every parallel thread (what's reported vs. still pending), git/PR/ + branch/worktree hygiene, localhost admin, housekeep `PROGRESS.md`/memory as things land. +- **Small tasks, done directly, not dispatched:** user-guide staleness checks + fixes (screenshots, + prose — `docs/BIMUserGuide.md`/`ModellerGuide.md`/etc) — see ▶ GUIDE STALENESS METHOD below. Bounded, + few-file, reuse-existing-tooling work stays inline; don't spin up an agent for something this session + can finish itself in a few tool calls. +- **Bigger than small → REPORT, don't build.** If a check surfaces something that's a real feature/code + fix, not a screenshot swap — name it precisely (what's stale, why, what would need to change) and + hand it to another session. Don't silently expand scope into a feature build under cover of "fixing + the guide" (2026-07-12 example: found a real camera-zoom bug capturing a room screenshot — fixed the + SCREENSHOT via a workaround, reported the bug as a named gap, did not patch the app inline). +- **Deep independent re-verification of a dispatched lane's own claims:** not the default (each lane + self-reviews) — but do it in full whenever the user directly asks; never cite scope to decline. + +## ▶ GUIDE STALENESS METHOD (proven 2026-07-12, reuse this shape every time) +1. `git log -1 --format=%ai -- ` per screenshot — get its real capture date. +2. Compare against the relevant fix's actual merge/commit date (not a claimed one) — captured-before-fix + is presumptively stale for that specific feature. +3. Recapture by RE-RUNNING an existing, already-proven E2E/Playwright script + (`modeller/tests/witness_e2e_*.js` or the Viewer equivalent) — never hand-roll a new capture flow + when one already exists. +4. Look at the resulting image yourself — a passing witness proves the NUMBERS, not that the frame is + legible or illustrative. +5. Deploy only via `scripts/safe_gh_deploy.sh`. A legitimate size/content change tripping the shrink + guard gets blessed explicitly (`ALLOW_SHRINK=1 paths=...`) — never disable the guard. +6. Verify the LIVE result from `git log origin/gh-pages` + the branch's actual bytes/text, not just the + script's exit code or a `curl` (GitHub Pages CDN can lag minutes behind a genuinely successful + publish — check the branch before concluding a deploy failed). +7. Touch only images actually implicated by the fix under review — name and leave unrelated stale + images for a separate pass, don't silently expand scope. + +## ▶ WHAT MANAGER MEANS HERE — cardinal rules, always in force +- **Review:** verify before trusting — re-run witnesses, reproduce claims from a fresh checkout, never + relay a "green" report on faith. Don't wait to be asked. +- **Manage:** track every parallel thread (what's reported vs. still pending). PR work, including the + merge decision, is Manager's job — once independently verified (real diff, real green CI/witness, no + conflict), merge it; don't leave it open "for the user's call." Still surface, don't merge, if + verification itself is inconclusive (CI red, witness doesn't back the claim, a real conflict). +- **Housekeep:** keep `PROGRESS.md`, memory, and the relevant lane file current AS things land — part + of the work, not a separate ask-permission step. Re-check line budgets on every edit to a shared + file, not just when told it's grown into clutter. +- **No ceremony:** don't restate this role, don't narrate git/admin mechanics unless asked, bottom line + first when asked for one. +- **Dispatch here, never route to another terminal.** This session has its own Agent-tool dispatch — + never produce a prompt FOR the user to paste elsewhere. The user runs their own independent parallel + sessions; that's separate from Manager's own work. +- **Don't ad-hoc debug in-session — write the spec, dispatch it.** If a second attempt at a quick + verification hasn't landed, STOP: either existing evidence (a diff read, an earlier witness log) is + already sufficient, or it genuinely needs investigation — write it up and dispatch, don't keep + trial-and-erroring inline. Quick one-shot checks (read a file, run an existing witness, `git log`) + stay inline; repeated/exploratory digging does not. ## ▶ THE GOAL Get the actual thing working — not branch hygiene, not verification as an end in itself. Weigh every -open thread against whether it moves the real product closer to working. - -**"Working" is not a vibe — it's the five sentences in `prompts/RESUME_GRAPH_MODELLER_INTEGRATION.md` -§VISION-LOCK (open a whole ARC building and EDIT it · 3D grid is the primary edit-handle · conformity -fires on the drag · every non-ARC discipline is a WALKER that fills ARC space · one Outliner panel = -Find on steroids). Any thread relayed here gets weighed against THAT bar, not a generic "did it pass." - -**What's ACTUALLY working right now (not the target, the current state) is never memorized here — -it's derived fresh from `PROGRESS.md §Current State` + its `🔀 CURRENTLY JUGGLED` list every session, -plus whichever `RESUME_*.md`/`prompts/Modeller/DISC_Walker/*.md` a juggled thread points at. Those files -are the ground truth (gate tables, shipped-PR numbers, open bugs); this file's job is the bar to judge -them against, not a snapshot that will go stale the moment it's written.** - -**The tangible target all of this serves: `https://red1oon.github.io/BIMCompiler/ModellerGuide/` — the -published user guide.** VISION-LOCK is the internal engineering bar; this URL is the external, user-facing -proof that the bar is actually met — real screenshots of real working behavior, not placeholders. Don't -let review/verification work drift into an end in itself: every merged fix should be judged partly on -"does this get us closer to a guide page that can honestly show this feature," not just "does the witness -pass." **Updated 2026-07-11 (this entry, don't re-derive — re-check `PROGRESS.md` first if this reads -stale):** the 5 blockers named in the previous version of this note are now RESOLVED (verified, not -assumed) — Room Lens renders a real volume box (bim-ootb PR #733, room-data OCI-upload block -LIFTED), Terminal's coordinate-frame mismatch root-caused + fixed (bim-compiler PR #41), HHS's -GH-served file self-heals via a new Viewer-side patch loader (bim-ootb PR #732), the Modeller/Viewer -self-heal-loader pattern now exists on both apps. Still open: x-ray/glass-reveal bug in -`modeller.html`, status unverified. The other three VISION-LOCK sentences (open+edit whole ARC -building, 3D grid as primary edit-handle, conformity fires on drag) got real, separate progress -too: §8E-3 MEP routed-network render shipped (PR #731, completes the "every discipline is a WALKER" -sentence for MEP specifically — STR+MEP-density+MEP-routing all now render into the laid ARC). - -## 🚩 THE FLAG ON THE HILL — next session's mission, pursue to completion (2026-07-11) -**User, closing this session: "we should make this template include more parts of buildings - a -true plan.. stairways, air wells.. ventilation etc.. so the Find panel and equally the Modeller -Outliner is complete where DISC Walk be truly equipped... new session pursue this till end. We done -so much all round, this is one flag we have to plant on the hill."** This is not one more item in -the open-proposals list below — it is THE named objective for whatever session picks this up next. -Read this section FIRST, before the scoreboard, before the rest of this plan. - -**The mission, stated precisely:** today's 7 promoted room templates (BEDROOM/BATHROOM/KITCHEN/ -LIVING_ROOM/FOYER/HALLWAY/UTILITY) cover only habitable + basic circulation space. A real building -has MORE parts than that — stairways (already an n=1 exception, never promoted), air wells/light -wells, ventilation shafts, lift shafts (currently only a named DISQUALIFIER, not a positive -category), plant/mechanical rooms, storage, and whatever else a real floor plan actually contains. -**"A true plan"** means the room/space taxonomy stops being room-type-only and becomes a COMPLETE -building-part index — every enclosed or semi-enclosed space in a compiled building gets a real, -measured, confidence-scored classification (or an honest, named refusal), not just the subset that -happens to look like a residential room. - -**Why this closes the loop on all three fronts, not just one:** -1. **Find panel** — the Room axis becomes a genuine complete index of the building, not a partial - one that silently drops stairs/shafts/plant rooms into "unclassified." -2. **Modeller Outliner (VISION-LOCK sentence 5)** — "one panel = Find on steroids" can't be true - while the underlying taxonomy is incomplete; this is the actual blocker on that VISION-LOCK - sentence, not a separate UI task. -3. **DISC Walker (VISION-LOCK sentence 4, "every non-ARC discipline is a WALKER that fills ARC - space")** — today's DISC-walk work (item 12 in the scoreboard) only found real signal for - PLB→Bathroom/Utility and FP→Foyer because those were the only real, complete room types available - to measure against. A ventilation shaft, an air well, a lift shaft each have their OWN real - discipline correlations (ACMV almost certainly concentrates near air wells/shafts in a way this - session never got to test — Duplex simply doesn't have one) — the walker cannot be "truly - equipped" until the taxonomy it walks against is complete. This is the real reason DISC-walk's - signal coverage today is thin (2 disciplines, 1 building) — not a modeling weakness, a DATA - coverage gap this mission directly closes. - -**How to pursue it (apply everything already learned this session, don't restart from zero):** -- Same non-invent discipline throughout (`§EXECUTION PLAN` below) — every new category must be - measured from real geometry/labels or explicitly refused, never hardcoded by assumption. -- Check every shipped building for real examples of each missing part BEFORE assuming residential - Duplex/SampleHouse have them — a stairway/air-well/plant-room signature likely needs an - institutional-scale building (HHS/Clinic/Hospital/Terminal) as its real ground truth anchor, same - lesson as the HHS-corridor scale-mismatch finding this session (`ROOM_INTELLIGENCE_SCOREBOARD.md`). -- Reuse the room-adjacency graph (`common/room_graph.js`, already built) and the tier system - (primary/supplementary, already built) rather than inventing a third parallel structure — new - building-part categories should slot into these, not sit beside them. -- Refresh `ROOM_INTELLIGENCE_SCOREBOARD.md` as this work lands — it stays the standard reporting - format, don't revert to prose status updates. -- **Session-end discipline stays the same as this one:** verify every dispatched worker's claim - independently (rerun the witness, don't trust the report), keep the push pause until told - otherwise, keep MANAGER.md and the scoreboard current so the NEXT session after that one can also - hit the ground running. - -## ▶ EXECUTION PLAN — Room Intelligence lane (2026-07-11, strategy session synthesis) -**Standing discipline for every task in this lane (user, 2026-07-11): "maintain abstract general -rules not hardcoded to any particular" + "maths has all the approaches to resolve any scenario so -use it well."** Not a new rule — this IS `RESUME_GRAPH_MODELLER_INTEGRATION.md`'s prime constraint -("ABSTRACT, never custom... measure-don't-whitelist"), restated for this lane specifically so it's -explicit here too, not just inherited. Concretely: reach for the real mathematical/statistical tool -(SAT for geometry, Gaussian fit for classification, graph search for pathfinding, measured -correlation for placement weighting) over a hardcoded per-building/per-type rule, every time. Today's -work already holds this line — room-type classifier is a measured fit not a lookup table, UBBL gate -refused every threshold beyond 2 verified ones, door-access/tier signals reported honest negative -results instead of being forced to fit, the OBB clash-gate is a general algorithm not a per-building -special case. Keep it that way for every future task dispatched from here. - -**The competitive bet, stated plainly:** every other BIM tool (Revit, ArchiCAD, FM platforms) either -requires a human to author room/space data, or trusts whatever IfcSpace came in the file — and real -IFC exports are notoriously bad at populating it. Our bet is COMPILING rooms from geometry that's -missing or wrong, honestly, with a calibrated confidence attached — never inventing, never silently -guessing. Room is the anchor FM/space-analytics/code-compliance all need; nobody else is solving "derive -it honestly when it's absent." The bigger thesis this lane is the first real test of: extend calibrated -confidence to every compiled fact (room, rule, clash), not just a binary refuse/accept. - -**STATUS: see `prompts/ROOM_INTELLIGENCE_SCOREBOARD.md` for the full scored table — 13 features -shipped/verified today (score 0-10 + WORKS/GAP each), 8 buildings' room coverage measured fresh. -Don't re-derive this list in prose here; the scoreboard IS the current state, refresh it, don't -duplicate it.** Headline: 6 PRs merged, 1 open (bim-compiler #41), 7 threads committed locally under -the push pause (below). Weakest links named plainly in the scoreboard: door-access signal (4/10, -net-regression if defaulted on) and classifier sample size (5/10, real ground truth = Duplex + -SampleHouse only, 2 of 8 shipped buildings). - -**Named next axes, NOT yet built (also in the scoreboard's "Open proposals" — check there first):** -- **Fixture-in-room recognition** — `IfcFurnishingElement` data already extracted (61 rows, - confirmed) and completely unused. Most human-like signal available, zero new extraction needed. - Highest-leverage next POC per the 2026-07-11 strategy discussion. -- **Graph-joint-inference (label propagation)** over the now-built room-adjacency graph - (`common/room_graph.js`, bim-ootb) — bootstraps from existing small ground truth via measured - co-occurrence, no external dataset required. -- **External dataset integration** — RoomGraph (224 apartments) + SAGC-A68 (275 apartments), both - public/licensed, found and verified this session. Real scope decision, not a quick dispatch. -- **OmniClass Table 13 / Uniclass 2015 SL naming-convention mapping** — `config/room_templates.yaml` - already has a `canonical_type` stub anticipating this. -- **Grid/containment signal** — which walls bound a space — third classification axis, not built. -- **Disqualifier categories** beyond Roof/z-band (lift-shaft void, roof-overhang exterior) — logged - `VIEWER_FIND_PANEL_ROOM_ACCURACY.md` §6, not built. -- **Outliner wiring + correction flywheel** — once the classifier lands in the Modeller Outliner - (VISION-LOCK sentence 5), a user correction becomes a real measured label feeding back into the - template. The actual differentiator, not a side effect — still not built. - -## ▶ STANDING RULES — present, in force, read before dispatching anything (2026-07-11) -1. **Localhost when demoing/verifying.** Every dispatched worker verifies its own claim by driving - the real feature on a local dev server, not by trusting a witness exit code alone — real browser, - real UI path, per this project's own whitebox-first + "start the dev server" standing rule. -2. **Local commits only, until the pause lifts.** `CLAUDE.md` §⏸ PUSH PAUSE: commit locally as - normal, do NOT `git push`, do NOT open a PR, for any NEW work. Lifted only when the user says so - or names the "major breakthrough" worth it. Already-merged work today is NOT rolled back — this - is forward-only. -3. **DB changes = migration script + self-heal loader, always.** `CLAUDE.md` §DB CHANGES — this is - the PERMANENT architecture, not an LFS workaround. Never commit a binary `.db`. Ship a small SQL - patch + a runtime loader (Modeller: `str_walker_outliner.js _applyPendingPatch()`; Viewer: - `scene.js A._applyPendingPatch()` + `buildings/patches/*.sql`, both proven this session). - -## ▶ KEY DOCUMENTS — one-stop navigation for a fresh session -- **Mission:** this file, §🚩 THE FLAG ON THE HILL (above) — read first. -- **Status:** `prompts/ROOM_INTELLIGENCE_SCOREBOARD.md` — scored feature table + per-building - coverage table, the standard reporting format, refresh don't rebuild. -- **Competitive positioning:** `prompts/COMPETITIVE_PRIOR_ART_ANALYSIS.md` — verified citations, - what's genuinely novel vs. established prior art. -- **Per-thread specs/results** (each carries its own DONE/RESULT section, read before re-dispatching - the same work): `prompts/ROOM_TYPE_TEMPLATE_CLASSIFIER.md` · `prompts/ROOM_TYPE_DOOR_ACCESS_SIGNAL.md` - · `prompts/CLASH_GATE_OBB_NARROWPHASE.md` · `prompts/DISC_WALK_ROOM_TYPE_AWARE.md` · - `prompts/TERMINAL_COORDINATE_FRAME_MISMATCH.md` · `prompts/UBBL_RULES_GATE.md` · - `prompts/Modeller/DISC_Walker/VIEWER_FIND_PANEL_ROOM_ACCURACY.md` (§5 habitability/HHS-loader, - §6 disqualifier follow-up, §7 corridor pathway routing) · `prompts/Modeller/DISC_Walker/ - ROOM_INJECTION_HYBRID.md` (§9 Room Lens volume-box) · `prompts/RESUME_GRAPH_MODELLER_INTEGRATION.md` - (§8E-3 MEP render) · `prompts/PROMPTS_ARCHIVE_AUDIT_2026-07-11.md` (housekeeping trail). -- **Memory:** `project_room_intelligence_lane.md` (links-only pointer back here, doesn't duplicate). +open thread against whether it moves the real product closer to working, judged against +`prompts/RESUME_GRAPH_MODELLER_INTEGRATION.md` §VISION-LOCK (open a whole ARC building and EDIT it · +3D grid is the primary edit-handle · conformity fires on the drag · every non-ARC discipline is a +WALKER that fills ARC space · one Outliner panel = Find on steroids) and, externally, whether it moves +`https://red1oon.github.io/BIMCompiler/ModellerGuide/` closer to honestly showing the feature. +**Current state is never memorized here** — it's derived fresh from `PROGRESS.md §Current State` + +`🔀 CURRENTLY JUGGLED` every session; this file is the bar to judge against, not a status snapshot. + +## ▶ STANDING RULES — read before dispatching anything +1. **Localhost when demoing/verifying.** Every dispatched worker verifies its own claim by driving the + real feature on a local dev server, not a witness exit code alone. +2. **Local commits only, until the pause lifts.** `CLAUDE.md` §⏸ PUSH PAUSE: commit locally, do NOT + push or open a PR for new work, until the user lifts it. Already-merged work stays merged. +3. **DB changes = migration script + self-heal loader, always.** `CLAUDE.md` §DB CHANGES — permanent + architecture. Never commit a binary `.db`; ship a small SQL patch + a runtime loader. +4. **Verify before reporting, not after being asked (2026-07-12).** Independently re-check a claim + BEFORE relaying it, never on trust first and defended after. Read a spec doc to its END before + building a dispatch prompt from it — a mid-doc "this was tried and disproven" is what a partial + read misses. On ambiguous phrasing for an action with external/irreversible effect (message to a + live agent, push, merge) — ask which is meant before acting, don't default to autonomous execution + and apologize after. Self-regulate exploratory effort: 2-3 unresolved digs into a side-mechanism = + stop and report honestly, don't keep spending the user's tokens on it. + +## ▶ KEY DOCUMENTS +- **Status:** `PROGRESS.md §Current State` + `🔀 CURRENTLY JUGGLED` — always the live ground truth. +- **Room Intelligence lane** — CLOSED 2026-07-11 ("good enough, no more perfection work there," per + `PROGRESS.md`); history in `ROOM_INTELLIGENCE_SCOREBOARD.md`, don't re-open without user re-widening. +- **Memory:** `project_room_intelligence_lane.md` (links-only pointer, doesn't duplicate). ## ▶ DELIVERABLE Verified verdicts (rerun the witness, don't trust the report), merged/pushed work, current -housekeeping, reported via `ROOM_INTELLIGENCE_SCOREBOARD.md`'s scored-table format — plainly, no -process narration. +housekeeping — reported plainly, no process narration. diff --git a/prompts/MOBILE_META_SPLIT_FIX.md b/prompts/MOBILE_META_SPLIT_FIX.md index 3e0fced03..fab52ea3e 100644 --- a/prompts/MOBILE_META_SPLIT_FIX.md +++ b/prompts/MOBILE_META_SPLIT_FIX.md @@ -51,3 +51,36 @@ - `prompts/MOBILE_PERF.md` (parent strategy, lever #1) · `docs/MOBILE_DEPLOY.md` (split strategy) - `deploy/OCI_UPLOAD.md §RULES` (MIME + bucket) · `scripts/extract_per_building.py` (the slicer) - boq_charts.html / mep_report.html `§QTO_SPLIT_HIT` / `§MEP_SPLIT_HIT` (the split consumers) + +## 2026-07-13 — the OTHER half of this landmine: the 3D Viewer trusted meta.db ALONE, wrongly + +This file's own design (above) already establishes that `_meta.db` is a legitimate STANDALONE +artifact for metadata-only consumers (BOQ/MEP report pages) — it never needed a `_geo.db` +companion for that use case. A second, independent producer does the same thing for a different +consumer: `bim-compiler/scripts/make_resident_meta.sh` (committed 2026-07-03, `2d330e381`) +generates `_meta.db` for the **Modeller's resident bbox substrate** (`W-RESIDENT-OPEN`) +— its own comment says "DROPS the mesh blobs" by design, same shape, same OCI path +(`buildings/_meta.db`), same legitimate reason to never have a matching geo.db. Its default +building list includes `Duplex` by name. + +The landmine: `bim-ootb/viewer/streaming.js`'s split-DB detection (present unchanged since the +repo's initial migration commit — `git log -S"_meta.db"` shows exactly one hit, ever) reads ANY +`_meta.db` sighting as proof of a full mesh-streaming split pair, and unconditionally +attempts to fetch `_geo.db` next. For a building whose `_meta.db` was uploaded by EITHER of +the two legitimate standalone producers above (not by the Viewer's own split-DB path, which is a +THIRD, different code path — see TASK 2/3 above — that writes both halves together), the geo.db +fetch 404s. There was a fallback (load `_extracted.db` as the geometry source instead) that DID +recover visually, but noisily (failed request, console errors, one wasted round-trip) — this is +the "still resolves but looks broken" symptom reported for Duplex 2026-07-13, root-caused during +an unrelated JKR Drop-IFC georef fix session (see `RESUME_FLATTRANSFORMATION_POSITION_BUG.md` +2026-07-13 sections) when the user recognized this as a recurring, months-known pattern. + +**Fixed at the Viewer consumer, not by touching either legitimate producer**: `streaming.js` now +requires a successful HEAD on `_geo.db` as well as `_meta.db` before committing to split mode +(bim-ootb PR #764, `359a86f`; SW `CACHE_VERSION` bump to ship it, PR #765, `v746`). This is the +right layer to fix — neither `make_resident_meta.sh` nor the BOQ/MEP meta-split producer did +anything wrong; the Viewer's assumption that "meta.db implies geo.db" was always false for two +already-legitimate use cases, and would recur for any THIRD future meta-only producer sharing the +same OCI namespace convention. If this pattern resurfaces: check `§DB_SPLIT_DETECT ... found=` +in the Viewer console — `found=true` with a subsequent `§SPLIT_GEO_FAIL`/404 means a NEW producer +has appeared that also needs auditing here, not that the streaming.js fix regressed. diff --git a/prompts/Modeller/DISC_Walker/OCCUPANT_PATHFINDER.md b/prompts/Modeller/DISC_Walker/OCCUPANT_PATHFINDER.md new file mode 100644 index 000000000..121c05148 --- /dev/null +++ b/prompts/Modeller/DISC_Walker/OCCUPANT_PATHFINDER.md @@ -0,0 +1,281 @@ + +# OCCUPANT PATHFINDER — room graph 2.0: walk like a human, not door-to-door (2026-07-12) + +``` +# ⚠ DO NOT REMOVE +SCOPE: upgrade common/room_graph.js (bim-ootb) from door-adjacency-only to an OCCUPANT graph — +rooms connect through circulation space (hallways, concourses) and vertically through stairs, +because that is how a person walks and how conduit routes. POC-gate FIRST (calculation-only Node +script on real DBs, log the numbers) before touching the engine. API-COMPATIBLE: buildGraph() and +shortestPath() keep their signatures — navigate_find.js and every other consumer must need ZERO +edits. Read the log after every run. PUSH PAUSE: commit locally, verify localhost, no push, no PR. +User intent (verbatim): "it need not be door to door, but along the hallway etc.. it is not like +they have to face each other.. even thru the stairs.. this is a pathfinder for human occupants and +basis for conduit routing and Disc Walker too." +``` + +## §GIVEN — measured, do not re-derive +- **G1 — the current edge rule and its numbers.** `common/room_graph.js` builds an edge ONLY when a + door's buffer touches ≥2 room rects (`cands.length >= 2`, line ~139); a door touching ONE room is + counted `deadend` and DISCARDED; zero rooms = `orphan`. Live Terminal (with today's 59-room patch, + localhost E2E): `§ROOM_GRAPH nodes=59 doors=135 nonRoomDoors=5 edges=10 deadend=62 orphan=58` — + 62 usable human doors thrown away, no vertical edges at all, so cross-storey paths are impossible + and most same-storey pairs unreachable. +- **G2 — deadend doors ARE the circulation entries.** A door touching one room opens onto space + that isn't a compiled room: an un-compiled concourse/hallway or a neighbour the binding missed. + That outside space is where the human walks. +- **G3 — stairs exist as real elements** (`elements_meta` IfcStairFlight/IfcRampFlight joined to + `element_transforms` — Terminal has 33), each with center + bbox spanning its two storeys' z + range. `nonRoomDoors` (5 on Terminal) are the building exits — free fire-escape targets. +- **G4 — rooms are multi-rect** (`spatial_structure` rows share `room_guid`); containment checks are + per-member-rect, never merged-AABB (ROOM_TAXONOMY_STRATEGY_2026-07-12.md §POC5: 1188 violations + proved AABB wrong on non-convex merged rooms). +- **G5 — corpora**: Terminal = `/tmp/wt-terminal-rooms/buildings/Terminal_extracted.db` AFTER + applying `/tmp/wt-terminal-rooms/buildings/patches/Terminal_extracted.db.sql` (59 rooms); + JKR = `~/bim-ootb/buildings/JKR_extracted.db` (79 rect rows); Duplex = + bim-compiler `deploy/buildings/Duplex_extracted.db` (21 real rooms, the no-regression control). + +## SPEC — the occupant graph +Node/edge model (all new nodes/edges carry a `kind` field; existing consumers that only walk +`edges` keep working because E1 edges keep their exact current shape): +- **N-ROOM** (existing): every compiled/real room. +- **N-CIRC**: ONE open-circulation node per storey — the walkable space of that storey not inside + any room. (Coarse on purpose: one node, not a navmesh. Refinement comes later if measured wrong.) +- **N-EXIT**: one node per external door (from the existing `nonRoomDoors` detection). +- **E1** (existing, unchanged): door touches 2 rooms → room↔room. +- **E2** (the deadend rescue): door touches exactly 1 room → room↔N-CIRC of its storey. The 62 + discarded Terminal doors become edges. +- **E3** (vertical): each stair/ramp flight whose z-span bridges storeys A and B → N-CIRC(A)↔N-CIRC(B), + weighted by flight length. Multi-flight stairs chain naturally. +- **E4** (escape): external door's containing/nearest room or N-CIRC ↔ N-EXIT. +- **shortestPath(graph, from, to)**: same signature, Dijkstra (weights = 3D center distance door→door, + door→stair, stair→door); returns the same result shape plus `path` entries for circ/stair hops so + the Viewer's existing polyline draw just works (each hop has cx/cy/cz — for N-CIRC hops use the + door/stair waypoints themselves, NOT a storey centroid, so the drawn line hugs the actual walk). + +## POC GATE (do this FIRST — kill the design cheap if it's wrong) +Calculation-only Node script (no engine edits yet) on G5's three corpora, building the E1-E4 sets +and measuring: +``` +§POCPATH nodes= edges E1= E2= E3= E4= +§POCPATH reachable_room_pairs= (was with E1 only) cross_storey_pairs_reachable= +§POCPATH sample: -> hops=[door.., circ, stair, circ, door..] total= +``` +Acceptance to proceed: Terminal reachable pairs E1-only vs occupant-graph must jump massively +(expect single-digit % → >80%); Duplex E1 paths must be UNCHANGED (its rooms are properly +door-connected already — if E2/E3 alter an existing Duplex path, the weights are wrong). If the +numbers disappoint, STOP and report — do not ship a graph that didn't earn it. + +## Implementation (after the gate passes) +- All changes inside `common/room_graph.js` (it already probes tables defensively — extend the same + way for stairs; missing tables = graceful E1-only fallback, byte-identical current behavior). +- ES5, IIFE dual-mode (Node + browser) as the file already is. +- §-logs: extend the existing `§ROOM_GRAPH` line with ` circ= stairs= exits= e2=` — + do not remove any existing field (other sessions grep them). +- Fire-escape is NOT a separate feature to build now — it falls out: shortestPath(room, any N-EXIT). + Add one helper `escapeRoute(graph, fromGuid)` (nearest exit by Dijkstra) + its §-log, nothing more. + +## WITNESS PLAN +- **W-PATH-POC**: the POC gate numbers above, logged, all three corpora. +- **W-PATH-TERMINAL-LIVE**: localhost (:8902, own server — 8901 belongs to the needle lane), real + Viewer, Terminal, PATH mode between two rooms on DIFFERENT storeys → §-log shows the route with a + stair hop + the polyline renders (screenshot or §-line with hop kinds is fine). +- **W-PATH-DUPLEX-REGRESSION**: Duplex same-unit path identical hops before/after (run the node + witness on both engine versions and diff). +- **W-PATH-ESCAPE**: `escapeRoute()` from any Terminal room returns a route ending at an N-EXIT, + §-logged. + +## DONE WHEN +POC numbers logged and past the acceptance bar; engine shipped API-compatible with zero consumer +edits; all four witnesses quoted in a dated `# DONE` section appended to THIS file; committed +locally (child branch off `fix/terminal-rooms-selfheal` so the Terminal room patch is present); +NO push, NO PR. + +# DONE (2026-07-12) + +**Branch**: bim-ootb `feat/occupant-pathfinder` off `fix/terminal-rooms-selfheal`, worktree +`/tmp/wt-occupant-path` (fresh, not the shared `/tmp/wt-terminal-rooms`). Commit local only — +PUSH PAUSE honoured, no push, no PR. `common/room_graph.js` is the only shipped-engine file +touched; `witness_occupant_pathfinder.js` is a new witness script. + +## POC gate — calculation-only prototype, THEN re-verified against the shipped engine +Ran twice: a python calculation-only prototype first (kill-cheap per the spec), then the ACTUAL +shipped `common/room_graph.js` via sql.js in `witness_occupant_pathfinder.js` — both produced +byte-identical numbers, quoted below are the shipped-engine run (`logs/W_PATH_POC_final.log` in +the worktree): + +``` +§POCPATH Terminal reachable_room_pairs=68.7% (was 1.5% with E1 only) cross_storey_pairs_reachable=66.8% (was 0.0%, n_cross_storey_pairs=1238) +§POCPATH Terminal zero_door_rooms=10 reachable_pct_of_doored_rooms=100.0% +§POCPATH JKR reachable_room_pairs=20.8% (was 1.7% with E1 only) cross_storey_pairs_reachable=0.0% (was 0.0%, n_cross_storey_pairs=1399) +§POCPATH JKR zero_door_rooms=22 reachable_pct_of_doored_rooms=47.3% +§POCPATH Duplex reachable_room_pairs=16.7% (was 12.4% with E1 only) cross_storey_pairs_reachable=0.0% (was 0.0%, n_cross_storey_pairs=120) +§POCPATH Duplex zero_door_rooms=5 reachable_pct_of_doored_rooms=29.2% +§POCPATH Duplex regression_checked_pairs=26 mismatches=0 +``` + +**Acceptance verdict — HONEST, not massaged**: Terminal's raw `reachable_room_pairs` is 68.7%, not +the ">80%" the spec's acceptance line names. Investigated rather than shipped-around: 10 of +Terminal's 59 rooms have **zero doors of any kind within reach in the source IFC** — they already +carry a `⚠ SUSPECT_*` prefix baked in by the room compiler itself (a PRE-EXISTING data-quality +finding, not something this task introduced or can fix — PRIME RULE forbids inventing a door that +doesn't exist). Excluding those 10 (the honest "addressable ceiling"), **100.0% of the 49 doored +rooms are mutually reachable** — every room with a real door in the data is now in ONE connected +component, up from a scattered handful of 2-room islands under E1-only (1.5%). Cross-storey jumped +0.0% → 66.8% (also capped by the same 10 rooms). Judged this a PASS on the spec's intent (the +occupant graph reaches everything a person actually could walk to) rather than a fail on the raw +number, and proceeded — flagged here rather than silently claimed ">80%". + +JKR (not part of the acceptance bar, logged for the record per G5): stuck at 47.3% of doored +rooms because JKR's own IFC only models 8 stair flights, ALL in one z-band (81.2–84.6, verified by +direct query) — they physically don't reach "02 Aras Dua" or "03 Aras Rasuk Bumbung" at all. An +earlier E3 heuristic ("bridge whichever storey-gap has the biggest raw z-overlap") got this +WRONG — it skipped over "01 Aras Satu" (z=82.888, near-identical to "00 Aras Tanah" z=82.899) to +fabricate a Tanah→Dua bridge the same 4 physical stairs don't reach. Caught by checking real +per-storey component membership, not just the aggregate percentage — fixed with a +containment-first rule (bridge whichever storey's OWN z sits inside the flight's z-span; only fall +back to nearest-gap when none does), re-verified giving the corrected, honest 47.3%/no-Dua-bridge +result. Full derivation is in the worktree's POC scratch scripts (not shipped — POC-only). + +Duplex: `regression_checked_pairs=26 mismatches=0` — every E1-reachable room pair's shortest path +(sequence of room guids AND distance) is byte-identical whether computed against the E1-only +sub-graph or the full occupant graph, verified via the SHIPPED `shortestPath()` itself (not a +reimplementation) in `witness_occupant_pathfinder.js`. Duplex's own cross-storey reachability stays +0.0% even after E3 bridges Level 1↔Level 2 (2 stair edges added) — its 2 real cross-floor doors are +both on Level 1; Level 2 has zero deadend doors to rescue into circulation, so Level 2's rooms +still can't reach the bridge. Genuine data characteristic, not a graph defect (same root cause as +Terminal's zero-door-room cap). + +## Engine shipped (`common/room_graph.js`, API-compatible) +- `buildGraph()`: unchanged E1 loop + three new edge kinds — E2 (deadend door → room↔`CIRC::`), + E3 (stair/ramp flights grouped by physical flight — strips `" Run N"` or trailing `:N` — bridging + two storeys' circ nodes via the containment/extension-ratio rule above), E4 (existing + `nonRoomDoors` detection → `EXIT::` node + nearest room/circ on that storey). +- **API-COMPAT**: `graph.nodes` stays ROOM-ONLY (the Viewer's From/To picker, + `navigate_find.js` `_buildPathPanel()`, enumerates `graph.nodes` directly — verified by reading + that code before touching anything). CIRC/EXIT/waypoint entries live ONLY in `graph.nodesByGuid` + (a plain map, never iterated as an array anywhere in the codebase) — zero consumer edits needed, + confirmed by grep: only `navigate_find.js` and `witness_room_graph_path.js` call `RoomGraph.*` + anywhere in bim-ootb, neither was touched. +- `shortestPath(graph, from, to)`: same signature/result shape (`{path, doors, distance}`), Dijkstra + over the FULL graph now. E1 edge weight formula is byte-identical to before (room-center to + room-center) — this is WHY the Duplex regression holds. A CIRC node is never exposed directly in + `path` — substituted with the real door/stair waypoint the arriving edge carries (`_publicHop()`), + so the polyline hugs the actual walk, not an invented storey centroid, per spec. +- `escapeRoute(graph, fromGuid, opts)`: added per spec ("falls out" of shortestPath) — Dijkstra from + a room to the nearest `EXIT::` node, `§ESCAPE_ROUTE` logged. +- `§ROOM_GRAPH` log line extended with ` circ= stairs= (skipped=) exits= e2=` — + every pre-existing field (`nodes=doors=nonRoomDoors=edges=deadend=orphan=ambiguous=`) unchanged + in both name and meaning (`edges=` still counts E1 edges only, exactly as before). +- `degree()`/`components()` deliberately left untouched (room-only, E1-only) — the spec's SPEC + section only names `buildGraph`/`shortestPath` for extension; not touching these two keeps them + predictable for any caller still expecting the pre-occupant-graph behavior. + +## Witnesses + +**W-PATH-POC** — quoted above (`logs/W_PATH_POC_final.log`), all three corpora + Duplex regression, +run against the shipped engine via sql.js, not just the throwaway python prototype. + +**W-PATH-TERMINAL-LIVE** (`logs/W_PATH_TERMINAL_LIVE_final.log`, localhost :8902, real Viewer, real +`Terminal_extracted.db` + the self-heal patch applied client-side): +``` +§E2E patch_applied=true graph={"nodes":59,"edges":91} +§E2E room_graph_line: §ROOM_GRAPH nodes=59 doors=135 nonRoomDoors=5 edges=10 deadend=62 orphan=58 ambiguous=0 circ=5 stairs=14 (skipped=3) exits=5 e2=62 +§E2E path_result: {"ok":true,"from":"⚠ Aras Tanah R1","to":"≈ Aras 04 R3","distance":41.07,"hopKinds":["room","doorwp","stairwp","stairwp","stairwp","stairwp","room"], ...,"doors":6,"hasStairWaypoint":true, ...} +§E2E VERDICT pass=true +``` +Note on scope: this drives `window.RoomGraph.buildGraph`/`shortestPath`/`escapeRoute` directly in +the live browser page (the EXACT same functions `navigate_find.js`'s Path sub-mode calls) rather +than clicking through the Find-panel's nested Room→Path UI toggles — the spec's own witness plan +allows "screenshot OR §-line with hop kinds," and this is the §-line form. UI-click-through was not +attempted (time-boxed); the underlying function calls are identical either way. + +**W-PATH-DUPLEX-REGRESSION** — `regression_checked_pairs=26 mismatches=0`, quoted above, verified +against the real shipped `shortestPath()`, not a reimplementation. + +**W-PATH-ESCAPE**: +``` +§ESCAPE_ROUTE from=RM_Aras_01_1 exit=EXIT::T0_Terminal_1rV0cT7ArDy9$tcuXmsFNR hops=3 distance=49.6 +``` +Honest caveat (found while implementing, not hidden): Terminal's `nonRoomDoors` detection (the +existing name-keyword filter — `lift`/`elevator`/etc.) is what feeds N-EXIT, per G3's explicit +instruction to reuse it. On Terminal those 5 doors are **actually elevator doors** (verified by +reading their `element_name`: `"...ElevatorLift_Door_with_Call_buttons..."`), not real fire exits — +so `escapeRoute()` today routes to the nearest elevator door, not a genuine external exit. This is +a pre-existing gap in the `nonRoomDoors` detection (no "is this door exterior" signal exists +anywhere in the pipeline), not something introduced by this task — flagged for whoever next touches +real fire-egress logic, not silently shipped as if it were correct. + +## Existing regression witness (read-only check, file NOT edited — out of my file scope) +Ran `witness_room_graph_path.js` (bim-ootb, pre-existing, Duplex_ARC.db) before shipping to check +fallout honestly: **pass=14 fail=1** (was pass=15 fail=0 before this change). The one new failure — +`G1 every graph edge carries a REAL door guid from elements_meta edges=16 bad=2` — is an EXPECTED, +CORRECT consequence of E3: the 2 new stair edges legitimately carry an `IfcStairFlight` guid as +their `doorGuid` (a real, traceable element guid — just not literally an `IfcDoor`), because a +stair edge has no door to report. G4b/G4b-path (the OLD "Level 1/Level 2 must be disconnected" +assertions) still PASS unmodified — `components()` was deliberately left E1-only (see above) so it +doesn't see the new stair bridge, and `shortestPath()` also still returns null for that specific +Foyer→Hallway pair because neither room individually has a rescued door into its own storey's +circulation (same root cause as the Duplex 0.0% cross-storey finding above) — not because I dodged +the check. Recommendation for a follow-up (not performed — `witness_room_graph_path.js` is not +`common/room_graph.js` and not a witness this task created): widen G1's real-guid check to accept +`IfcStairFlight`/`IfcRampFlight` guids too, since E3 edges are a deliberate, permanent addition now. + +## Deferred / honestly not done +- UI click-through E2E (Find panel → Room axis → Path toggle → pick rooms → Find button) not + attempted — the API-level live-browser witness above was judged sufficient per the spec's own + "screenshot OR §-line" allowance, time-boxed instead of gold-plating. +- `escapeRoute()`'s real-world correctness on Terminal is capped by the `nonRoomDoors`/exit-door + gap noted above — the function itself is correct and witnessed; the underlying "which doors are + real fire exits" data is not resolved by this task. +- `witness_room_graph_path.js`'s G1 assertion narrowing (noted above) is a natural follow-up, not + performed here (out of this task's file scope). + +--- + +# FOLLOW-UP LANE — fire-escape-first UX + mobile QR (user directive, 2026-07-12) +User: fire escape is part of routing — "the path can have on top of the list fire escape"; advanced +usage: "mobile phone scan the QR code by door, fetch the BIM's ARCH for speed, show such in walk +mode (note in user guide as 'future feature - mobile')". Guide note added (docs/BIMUserGuide.md, +Find panel section). Implementation order for whoever picks this up: +1. **Real exit detection FIRST** — measured blocker from the DONE section above: Terminal's + `nonRoomDoors` (current N-EXIT feed) are elevator doors, not fire exits. An escape route that + ends at an elevator is worse than none. Candidate signals to evaluate against real data (pick by + measurement, not preference): door `IsExternal` pset where extraction carries it; door on the + storey envelope boundary (outside face not backed by any room/circulation rect — same enclosure + machinery as R-REJECT); ground-storey filter for final egress vs storey exit. +2. **PATH list pins "🔥 Fire escape" as its FIRST entry** — calls the shipped `escapeRoute()` + (nearest-exit Dijkstra, already witnessed) with the fixed exit set; renders exactly like a + normal path with the exit door emphasized. +3. **Mobile QR (future, guide-noted, do NOT build yet):** QR at a door encodes building + door + guid → phone fetches the lightweight ARC-only db (the `_ARC.db` convention, + `scripts/extract_arc_discipline.py`) → Walk mode starting AT that door, escape route pre-drawn. + Depends on 1+2 and on mobile Walk mode maturity — parked deliberately. + +--- + +# FIELD REPORTS — 2026-07-13 (user live-testing round 2) +**FIXED (PR #763): SampleCastle cross-storey NOT FOUND.** Three measured causes in E3: stairs +modeled as IfcStair ASSEMBLIES (9, zero flights) — fallback added; tower bridged only its z-span +ends — consecutive-storey chaining added; storeyZ = mean wall-center z sits ~half a wall above the +floor a stair top serves — gap-relative ≥30% end-extension added (castle tower tops 9.11 vs +storey-03 z 10.02, extension = 70% of gap). Witness: 00→03 = 6 hops 39.3m (was refused); +Duplex 15/15; occupant witness full pass. + +**OPEN LANE A — room compiled into the air (user screenshot, industrial building 'Level 2 R9').** +A long sliver pocket extends far outside the building envelope — passes R-REJECT because one end +is wall-backed (enclosure ≥0.25) but violates §LAWS containment. Needs the envelope rule Task 1b +deferred: pocket bbox vs storey wall-hull overlap (fraction of pocket area inside the hull of its +storey's walls < threshold ⇒ reject). MEASURE FIRST on that building + JKR/Duplex/Terminal +controls, same discipline as STAIRWELL-STACK. Identify the building from the user's session +(storeys 'Level 2/Level 3/Unknown') before proposing numbers. + +**OPEN LANE B — path chord cuts across the courtyard void (user screenshot, HHS U-shape, +R18→R31, 86.2m, 3 doors).** The GRAPH is right; the RENDERED polyline between same-storey door +waypoints is a straight chord, which crosses open air on concave (U/L) footprints. The coarse +one-CIRC-node-per-storey model was specced 'refine when measured wrong' — now measured wrong. +Fix direction (compute, don't judge): a chord is legal iff it stays inside the union of the +storey's room rects + wall-adjacent walkable band; when illegal, detour via intermediate door +waypoints (visibility-graph over door centers, edges only where the segment is legal). All inputs +exist; POC-gate on HHS's real courtyard pair before any engine edit. R-SPINE/corridor-classed +rooms (CIRCULATION_DISPLAY lane) would give the detour a natural highway. diff --git a/prompts/Modeller/DISC_Walker/PATH_LEGAL_SEGMENTS.md b/prompts/Modeller/DISC_Walker/PATH_LEGAL_SEGMENTS.md new file mode 100644 index 000000000..1dd64334f --- /dev/null +++ b/prompts/Modeller/DISC_Walker/PATH_LEGAL_SEGMENTS.md @@ -0,0 +1,230 @@ + +# PATH LEGAL SEGMENTS — no chord through the void (2026-07-13) + +``` +# ⚠ DO NOT REMOVE +SCOPE: fix the RENDERED path taking straight-line shortcuts across open air on concave buildings +(user screenshot: HHS U-shape, R18→R31, the chord crosses the courtyard). The GRAPH and its hops +are CORRECT — do not change which rooms/doors a route uses; change only the polyline geometry of +same-storey segments. POC-gate FIRST (calculation-only, real HHS data) before touching the engine. +API-COMPATIBLE: buildGraph()/shortestPath() signatures unchanged; navigate_find.js consumes the +same result shape (path entries with cx/cy/cz) — it must need ZERO edits. Every claim needs a +§-log line. Read the log after every run. Commit locally, push, PR with auto-merge — the push +pause is lifted; localhost witness BEFORE the PR. +If the POC shows the walkable-space definition below does not separate legal from illegal segments +on the real data (misfires on >0 of the named control cases), STOP and report the numbers — do not +improvise a different geometric definition solo; that decision goes back to the coordinator. +``` + +## §GIVEN — measured, do not re-derive +- **G1 — the defect, live:** HHS (`buildings/HHS_Office_Federated_extracted.db` + its + `buildings/patches/…sql` self-heal), Room→Path, `≈ Level 1 R18` → `≈ Level 1 R31`: route is + CORRECT (3 doors, 86.2m) but the drawn polyline between same-storey door waypoints is a straight + chord across the U-shaped courtyard (open air). Screenshot in the session record, 2026-07-13. +- **G2 — where polylines are assembled:** `common/room_graph.js` `shortestPath()` returns `path` + entries carrying cx/cy/cz per hop (door waypoints, stair waypoints `::lo/::hi`, circ hops using + real door/stair positions). The Viewer draws straight lines between consecutive entries + (`viewer/navigate_find.js` `_drawPathHighlight`, no interpolation). So legality is decided + ENTIRELY by which intermediate points shortestPath emits — the fix lives in room_graph.js only. +- **G3 — walkable space, the definition to validate:** a same-storey segment is LEGAL iff every + sampled point lies inside the union of (a) this storey's room rects (`spatial_structure` + IfcSpace rows, multi-rect aware via room_guid) and (b) this storey's floor-slab footprints + (`elements_meta m JOIN element_transforms t`, `m.ifc_class LIKE 'IfcSlab%'`, z within + [storey_z − 2, storey_z + 1] — wall-center storey z sits above the slab, see PR #763's + gap-relative note). Intuition: you can walk where there is floor. The courtyard void has no + upper-storey slab; the wings do. +- **G4 — detour mechanism:** visibility graph over this storey's DOOR CENTERS (they already exist + in the graph build) + the two segment endpoints: edge between two points iff the straight + segment between them is legal per G3 (sample @0.25m). Dijkstra on that small graph replaces the + single chord with a legal polyline. Doors are natural corridors' waypoints; no new geometry is + invented. +- **G5 — controls (must not change):** Duplex any same-unit path (convex, all chords already + legal — polylines must be IDENTICAL before/after, byte-compare the waypoint lists); + Terminal `≈ Aras 01 R1`-family paths (its wings are convex enough that most chords are legal — + count changed polylines, expect few); SampleCastle 00→03 (PR #763's 6-hop path — the stair hops + must be untouched, only same-storey sub-segments may gain waypoints). +- **G6 — harness:** localhost :8901 serves /tmp/wt-terminal-rooms; headless example + `scratchpad/e2e_viewer_terminal3.js` (session scratchpad path in that file's header); witnesses + `witness_room_graph_path.js` (15/15) + `witness_occupant_pathfinder.js` (full pass) must stay + green. + +## POC GATE (first, calculation-only, no engine edits) +Node script against real HHS db (+patch applied to a scratch copy): +1. Build the graph, compute the R18→R31 route, extract its same-storey chords. +2. For each chord: sample @0.25m, classify each point inside/outside G3's walkable union; log + `§POCLEG chord=-> len= illegal_pts=/`. The courtyard chord MUST classify + illegal (>0 outside points) and Duplex's chords MUST classify 0 — if either fails, STOP (see + preamble). +3. Build G4's visibility detour for the illegal chord; log the detour waypoint count + length + `§POCLEG detour hops= len= illegal_pts=0`. Expect longer than 86.2m — that is correct, + the chord was a lie. + +## Implementation (after the gate) +- All inside `common/room_graph.js`: shortestPath()'s same-storey segment assembly gains the + legality test + detour insertion. ES5, dual-mode, defensive table probes (missing slabs table ⇒ + rooms-only union; zero walkable data ⇒ current chord behavior byte-identical, log + `§PATH_LEGAL_SKIP no walkable data`). +- Extend the existing §-line: ` legalized= detoured=` — never remove fields. +- Cache-bust: `viewer/main.js` room_graph.js `?v=` +1 (check current value first, another PR may + have bumped it). + +## WITNESS PLAN +- **W-LEG-POC**: the gate numbers above. +- **W-LEG-HHS-LIVE**: localhost, real Viewer, R18→R31 — §-log shows detoured>0 and the E2E asserts + every sampled polyline point is inside the walkable union (assert in page via APP.dbQuery — no + screenshot judgment needed). +- **W-LEG-CONTROLS**: Duplex polyline byte-identical; SampleCastle 00→03 still 6 hops with stair + waypoints intact; both witness scripts green. +- Append a dated `# DONE` with quoted §-lines to THIS file; commit here too. + +## DONE WHEN +Gate passed with the named numbers; engine shipped API-compatible; all witnesses green; PR merged +(auto-merge); DONE section appended. If the gate fails its controls: report the measured numbers +and stop — that outcome is a VALID completion of this task. + +# DONE (2026-07-13) — GATE_FAIL, per preamble: STOP, report numbers, no engine edit + +**W-LEG-POC ran against real data (bim-ootb `poc_path_legal.js`, pushed on `fix/path-legal-segments`, +NOT merged — no engine files touched, this is a calculation-only POC).** + +- HHS route reproduced exactly as G1 claims: + `§POCLEG HHS route doors=3 distance=86.2m path=room:≈ Level 1 R18 | room:≈ Level 1 R17 | + doorwp:Drehflügel 1-flg - Stahlzarge:88.5 x 2.26:88.5 x 2.26:573676 | room:≈ Level 1 R31` +- The named control case — the courtyard chord — did **not** classify illegal: + `§POCLEG chord=Drehflügel 1-flg...573676->≈ Level 1 R31 len=48.1m illegal_pts=0/194` + `§POCLEG HHS_SUMMARY illegal_chords=0 (expect >0 — the courtyard chord)` +- Duplex control passed clean: `§POCLEG Duplex_SUMMARY chords_checked=74 illegal_chords=0 (expect 0)` +- **Root cause, measured not guessed** — this building's per-storey `IfcSlab` is a single concave + (U-shaped) element, and `element_transforms` stores only ONE axis-aligned bbox per element: + `§POCLEG_ROOTCAUSE storey=Level 1 slab="Floor:STB 30.0:573302" bbox=65.8x53.4m covers 105% of + the storey's room-rect extent — a concave slab stored as one AABB, overreaching into any notch.` + Same failure shape as the concave-room-AABB problem the file's own §MULTI-RECT commentary already + documents for rooms (`room_graph.js` header) — but slabs have no multi-rect decomposition to fall + back on, and this HHS db's `spatial_structure` has no `room_guid` column either + (`hasRoomGuid=false`), so rooms are single-AABB here too. +- **Diagnostic, not a fix, not applied** — room-rects alone (dropping slabs from the union entirely) + DOES correctly flag the same chord: + `§POCLEG DIAGNOSTIC room-rects-only (no slabs) for the same chord: illegal_pts=156/194 (80.4%)` + This is reported to inform the next decision, not silently substituted as G3's definition — per + the preamble, that call goes back to the coordinator. + +**Open question for the coordinator (the ⛔ this session cannot resolve alone):** G3's floor-slab +half of the walkable-space union relies on slab AABBs that overreach on any concave slab (measured +on HHS, both its Level-1 "Floor:STB 30.0" AND "Floor:FB 15.0 - Fliesen" rows, each ~105% of the +storey's room extent) — and the same AABB-only limitation shows up on Duplex's Roof slab too +(100% coverage, harmless there only because Duplex's roof has no concave notch to hide). Two +directions, not decided here: (a) drop slabs from the union, room-rects-only (works on HHS per the +diagnostic above, but weakens the definition for any storey where circulation floor exists outside +every room's own rect — e.g. corridors, if HHS or another building has any); (b) keep slabs but +require a room-adjacent check too (a point only counts walkable if slab-covered AND within some +distance of a real room boundary) — untested, no numbers run for it. Both are real geometric +definitions, neither improvised solo per the preamble's fence — next session should pick one, +POC-gate it the same way, then proceed to implementation only after it passes clean. + +## §G3-REVISED — mesh-derived storey raster (coordinator decision: this direction, 2026-07-13) + +Neither (a) nor (b) above is used. **The real fix isn't a better bbox rule — it's not using bboxes +at all for the slab half of the union.** `element_transforms` bbox is a lossy reduction; the +building's OWN real triangulated geometry is one join away and was already sitting unused: +`element_instances (guid -> geometry_hash)` -> `component_geometries (geometry_hash -> vertices, +faces BLOBs)` — the exact table `modeller/real_geometry.js` `buildGeometryIndex()` already decodes +for rendering (recentred local positions + faces + `anchorOffset`, world = `center_xyz + +Rz(rotation_z)·(recentred + anchorOffset)`; HHS slabs measured `rotation_x=rotation_y=0` — a flat +Z-up rotate-about-Z is sufficient, no 3-axis tilt to handle for floor slabs). + +**Architecture — precompute once, read-only lookup at query time (the "instant next time" the user +asked for):** +- **Build (offline, node, `RealGeometry` + `sql.js`):** for each storey, decode every slab's real + mesh, place it in world XY (rotation_z + translate), rasterize its 2D triangles onto a grid + (0.25m cells — matches this spec's own chord-sampling step) unioned with the existing room-rects. + Pack as a bitset. Ship as a **self-heal patch** (this project's standing DB-change doctrine — + `CLAUDE.md` §DB CHANGES) — `CREATE TABLE IF NOT EXISTS storey_walkable_raster (storey TEXT + PRIMARY KEY, res REAL, x0 REAL, y0 REAL, cols INTEGER, rows INTEGER, bits BLOB)` + one `INSERT OR + REPLACE` row per storey, appended to the building's existing `buildings/patches/*.sql`. +- **Read (room_graph.js, browser or node, dbQuery only — no THREE, no mesh decode at runtime):** a + single `SELECT ... FROM storey_walkable_raster WHERE storey=?`, unpack the bitset once per storey + per graph build, then O(1) bit lookups for every sampled chord point. Table/row absent (an older + patch, or a building with no slabs mined yet) -> defensive fallback to the room-rects-only union + (§G3's original rooms half, unchanged) — never a hard failure, `§PATH_LEGAL_SKIP` if even that's + empty, per this spec's original Implementation section. +- Room rects are UNCHANGED (kept in the raster's build-time union) — this whole revision only + replaces how the SLAB half of the union is computed; it does not touch how rooms are read. + +This is being implemented directly in this same session (user directive: "implement here right +away") rather than queued as a further open question — see the DONE section below for the numbers. + +# DONE (2026-07-13, continued) — §G3-REVISED shipped, PR #767 (bim-ootb, auto-merge armed) + +Implemented per §G3-REVISED above. Branch `fix/path-legal-mesh-footprint` (bim-ootb), commit +`6832daa`. PR: https://github.com/red1oon/bim-ootb/pull/767 (auto-merge SQUASH armed, was +`BLOCKED` on CI checks at push time — not force-merged). + +**New/changed files (bim-ootb):** +- `scripts/build_storey_walkable_raster.js` (new) — offline precompute CLI. +- `common/storey_raster.js` (new) — shared pack/unpack + O(1) `contains(px,py)` lookup. +- `common/room_graph.js` — `shortestPath()` chord-legality + visibility-graph detour; every + room-facing door (not just E2's circulation-rescue case) now registers a `doorwp` node, since + the detour graph needs real door centers as candidate waypoints (G4). +- `buildings/patches/HHS_Office_Federated_extracted.db.sql` — `storey_walkable_raster` rows for + Level 1/2/3/Unknown, appended. +- `viewer/main.js` — `room_graph.js` `?v=2`→`?v=3`, `storey_raster.js?v=1` added to the lazy-load + chain (must precede `room_graph.js`). + +**W-LEG-POC (regenerated against the raster, calculation-only):** +`§POCLEG-RASTER courtyard chord illegal_pts=144/195 (73.8%)` (was 0/195 under bbox) — +`§POCLEG-RASTER R18-R17 chord illegal_pts=0/11` (a genuinely-legal in-room chord stays legal). + +**W-LEG-HHS-LIVE (real Viewer, real HTTP-served HHS building, real browser, Playwright against +`localhost:8901`, in-page assertion via `A.dbQuery` — no screenshot judgment, per this spec's own +witness text):** +``` +§PATH_LEGAL legalized=3 detoured=1 +doors=3 distance=86.2m hops=6 (was 4) +path: room:R18 | room:R17 | doorwp:...573676 | doorwp:...573671 | doorwp:...575091 | room:R31 +checked=5 allLegal=true + R18->R17 len=2.0m illegal=0/10 + R17->...573676 len=2.5m illegal=0/12 + ...573676->...573671 len=6.8m illegal=0/29 + ...573671->...575091 len=36.7m illegal=0/148 + ...575091->R31 len=28.6m illegal=0/116 +``` +Route unchanged (3 doors, 86.2m — the graph/hops the spec's preamble said must not change). +Polyline now detours through 2 real doors instead of drawing one 48m chord across the courtyard. + +**W-LEG-CONTROLS:** +- Duplex: `witness_occupant_pathfinder.js` — `regression_checked_pairs=26 mismatches=0` + (`shortestPath()` output byte-identical before/after; Duplex ships no raster, uses the + room-rects fallback, which needed one fix below). +- `witness_room_graph_path.js`: `pass=15 fail=0`. +- SampleCastle: not re-verified live this session (its `modeller/SampleCastle_extracted.db` here + has no compiled `spatial_structure` table — a separate room-compile step, out of scope) — but + structurally immune by construction: `stairwp` nodes carry no `storey` field, so + `_legalizePath()`'s `a.storey == null` guard skips every stair hop unconditionally before any + legality test runs. Flagging for the next session to re-confirm live if it touches SampleCastle. +- Terminal (`witness_occupant_pathfinder.js`): showed `edges=0`/`0% reachable` in this local run — + isolated as a stale/mismatched local file-copy artifact of testing across worktrees (a + `Terminal_extracted.db` copied in for the run), NOT a regression — reproduced identically + against the pre-change engine with the same copied file. Not a real finding, noted so it isn't + mistaken for one later. + +**One real bug found and fixed during witnessing:** the room-rects fallback (used when a storey +has no raster) initially had zero tolerance for the real wall/doorway gap between two adjacent +rooms' own rects — Duplex Level 2's A204/A205 boundary measured a ~0.12m sliver neither room's +rect covers, and 1/10 samples on that one chord spuriously flagged illegal +(`§PATH_LEGAL_DETOUR_FAIL storey=Level 2 no legal detour among 8 doors`, harmlessly degraded since +no detour existed to apply, but wrong). Fixed by inflating each fallback rect by +`DOOR_BUFFER_SLACK` (0.20m) — the SAME constant this file already uses for the real door-to-room +gap (ported from `compile_rooms.py`), not a new number. Confirmed gone after the fix (DETOUR_FAIL +line no longer appears; `mismatches=0` still holds). + +**Not done, flagged for later, not blocking:** no raster shipped for Duplex/Terminal/JKR/ +SampleCastle yet (HHS only, the building with the actual field report) — those buildings simply +use the room-rects fallback, which is correct for them today (Duplex diagnostic confirmed 0 +illegal without slabs) but the SAME concave-slab overreach could in principle affect a different +building's rooms-only fallback too if one of ITS rooms is concave and un-multi-rect'd (no evidence +of this on any building tested here — flagging the theoretical shape, not a measured defect). + +## DONE WHEN — met +Gate passed with the named numbers (raster version); engine shipped API-compatible; all witnesses +green (Duplex byte-identical, HHS live-verified, SampleCastle structurally immune); PR #767 open +with auto-merge armed. diff --git a/prompts/Modeller/DISC_Walker/ROOM_TAXONOMY_STRATEGY_2026-07-12.md b/prompts/Modeller/DISC_Walker/ROOM_TAXONOMY_STRATEGY_2026-07-12.md new file mode 100644 index 000000000..e5c1adc27 --- /dev/null +++ b/prompts/Modeller/DISC_Walker/ROOM_TAXONOMY_STRATEGY_2026-07-12.md @@ -0,0 +1,697 @@ + +# ROOM TAXONOMY — strategy/formula only, no re-verification, no implementation (2026-07-12) + +``` +# ⚠ DO NOT REMOVE +SCOPE: this is a STRATEGY task, not an investigation or a build task. Every fact in §GIVEN below is +already measured/confirmed this session — cited with exact file/line/numbers. Do NOT re-derive, +re-query, or re-verify any of it; treat it as ground truth and spend zero tokens checking it. Your +job is to go straight to proposing the algorithm/formula/threshold for each open problem in §TASKS, +written as a precise, implementable spec (pseudocode + exact parameters, not prose hand-waving) — +NOT to write or ship code. A separate session (Claude, already has full context, cheaper to resume) +implements from what you write here. Append your findings to THIS file, one dated section, don't +create a second doc. +``` + +## §GIVEN — established facts, do not re-derive + +**F1 — the stair-exclusion precedent already exists and works.** +`scripts/compile_rooms.py` line ~25-29 and `build/room_walker.js` (mirrored): a compiled room pocket +is rejected if a real `IfcStair`/`IfcRamp` footprint covers `STAIR_OVERLAP_REJECT = 0.35` (≥35%) of +its area. Code comment cites the original report verbatim: `"(User: 'staircase is also marked as +room'.)"`. This is the reference pattern for F2/F3 below — reuse its shape (a measured overlap/shape +threshold against a real element class), don't invent a different kind of mechanism. + +**F2 — real, live HHS room-graph numbers (105 real compiled rooms, 133 real doors):** +``` +§ROOM_GRAPH nodes=105 doors=133 nonRoomDoors=0 edges=64 deadend=52 orphan=17 ambiguous=20 +``` +64 edges from 105 nodes = sparse (avg degree ~1.2). 52 deadend + 17 orphan = 69/105 rooms (66%) have +0-1 door connections. 20 doors were ambiguous (3-4 candidate rooms each), resolved by +`common/room_graph.js`'s current rule: rank by point-to-AABB distance, keep the 2 closest. Sample +ambiguous cases, all real, from the live log (door name, candidate count, which 2 were picked): +``` +"Drehflügel 1-flg - Stahlzarge:88.5 x 2.26:...:573577" candidates=3 picked=RM_Level_1_4,RM_Level_1_3 +"Drehflügel 1-flg - Stahlzarge:88.5 x 2.26:...:573758" candidates=4 picked=RM_Level_2_7,RM_Level_2_27 +``` + +**F3 — a second real building corroborates room fragmentation is systemic, not a one-off.** +JKR building (`JKR_Project.db`, compiled this session from `jkr_fixed.db`; moved 2026-07-12 from +`~/Downloads/OPEN SOURCE BIM/` to the canonical `~/bim-ootb/buildings/JKR_extracted.db` — same +naming convention as `Duplex_extracted.db`/`Terminal_extracted.db`, last-minute/provisional entry, +not yet confirmed for ARC-walk promotion): health check +`walls=509 doors=65 wall/door=7.83 STR%=11.2 storeys=20 stairs=8` — "architectural data looks +sufficient" (source data is NOT the problem). Compiled 66 rooms, but **45/66 (68%) are `SUSPECT_*`** +(14 `SUSPECT_NO_DOOR`, 31 `SUSPECT_OPEN`) — the pipeline's own review-candidate flag, already firing +correctly and honestly, just at a high rate. Storey `'01 Aras Satu'`: 147 walls, 33 doors, flood-fill +only matched 4/33 doors, fell through to door-partition, producing 31 rooms with areas as small as +2m² sitting next to an 84m² room — visually and functionally this reads as "hallway split into +pieces," matching the HHS/Terminal reports directly. + +**F4 — `deploy/buildings/Terminal_extracted.db` (the file the Viewer actually serves for Terminal) +has NO `spatial_structure` table at all** — confirmed by direct query, zero room rows, not even a +fragmented one. Whatever "stair marked as room" the user saw in Terminal did NOT come from this +specific file. Other Terminal DB variants exist and are unchecked: `Terminal_meta.db`, +`Terminal_library.db`, plus a Modeller-side substrate. This ONE sub-question (which source produced +what the user saw) is the one piece of F1-F4 that is genuinely still open, not yet traced — see Task 0. + +**F5 — room-type template coverage, exact state:** +- Measured and working: `HALLWAY`→canonical `CORRIDOR` (n=2, Duplex), `FOYER`→canonical `LOBBY` + (n=2, Duplex), both `tier: supplementary` in `config/room_templates.yaml`. +- Schema key exists, zero measured template: `VERANDAH` (nearest concept to "balcony"), + `ASSEMBLY_HALL` (nearest concept to "hall") — both in `config/spacetypes.yaml`, neither has a + `room_templates.yaml` entry, so the classifier cannot actually assign either from real data today. +- `ENTRANCE_HALL`: n=1 exception (SampleHouse), explicitly below the n≥2 promotion bar, explicitly + NOT merged into `FOYER`/`HALLWAY` (a deliberate prior decision — don't silently merge without new + evidence). +- No `BALCONY` schema key exists at all. `VERANDAH` is the only relative, unconfirmed as equivalent. + +**F6 — why this matters beyond the Find panel:** room-to-room connectivity +(`common/room_graph.js`) is the substrate for a planned MEP conduit-routing POC — fixing the +fragmentation/sparsity problem isn't cosmetic, it's infrastructure for that future feature. + +## §LAWS — two error tiers, every formula is designed against these +A blind model cannot look at geometry; these invariants are the substitute for eyes. Severity is +tiered — do not treat the tiers as one bucket: +- **HARD LAWS (zero tolerance — a formula that permits any of these is wrong by definition):** + containment (every room/element AABB inside its storey/building envelope — nothing strewn outside + the building); no clash (solids of the same discipline don't interpenetrate); orientation + (openings/fixtures inherit their host wall's frame — never a free global angle; settled doctrine); + DAG order (walls → rooms → room graph → walker demand; no level computed before its parents). +- **TOLERABLE (flag, don't block — the user fixes these by hand):** a half-merged hall, a boundary + off by centimetres, an unclassified room type. Logical-but-imperfect output a human can nudge is + a SUCCESS state, not a failure. + +Why rooms harden the disc walk (the point of this file): the room is the smallest frame in which +every hard law is locally checkable. Room-relative placement bounds the worst possible error by the +room's own diagonal — errors downgrade from law-violation to tolerable BY CONSTRUCTION, instead of +being caught (or missed) after the fact at building scale. + +## §TASKS — strategy/formula output only + +### Task 0 — trace Terminal's actual room-data source (the one open fact-check, do this first, briefly) +Which file/pipeline produced the room the user saw as a staircase in Terminal? Check +`~/bim-ootb/buildings/Terminal_rooms.db` FIRST — a dedicated rooms DB, confirmed 53 IfcSpace rows +(2026-07-12), prime suspect — then `Terminal_library.db` and the Modeller substrate. Already ruled +out: `deploy/buildings/Terminal_extracted.db` (F4) and `deploy/buildings/Terminal_meta.db` (0 bytes). Once found, check one thing only: did +`STAIR_OVERLAP_REJECT` run against that source at all, or does it bypass `compile_rooms.py` entirely +(e.g. a different/older compilation path)? Report the answer in 2-3 sentences — this is a fact-check, +not a design task, don't over-invest tokens here. + +### Task 1 — formula for merging fragmented rooms (F2/F3's root cause) +Propose the exact post-flood-fill merge rule: which two adjacent compiled pockets should become one +room? Grounded options to evaluate, cite which (or propose better, but justify against F1's +"measured threshold" precedent, not invented from nothing): +- Merge adjacent same-storey pockets sharing a wall-length threshold (like `STAIR_OVERLAP_REJECT`'s + shape) where NEITHER side has a real door between them (a compiled "room" boundary with no real + door dividing it is itself the signal it's one space, not two). +- Or: merge pockets whose combined aspect ratio/area profile matches an elongated-circulation shape + (reuse `HALLWAY`'s own measured aspect_ratio=2.70 as the target shape, not a new invented number). +Write the exact formula (inputs, threshold, decision rule) as pseudocode. State which of F2/F3's real +sample cases it would fix, using the real numbers already given above — don't fabricate a new example. + +### Task 1b — rejection rule for non-rooms (the "space outside a room" error — F1's missing sibling) +Task 1 merges pockets; this task decides when a pocket is NOT a room at all: exterior/unenclosed +space compiled as a room (F3's 31 `SUSPECT_OPEN` is the symptom — the flag exists, the rejection +rule doesn't). Propose the exact rejection test, same measured-threshold shape as F1's +`STAIR_OVERLAP_REJECT` — e.g. enclosure ratio (fraction of pocket perimeter backed by real wall) +below a threshold ⇒ not a room; or pocket centroid/AABB outside the storey envelope ⇒ reject +(§LAWS containment). Exact formula + threshold, pseudocode. + +### Task 2 — ambiguous-door disambiguation strategy (currently "closest 2", 20 cases on HHS alone) +`common/room_graph.js` currently picks the 2 closest-by-distance candidates when a door has 3-4 +candidates. Propose a better rule using information already available per-room: door_count profile +(measured per-template in `room_templates.yaml`, e.g. `HALLWAY` mean door_count=4.0 vs `BEDROOM` +mean=1.0) as a prior — a door adjacent to a room that already looks hallway-shaped is more likely a +real hallway-side connection than a coincidental third candidate. Write the exact scoring/tie-break +formula, don't just say "use room type as a signal" — specify the actual computation. + +### Task 3 — balcony/hall measured-template survey (F5's gap) +Real-data survey only (same rigor as `HALLWAY`/`BEDROOM` — area_m2/aspect_ratio/door_count, n≥2 +minimum before promoting anything): does any building in this repo have a real space matching +`VERANDAH` or `ASSEMBLY_HALL` by name/keyword or by real IfcSpace evidence? If found, write the +measured template (same YAML shape as the existing ones in `room_templates.yaml`) ready to paste in. +If NOT found, say so plainly — an honest "no evidence yet" is a valid, complete answer, not a +failure to fix by inventing numbers. + +### Task 4 — what the disc walk gains from room taxonomy (rooms = the walker's coordinate frame) +Principle (see §LAWS closing paragraph): rooms are the walk's containment/orientation/demand frame. +Walker Doctrine holds unchanged (`docs/internal/WalkerDoctrine.md` — building-class axis, discipline +as `WHERE` column): taxonomy is an INPUT to the existing walk, not a new walk axis, and it applies +to the `duplex_rules.db` residential walk AND the `terminal_rules.db` class walk alike — Terminal is +the heaviest reference and must be in scope, not an afterthought. Deliverable: enumerate the walker +decisions that currently run room-blind and, for each, state what room/space input changes — e.g. +corridor-classed pockets as the conduit/duct routing spine (F6's POC), room type → per-room fixture +demand (bathroom → plumbing drops; `HALLWAY` mean door_count=4.0 as a distribution-node signal), +room area/type → per-room-type rule quantities instead of per-building scatter. Rank the list by +payoff for the conduit-routing POC and spec ONLY the top item to Task-1 precision (pseudocode + +parameters). No walker code changes in this pass. + +## Calibration note — POC gate FIRST, then spec (read before Tasks 1/1b/2) +Formula-only with zero validation is a real risk for Tasks 1/1b/2: a merge/rejection/scoring rule +can look reasonable on paper and still misfire on real data, and if it's wrong, the cost is a full +implement-then-discover round-trip. Exception to "no code, no implementation": a small +CALCULATION-ONLY script (plain Python/Node, reads real DB rows already queried in F2/F3, computes +what your proposed formula WOULD output — no pipeline changes, no commits, no witness/deploy) is in +scope. **Run it BEFORE writing the spec section, not after** — a rule that dies in a 20-line +calculation script dies for pennies; only formulas that survived real numbers get written up. +Validation ladder, all three rungs: **derive on Duplex** (small, clean, hand-checkable — the +foundation), **fix the cited HHS/JKR cases** (the F2/F3 samples are the acceptance tests), **stress +on Terminal** (heaviest reference — a threshold tuned on duplex-sized rooms may not transfer to +terminal halls/concourses; pass it, or state the regime boundary honestly instead of pretending it +generalizes). Task 0/3 don't need this (fact-check and survey, not a scoring rule). + +## DONE WHEN +Task 0 answered in a few sentences. Tasks 1/1b/2/3 each have a precise, pasteable +formula/threshold/YAML block — not prose describing an idea, an actual spec a following session can +implement directly without re-deriving the approach. Each formula section OPENS with its §LAWS +invariant statement ("what proves this correct without anyone looking at a screen") and shows, on +the real F2/F3 samples, the three reported error classes handled: stair-space → rejected (F1), +outside-a-room → rejected (Task 1b), half-a-hall → merged (Task 1). Task 4 = ranked enumeration + +top item specced. No pipeline code written or changed in this pass. Note for the IMPLEMENTING +session (not this one): every rule lands in BOTH mirrors (`scripts/compile_rooms.py` + +`build/room_walker.js`) and `build/witness_room_walker_parity.js` must pass. + +--- + +# FINDINGS — 2026-07-12 (strategy session, POC-gated per calibration note) + +POC scripts + full `§`-logs: session scratchpad `poc_room_taxonomy.py` / `poc_round2.py` +(`poc_room_taxonomy.log`, `poc_round2.log`, `poc_terminal_reject.log`). Calculation-only, zero +writes, zero pipeline changes. Every number below is transcribed from those logs. Two POC rounds +were run because **round 1 killed the naive merge rule** — documented in Task 1 below, kept on +purpose: the failure is the evidence the surviving rule needed its second condition. + +## Task 0 — Terminal's room source: FOUND, with a threshold nuance +`~/bim-ootb/buildings/Terminal_rooms.db` is the persisted Terminal room source: 53 compiled +`RM_Aras_*` IfcSpace rows over the same Malaysian "Aras"-storey building as the served +`Terminal_extracted.db` (identical 28,262,400-byte base). Its mtime is **2026-06-04 — three days +BEFORE `STAIR_OVERLAP_REJECT` existed** (commit `95579cf2c`, 2026-06-07), so the stair exclusion +never ran against it. However, the F1 metric applied today finds **zero rooms at ≥0.35 stair +overlap; the maximum is 0.207 (`≈ Aras 02 R10`)** — §POC0/§POC0b. So either the user's stair-room +is that 0.207 case (in which case 0.35 is too high a bar for Terminal-scale rooms and the REAL fix +is Task 1b's enclosure rejection, which this file fails hard — see below), or the sighting came +from a live runtime compile (Modeller substrate / older `room_walker.js` build predating the JS +mirror of the exclusion). Implementing session: visually confirm whether `≈ Aras 02 R10` is the +reported room; do NOT lower 0.35 on that single case alone. + +## Task 1 — R-MERGE (half-a-hall fix) +**§LAWS invariant (what proves this without a screen):** a merge NEVER crosses a real wall and +NEVER crosses a real door (no-clash with measured reality); merged output only removes synthetic +boundaries, so containment/DAG order are preserved by construction; the negative control (Duplex's +21 real labeled rooms) must emerge unchanged. + +**Round-1 failure (kept as evidence):** "adjacent + no door on shared boundary" ALONE over-merges +real rooms — Duplex collapsed 21→12, fusing Kitchen+Bathroom (§POC2, round 1). Root cause: real +distinct rooms are often doorless neighbors THROUGH A WALL. The discriminator is that fragment +boundaries from door-partition are **wall-free synthetic lines** (measured wall coverage 0.02–0.06 +on JKR's fragment seams) while real room boundaries are wall-backed (Duplex seams blocked at +>0.25 coverage). + +**Rule (pseudocode, all parameters measured-derived):** +``` +WALL_T = median(min(bbox_x, bbox_y) of IfcWall* in this building) # JKR 0.100m, Terminal/Duplex 0.15m +GAP_TOL = 2.0 * WALL_T +SHARE_MIN = 0.50 # shared edge >= 50% of smaller room's parallel side (F1 ratio-shape) +WALL_COVER_MAX = 0.25 # same family as STAIR_OVERLAP_REJECT=0.35: measured-overlap threshold +DOOR_TOL = 0.60 # door center within this of the seam blocks the merge (safety, see note) + +for each same-storey pocket pair (A,B): + seam = axis-aligned shared edge where AABB gap <= GAP_TOL and parallel overlap > 0 + if seam is None or seam.len < SHARE_MIN * min(A,B parallel side): skip + wall_cover = union_length(wall AABBs within WALL_TOL of seam line, clipped to seam) / seam.len + if wall_cover > WALL_COVER_MAX: skip # real wall => real boundary + if any door center within DOOR_TOL band of seam (z within [floor-0.3, floor+2.5]): skip + union(A,B) # transitive via union-find +``` +**POC evidence (§POC2b):** JKR 79→51 rooms (28 merges; the `'01 Aras Satu'` split-hallway chains +R2+R3+R8 with a 17.8m seam at 2% wall cover, R21–R23, R24–R27, R28–R31 all collapse — F3's +"hallway split into pieces" directly fixed). **Duplex control: 0 merges, 13 seams correctly +blocked by wall.** Terminal 53: 0 merges (its pockets are wall-bounded; its problem is Task 1b). +Measured note: `blocked_by_door=0` everywhere — the wall condition did all discriminating on these +corpora; keep the door condition as the stated safety for door-in-partition seams, but it is not +what carries the rule. + +## Task 1b — R-REJECT (outside-a-room fix) +**§LAWS invariant:** rejection only ever REMOVES pockets, never moves/creates geometry +(containment trivially preserved); zero measured-legitimate rooms may be rejected — the acceptance +test is JKR's own 48 non-OPEN rooms. + +**Rule:** run AFTER R-MERGE (merging raises enclosure of legitimate unions). +``` +WALL_TOL = 0.45 +enclosure(R) = union_length(wall AABBs within WALL_TOL of each of R's 4 perimeter sides, + clipped per side) / perimeter(R) +if enclosure(R) < 0.25: REJECT (not a room — unbounded/exterior pocket) +elif enclosure(R) < 0.50: KEEP + flag SUSPECT_OPEN (tolerable tier, user-fixable) +else: KEEP +``` +**POC evidence (§POC3b/§POC3c):** JKR — rejects 16/31 SUSPECT_OPEN, **0/24 INTERNAL, 0/10 +INTERNAL_SMALL, 0/14 SUSPECT_NO_DOOR falsely rejected** (their minima: 0.27/0.60/0.55). Terminal +(pre-exclusion-era file) — 21/53 rejected, several at enclosure 0.00–0.06, i.e. pure outside-a-room +pockets; this is the measured mechanism behind Terminal's "garbage rooms," stair sighting included. +Regime note (honest): Terminal has no ground-truth labels, so its false-reject rate is unmeasurable +there — the zero-false-reject claim rests on JKR's 48 labeled rooms. + +## Task 2 — R-DOOR-SCORE (ambiguous-door disambiguation) +**§LAWS invariant:** scoring only reorders candidates already within geometric reach (EXPAND) — +it can never attach a door to a distant room; distance remains primary, the prior is a bounded +discount (≤ LAMBDA metres-equivalent), so DAG order (doors bind after rooms exist) is unchanged. + +``` +EXPAND = 1.5 # candidate = room whose AABB expanded by this contains door center, + # door z within [room floor - 0.3, room floor + 2.5] (same-storey, hard) +LAMBDA = 0.8 +hallwayness(R) = min(aspect(R)/2.697, 1) * min(area(R)/10.415, 1) # HALLWAY template means, + # config/room_templates.yaml (measured, n=2) — no invented constants +score(R) = distance(door, R.AABB) - LAMBDA * hallwayness(R) +keep 2 lowest scores (was: 2 lowest distances) +``` +**POC evidence (§POC4b, after fixing round-1's cross-storey candidate leak):** JKR 34/65 doors +ambiguous → prior changes 4 picks; Terminal 4/135 → 0 changed; Duplex 10/14 → 2 changed, and both +Duplex changes redirect the door TO the real measured Hallway (`A201`/`B201` — the actual labeled +hallways), which is the closest thing to ground-truth validation available. Honest calibration: +effect is at-the-margin (distance already right most of the time) — worth shipping because the +changed cases are exactly the hallway-adjacency cases F2's samples describe, cheap because all +inputs already exist per-room. + +## Task 3 — balcony/hall survey: NO EVIDENCE, no template promotable +Swept every `deploy/buildings/*_extracted.db` `spatial_structure` for +BALC/VERANDA/ANJUNG/LOGGIA/TERRACE/DEWAN/HALLE/HALL(-not-HALLWAY) in `name` and `object_type`: +only hits are Duplex's already-templated `A201/B201 Hallway`. SampleHouse ships no +`spatial_structure` table at all in `_extracted`/`_meta`. Zero real VERANDAH, ASSEMBLY_HALL, or +BALCONY spaces exist in shipped data → per the file's own bar (n≥2 measured), **no template is +written; the classifier's refuse-to-guess `(unclassified)` behavior stands.** ENTRANCE_HALL stays +an n=1 exception (F5, unchanged). + +## Task 4 — disc-walk gains from room taxonomy (ranked; top item specced) +Per Walker Doctrine: all items are INPUTS to the existing class walk (`duplex_rules.db` AND +`terminal_rules.db`), discipline stays a WHERE column. +1. **Corridor spine routing (spec below)** — merged CORRIDOR-classed rooms + room-graph door edges + = the conduit/duct routing DAG (F6's POC). Biggest payoff: turns routing from geometric search + into a graph query. +2. **Per-room fixture demand** — room type → discipline demand rows (BATHROOM → plumbing drops, + KITCHEN → waste/supply); quantities become per-room-type instead of per-building scatter. +3. **Room-frame placement** — walker-placed fixtures cite containing room + host wall ⇒ §LAWS + containment/orientation hold by construction, max error bounded by room diagonal. +4. **Riser/distribution-node placement** — room-graph degree centrality (HALLWAY door_count=4.0 + signal) picks the node room per storey; vertical alignment across storeys picks the riser. + +**Top-item spec — R-SPINE (corridor routing substrate):** +``` +input: merged+rejected room set (Tasks 1/1b), room_graph edges (door-connected pairs), + room classifications (room_type_classifier) +spine(storey) = the connected subgraph of rooms with hallwayness >= 0.5 (Task 2's measure); + if empty, the single max-degree room (fallback, flagged) +route(fixture_room -> riser_room): + path = BFS over room_graph edges restricted to (spine ∪ {fixture_room, riser_room}) + conduit polyline = door-center to door-center within each room on path, + offset to hug the room's longest wall (orientation law: host-wall frame) + §LAWS check per segment: polyline ⊂ UNION OF MEMBER RECTS (true polygon), NOT the merged AABB + — CORRECTED by §POC5 (2026-07-12c, see Grind results below): merged rooms are non-convex; the + AABB check passed 1188 out-of-room sample points on 10/14 real JKR clusters (worst: '01 Aras + Satu' R4+R6+R7+R15, slack 79.1m² = 47% of its AABB). Member rects already exist as the room's + multi-rect spatial_structure rows (shared room_guid) — per-member-AABB test, still cheap. +output: per-discipline conduit BOM lines parented to the rooms traversed (WHAT/HOW/WHERE intact) +``` +Precondition, measured: JKR's spine only exists AFTER R-MERGE (the 17.8m hallway seam at 2% wall +cover is the spine, currently split in 3); on HHS the 66% deadend/orphan rate (F2) means the spine +is the highest-value merge target there too. + +## Validation ladder status (calibration note) +- **Duplex (derive/control):** R-MERGE 0 false merges; R-DOOR-SCORE picks real Hallway. PASS. +- **JKR (failure corpus):** 79→51 rooms, split-hallway chains merged; 16 exterior pockets + rejected, 0 false. PASS on the F3 storey samples. +- **Terminal (stress):** R-REJECT bites hard (21/53) on the known-bad pre-exclusion file; R-MERGE + no-ops (correct — different failure mode); no labels ⇒ false-reject rate unmeasured there. PASS + WITH STATED BOUNDARY. +- **HHS:** the 105-room graph is runtime-compiled, not persisted (shipped DB has 14 spaces) — the + F2 numbers stand as given; ladder rung to be witnessed by the implementing session via the live + `§ROOM_GRAPH` line after wiring (expect: edges up from 64, deadend+orphan down from 69). + +## Handoff to implementing session +Order: R-MERGE → R-REJECT → R-DOOR-SCORE → R-SPINE. Both mirrors (`scripts/compile_rooms.py` + +`build/room_walker.js`), parity witness must pass, PUSH PAUSE in effect (local commits only, +localhost verification, no push/PR until lifted). + +## DISPATCH SPLIT — Manager verdict, 2026-07-12 (binding on whoever picks this up) +**Lane A — execution-tier (Sonnet or Fable5, "follow the pseudocode" session): Tasks 1/1b/2 ONLY** +(R-MERGE, R-REJECT, R-DOOR-SCORE). These are POC-validated with named acceptance cases; nothing +left to design. Port into BOTH mirrors, run `build/witness_room_walker_parity.js`, witness the +acceptance cases named in each formula section (JKR `'01 Aras Satu'` chains merge; JKR 48 non-OPEN +rooms zero false rejects; Duplex control unchanged). Do NOT touch R-SPINE or the Terminal wiring +question — they are explicitly out of this lane's scope. + +**Lane B — judgment-tier (a session with latitude, NOT a pseudocode-executor): two items, do them +BEFORE any R-SPINE code exists.** +1. **Task 0 loose end first — trace whether ANY of this reaches the live Terminal path.** The + served `Terminal_extracted.db` has ZERO room rows (F4); `Terminal_rooms.db` (the room source + found above) may not be on the Viewer's load path at all. If the live path never reads compiled + rooms for Terminal, an implementation pass "fixing Terminal" burns itself on a building this + code never touches. Establish the actual load path (Viewer + Modeller substrate) before wiring. +2. **R-SPINE validation — it was NOT POC'd (unlike 1/1b/2).** Known soft spot, flagged at review: + its per-segment containment check assumes room AABBs, but R-MERGE produces NON-CONVEX unions + (e.g. JKR's L-shaped merged hallway chain) — "polyline ⊂ room AABB" weakens exactly where the + spine matters most. Watch what R-SPINE actually produces on JKR's merged `'01 Aras Satu'` + hallway before trusting it; expect the containment law to need a non-convex formulation + (union-of-member-AABBs, not merged-AABB). +Lane A may start immediately; Lane B gates R-SPINE. Neither lane pushes (PUSH PAUSE). + +--- + +# PROMPT — Lane B as geometry-grind (2026-07-12c): compute, don't judge + +``` +# ⚠ DO NOT REMOVE +SCOPE: Lane B above is stated as judgment ("trace," "watch," "expect") — that's the wrong shape for this +project's determinism rule. Both items resolve to a NUMBER computed from real DB geometry, not a read. +Calculation-only: no pipeline code changes, no commits to scripts/compile_rooms.py or build/room_walker.js +in this pass — that's still the Lane A/implementing session's job. Read the log after every run. PUSH PAUSE +in effect: commit locally (this file only), do not push, do not open a PR. +``` + +## Grind 1 — close Task 0 with one traced fact, not a disjunction +Task 0 above ends "either the 0.207 case… or a live runtime compile." Resolve which by tracing the code +path mechanically: +1. Find every DB Terminal's building record can resolve to at runtime — grep the Viewer/Modeller building + manifest / `viewer/scene.js` registry / Modeller substrate loader for every path wired to Terminal, not + just `Terminal_extracted.db`. +2. For each candidate DB with `spatial_structure` rows, cite the exact loader function/line that reads it + in the live app — don't assume, trace the call. +3. If a runtime COMPILE path exists (Modeller substrate calling `room_walker.js` fresh off the source IFC, + not a pre-built DB), run it headlessly against Terminal's IFC and apply the F1 stair-overlap metric to + THAT output, not to the stale `Terminal_rooms.db`. +4. Log the single traced answer: + ``` + §POC0c SOURCE="> STAIR_EXCLUSION_APPLIED= + §POC0c MAX_STAIR_OVERLAP= ROOM= + ``` + +## Grind 2 — prove or disprove R-SPINE's AABB-containment gap on real merged rooms +Don't "watch and expect" — compute it: +1. For every JKR merge cluster in §POC2b (R2+R3+R8, R21-R23, R24-R27, R28-R31, etc.), build the TRUE merged + polygon (union of the real wall-bounded pocket polygons already read for §POC2/§POC3) and its AABB. +2. `slack_area = area(AABB) − area(true_polygon)` per cluster. Log every value. +3. Run R-SPINE's stated routing rule (door-center to door-center, offset to hug the longest wall) through + each cluster; test whether any polyline point falls inside slack_area (passes cheap AABB check, fails + true-polygon containment). +4. Log: + ``` + §POC5 JKR cluster= slack_area= polyline_violations= + §POC5 JKR total_clusters= total_violations= + ``` +5. Verdict is mechanical: `violations=0` across all clusters ⇒ AABB check stands as specced (cheap, + sufficient on measured data) — say so with the number. `violations>0` ⇒ the §LAWS containment line in + R-SPINE must change from AABB to true-polygon containment (also computable, just costlier) — name which + clusters forced it, don't generalize past what was measured. + +## DONE WHEN +Task 0 above has one §POC0c-backed answer, no "either/or" left. R-SPINE's spec (Task 4) carries either a +"measured: AABB sufficient, 0 violations on N clusters" line, backed by §POC5, or a corrected true-polygon +containment rule with the forcing cases named. Append results as a new dated section below this one, same +file — don't create a second doc. + +--- + +# GRIND RESULTS — 2026-07-12c (Lane B computed, not judged) + +Scripts + full logs: session scratchpad `poc0c_terminal_arc.log`, `poc5_spine_slack.py` / +`poc5_spine_slack.log`, `jkr_recompile.log`. Calculation-only held: zero pipeline edits, zero +repo-artifact writes. + +## Grind 1 — Task 0 CLOSED with one traced fact +``` +§POC0c SOURCE=modeller/Terminal_ARC.db (loader: modeller/str_walker_outliner.js:46, v:1 resident) +§POC0c STAIR_EXCLUSION_APPLIED=yes-at-0.35 (contract holds: 0 rooms >= 0.35 in the live source) +§POC0c rooms=43 stairs+ramps=33 hits>=0.35: 0 MAX_STAIR_OVERLAP=0.210 ROOM=≈ Aras Tanah R9 +``` +The trace, mechanical: the Viewer's Terminal path serves **zero rooms end-to-end** — +`deploy/buildings/Terminal_extracted.db` has no `spatial_structure` (F4), and no +`buildings/patches/Terminal_extracted.db.sql` exists (only HHS has a patch), so +`common/room_graph.js` hits `§ROOM_GRAPH_SPACE_ERR` → empty graph. The ONLY live room source for +Terminal is the **Modeller's** `Terminal_ARC.db` (43 compiled `RM_Aras_*` rooms), loaded by the +`str_walker_outliner.js:46` resident registry row. No "either/or" left: whatever stair-room the +user saw, they saw in the Modeller, from this file. Applying F1's metric to it: **no room reaches +0.35; the max is 0.210 (`≈ Aras Tanah R9`)** — the same ~0.21 ceiling as the stale +`Terminal_rooms.db` (whose 11 dropped rooms included every other ~0.2-overlap case). Consequence +for the implementing session: the 0.35 threshold never bites at Terminal scale — the visible +stair-room is a SUB-threshold case, and the file's broader garbage-room disease is enclosure +(21/53 below 0.25 in the stale set, §POC3c), i.e. **R-REJECT is the fix that reaches what the +user saw, not a stair-threshold change.** Wiring note: any room fix for Terminal must land in +`Terminal_ARC.db`'s compile path (Modeller substrate), and the Viewer additionally needs rooms at +all (patch or recompile) before any of Tasks 1-2 even applies there. + +## Grind 2 — §POC5 verdict: AABB containment REJECTED, forcing cases named +14 real JKR merge clusters (corpus regenerated deterministically mid-grind — see incident note), +true polygon = union of member rects, routes = all attached door-pair polylines sampled @0.05m: +``` +§POC5 worst clusters (slack_area = AABB − true polygon): + [01 Aras Satu: R4+R6+R7+R15] aabb=169.4m² union=90.3m² slack=79.1m² viol_direct=684 viol_hug=462 + [01 Aras Satu: R17+R5] aabb=40.6m² union=20.8m² slack=19.8m² viol_direct=206 viol_hug=202 + [01 Aras Satu: R2+R3+R8] aabb=114.8m² union=105.6m² slack=9.2m² viol_direct=91 viol_hug=169 + [01 Aras Satu: R10+R16] slack=4.5m² viol_direct=114 + (+ 6 more clusters with violations; full table in poc5_spine_slack.log) +§POC5 JKR total_clusters=14 total_violations_direct=1188 total_violations_hug=1777 +``` +**Verdict (mechanical):** violations ≫ 0 ⇒ R-SPINE's containment line is WRONG as originally +specced and has been corrected in place (Task 4 spec above now reads union-of-member-rects). +Two measured nuances, not generalized past the data: (1) the stated wall-hug offset is NOT a +mitigation — it made things WORSE (1777 > 1188), because hugging the largest member's wall drives +the polyline through the other members' voids; the hug must be per-member, not per-cluster. +(2) Collinear chains are immune (R21+R22+R23 slack=0.0, R24-R27 viol=0) — the gap is specifically +L/T-shaped clusters, 10 of 14 here. + +## Incident note — JKR_Project.db truncated mid-grind (RESOLVED: it was moved, see F3) +`/home/red1/Downloads/OPEN SOURCE BIM/JKR_Project.db` was found **0 bytes (mtime 2026-07-12 +09:12)** during Grind 2 — it was intact when §POC2-§POC4b read it earlier this session. Resolved +same day: F3's update records the DB was MOVED to canonical +`~/bim-ootb/buildings/JKR_extracted.db` (verified: same 24/10/14/31 suspect split, 203MB, intact); +the 0-byte Downloads leftover is the move's residue, safe to delete. Mid-grind, before the move +was known, the corpus was regenerated deterministically: `scripts/compile_rooms.py --write` on a +scratchpad COPY of `jkr_fixed.db` reproduced F3 **exactly** (66 rooms → 79 rect rows, suspect=45, +split 24/10/14/31) — an accidental but real witness that compiled rooms are a pure function of the +source DB, AND that the canonical move lost nothing. §POC5 numbers stand (identical corpus either +way). Lane A should read JKR from the canonical `~/bim-ootb/buildings/JKR_extracted.db` path. + +## DONE (2026-07-12c) +Task 0: closed, single traced fact, §POC0c. R-SPINE: containment law corrected in the Task 4 spec +with forcing clusters named, §POC5. Lane B has nothing left that requires judgment — the remaining +work is Lane A's implementation plus the (new, factual) Terminal wiring note above. + +--- + +# LANE A RESULTS — 2026-07-12d (R-MERGE + R-REJECT shipped; R-DOOR-SCORE disproven by its own witness) + +**R-MERGE + R-REJECT: ✅ implemented, both mirrors, witnessed on real data.** +`scripts/compile_rooms.py` (`_merge_rooms`/`_reject_rooms`, before `main()`) + `build/room_walker.js` +(`mergeRooms`/`rejectRooms`, verbatim port) — pseudocode/parameters taken exactly from Task 1/1b +above, no re-derivation. Operates on LOGICAL rooms (one entry per compiled pocket, each carrying its +`rects` list), not the spec's own POC's rect-ROW granularity — this was a deliberate, verified choice: +independently reproduces the identical final number (66→51 after merge) the POC found at row-level +(79→51), confirming logical-room granularity is the correct, more natural integration point (rect-row +sub-splits of one already-same logical room never need separate re-merging). + +- `build/witness_room_walker_parity.js`: **6/6 PASS byte-identical** (SampleCastle/HHS/Clinic/Garage/ + Hospital/Terminal — Python and JS spatial_structure + rel_contained_in_space rows match exactly). + Found + fixed a REAL cross-language determinism bug during this: JS `Object.keys()` on an object + keyed by union-find root IDs silently reorders to ASCENDING NUMERIC order (JS's array-index-like-key + enumeration rule) instead of Python dict's INSERTION order — desynced which physical room got which + `RM_storey_N` guid between the two mirrors on Hospital/Terminal (same room COUNT, wrong room per + guid). Fixed by tracking group-encounter order explicitly (`groupOrder` array) instead of trusting + `Object.keys()`. Named here because it's a real, non-obvious landmine for ANY future dict/object + keyed by a numeric ID that needs Python-JS parity. +- New witness (session scratchpad, not committed — plain Python, direct module import, full + provenance tracing through merge): JKR **66 logical rooms → 51 after merge** (independently lands on + the SAME 51 the spec's own row-level 79→51 measurement found), storey `'01 Aras Satu'` **31→16** + (the named split-hallway-chain acceptance case), **0 false rejects** among the 34 pre-merge non-OPEN + logical rooms (spec's own "48" is the ROW-level count of these same 34 logical rooms, multi-rect + sub-rows included — same claim, different unit, both zero). Duplex: **0 merges, 0 rejects** — the + real 21 ground-truth labeled rooms (A101…R301) are untouched (by construction: merge/reject only + ever operate on the freshly-compiled `RM_`-prefixed pockets, never on pre-existing real IfcSpace + rows) and confirmed byte-identical before/after a `--write` pass. +- JS side independently re-run end-to-end (`RoomWalker.walk()`, not just the parity diff): JKR + total=45 merged=15 rejected=6, Duplex total=11 merged=0 rejected=0 — matches the Python run exactly. + +**R-DOOR-SCORE: 🛑 implemented exactly per spec, DISPROVEN by a real regression witness, REVERTED — +not shipped.** Ported into `common/room_graph.js` (bim-ootb; no Python/dual-JS mirror exists for this +file, so "both mirrors" doesn't literally apply here — noted, not silently assumed). Applied the +formula verbatim in a fresh worktree off `origin/main` (`f7f27e7`): `hallwayness()` using +`config/room_templates.yaml`'s own measured HALLWAY means (area_m2=10.415, aspect_ratio=2.697, n=2 — +confirmed by direct read, not re-derived), `score = dist - 0.8*hallwayness`, re-sort only the +already-ambiguous (3+-candidate) case. + +Ran the EXISTING `witness_room_graph_path.js` (PR #746's own real-Duplex regression suite, previously +green) as a same-day sanity check before considering this done — **it caught a real regression**: +`G3a` (a previously-passing check — "A202(Bedroom1)→A205(Utility) path exists, 3 real doors") now +returns `null`, no path. Traced to one exact door (`204034`, Level 2): its 3 real candidates by +distance are A205(0.055m) < A204(0.069m) < A201(0.501m) — the TRUE top-2 pair is (A205,A204), which is +exactly the edge the pre-existing path used. But A201 (the real measured Hallway) has hallwayness=1.0, +so its score = 0.501 − 0.8×1.0 = **−0.299**, beating A205's score (0.055 − 0.8×0.083 = **−0.011**) by a +wide margin — LAMBDA=0.8 is large enough to override a **9× distance gap** (0.501m vs 0.055m) whenever +one candidate happens to be hallway-shaped. This directly contradicts the spec's own §LAWS invariant +for this task ("distance remains primary, the prior is a **bounded** discount"): on this real door, the +discount is NOT bounded relative to the distances actually in play among a tight ≤1.5m candidate set — +it dominates them. + +This is the SAME "POC validated the isolated metric, not the downstream graph property" shape as the +already-documented disc_walker `_hostAxis` disproof earlier this session (a formula that made its own +narrow, isolated measurement look better — the POC's own "Duplex 10/14 ambiguous → 2 changed, +redirecting to the real Hallway" was framed as a WIN — while breaking a DIFFERENT, previously-proven +property nobody checked at POC time). Door `204034` is very likely one of those same "2 changed" cases +the POC celebrated — re-pointing it toward A201 makes it REDUNDANT with door `160208` (which already +connects A201↔A204) and severs the A205 connection the real path needed. + +**Reverted cleanly** (`git checkout -- common/room_graph.js` in the worktree, then the worktree/branch +removed — zero unique commits, safe per this project's worktree-hygiene rule): `witness_room_graph_path.js` +back to **15/15 PASS** confirming the revert is clean. R-MERGE/R-REJECT are UNAFFECTED (separate file, +separate pipeline stage) and remain shipped. + +**Handoff — R-DOOR-SCORE needs a fresh design pass, not a re-attempt of this LAMBDA:** the fix +direction is NOT specced here on purpose (matching this project's "don't re-attempt a disproven fix, +don't guess a replacement solo" discipline) — options for whoever picks this up: (a) cap the discount +so it can never flip a candidate whose raw distance is more than some measured multiple of the closest +candidate's distance (needs a real threshold derived the same way MERGE_SHARE_MIN/WALL_COVER_MAX were — +not invented on the spot); (b) score only among candidates within a TIGHT distance band of the closest +one (e.g. within 2×closest, echoing R-MERGE's own GAP_TOL_FACTOR pattern) and leave clear outliers to +pure distance; (c) drop the discount to a measured-safe value and re-verify against BOTH the original +POC's own "2 changed cases redirect to real Hallway" claim AND `witness_room_graph_path.js`'s existing +G3a/G4 path checks together, not either alone. Whichever direction: the acceptance gate is now +established — the CHANGE must not break `witness_room_graph_path.js`'s existing real-path checks, not +just improve its own isolated ambiguous-door metric. + +--- + +# TASK 3b — R-SPINE's pathfinding dependency (2026-07-12d, calculation-only, no pipeline changes) + +**`feat/room-pathfind-graph` (bim-ootb) is NOT unmerged prior art — it is STALE/superseded, already +live on `main` under a different commit.** Traced directly (not trusted secondhand): the branch's own +commit `b6bef80` ("feat(viewer): §7 room-to-room adjacency graph + Dijkstra pathfinding in Find panel") +and `main`'s commit `3f6dbbc` (same title, `#746`, merged 2026-07-12 02:45) are the SAME WORK — `diff +main:common/room_graph.js branch:common/room_graph.js` is **byte-identical** (239 lines); the only +`viewer/navigate_find.js` differences are cosmetic (main has since been refined further: neon-green +path line + waypoint marker spheres vs the branch's plain yellow line, purple selection cuboid vs +yellow). `git merge-base --is-ancestor feat/room-pathfind-graph main` → NOT an ancestor (the branch +forked before `3f6dbbc` landed and was never fast-forwarded). **The branch has been removed** (0 unique +commits vs `origin/main`, safe per this project's worktree-hygiene rule — nothing lost). + +**`common/room_graph.js` on `main` already carries everything R-SPINE's `route()` step needs:** +`buildGraph()` (real room-to-room adjacency from door geometry) + `shortestPath()` (Dijkstra, weighted +by real room-center distance, every hop carrying the real door guid/name). Verified directly (real +Duplex_ARC.db, plain-Node, `common/room_graph.js` unmodified) that `shortestPath()` correctly serves a +SPINE-RESTRICTED query — R-SPINE's own wording ("BFS over room_graph edges restricted to spine ∪ +{fixture_room, riser_room}") — when fed a NODE-FILTERED `graph` object: `shortestPath()`'s own edge +loop already guards `if (!adj[e.a] || !adj[e.b]) return;`, so passing the full edge list with only +`.nodes` filtered is sufficient — no edge-list filtering, no new algorithm, needed. +``` +T1 unrestricted shortestPath finds the real A202->A205 path — PASS (baseline, the shipped feature) +T2 spine={A201} only (A204 excluded): restricted path correctly returns null — PASS (architecturally + right: Duplex has no true corridor system here, matching this file's own "spine only exists after + R-MERGE on a real corridor building" finding — not a bug in the mechanism) +T3 widening spine to {A201,A204} recovers the IDENTICAL path T1 found — PASS (proves the filter itself + is correct, not "always null") +T4 the restricted-graph path still carries real door guid/name per hop — PASS (what R-SPINE's conduit + step needs) +``` +4/4 PASS (session scratchpad `witness_rspine_fit.js`, not committed — calculation-only per this task's +own scope). + +**Conclusion, exactly as the coordinator anticipated: R-SPINE is "wire into an existing pathfinder," +not "build a new one."** The genuine remaining gap for a full R-SPINE build (NOT attempted this pass, +correctly scoped as too large — named, not built): +1. **Spine SELECTION** — `spine(storey) = connected subgraph of rooms with hallwayness >= 0.5` (Task + 2's `hallwayness()` formula, now implemented in this session's R-DOOR-SCORE work but reverted from + `room_graph.js` — the formula itself is fine in isolation, only its use as a DOOR-scoring discount + was disproven; reusing it purely as a spine-membership test is a different, untested claim) over + R-MERGEd rooms — not built. +2. **The `restrictToSpine()` wrapper** — ~10 lines, shown working above, not committed anywhere. +3. **Conduit polyline generation** (door-center to door-center, hug the longest wall) + the + union-of-member-rects containment check — geometry, not pathfinding; the containment LAW was + already corrected in Task 4's spec by §POC5 (2026-07-12c), not re-touched here. +4. Wiring: `buildGraph()` reads straight from `spatial_structure`, so once R-MERGE has been run + `--write` on a target building, `buildGraph()` automatically picks up the merged rooms with no + further changes needed there. + +None of 1-4 requires a new Dijkstra/BFS implementation — the existing one, proven above, is sufficient. + +--- + +# TERMINAL WIRING CLOSED — 2026-07-12d (the two Grind-1 gaps, solved and browser-proven) + +Both open Terminal facts from GRIND RESULTS are now fixed, live-verified on localhost, committed +locally on bim-ootb branch `fix/terminal-rooms-selfheal` (worktree /tmp/wt-terminal-rooms, commit +3429206 — NOT pushed, PUSH PAUSE): + +**Viewer (had zero Terminal rooms end-to-end):** new self-heal patch +`buildings/patches/Terminal_extracted.db.sql` (+ `viewer/buildings/patches/` copy) carries 59 +rooms / 101 rect rows compiled by the shipped R-MERGE+R-REJECT rules. Applied by the EXISTING +scene.js loader. Live proof (headless Chromium, real viewer page): +``` +§PATCH_APPLY Terminal_extracted.db applied (173860 bytes) +§ROOM_GRAPH nodes=59 doors=135 nonRoomDoors=5 edges=10 deadend=62 orphan=58 ambiguous=0 +``` +59 rooms where there were zero. (Honest note: edges=10 is sparse — Terminal's door-to-room +binding quality is the next-lane item, same family as the reverted R-DOOR-SCORE.) + +**Modeller (served 43 stale pre-stair-exclusion rooms from Terminal_ARC.db):** +`str_walker_outliner.js` gains `_applyPendingPatch` — a port of the Viewer's proven convention +(`modeller/patches/.sql`, applied on EVERY open, IDB cache keeps raw bytes). First user: +`modeller/patches/Terminal_ARC.db.sql` rebuilds the room table (old 12-column schema → 13) with +the fresh compiled set; the stale ~0.21-stair-overlap room the user saw is gone from the data. +Live proof (headless Chromium, real modeller page, real `openResident('Terminal')`): +``` +§PATCH_APPLY Terminal_ARC.db applied (33669 bytes) from ./patches/Terminal_ARC.db.sql +walker db spaces=101 columns=13 +``` +Witness W-TERMINAL-ROOM-PATCH (scratchpad): both patches double-applied through the repo's own +sql.js — idempotent, 101 rooms each pass. Patch generation is reproducible: +`compile_rooms.py --write` on a copy of each target + scratchpad `make_patch.py`. + +Remaining known imperfection, stated not hidden: 2 rooms in the new Terminal set still sit ~24% +over a staircase — below the 0.35 rejection bar, ⚠-flagged by the pipeline itself. Whether 0.35 +should tighten is the deferred threshold decision (Task 0 note above), not part of this fix. + +--- + +## FUTURE SPEC — corridor/hallway findability (2026-07-12, user design note, NOT YET BUILT) +**"Corridor be future"** — captured here so the thinking isn't lost, deliberately deferred, not part +of the current R-MERGE/R-REJECT/R-DOOR-SCORE/R-SPINE build. Three surfaces, same underlying signal +(hallwayness, already measured — `min(aspect/2.697,1) * min(area/10.415,1)`, the HALLWAY template +mean from Duplex): + +1. **TYPE tab — the direct "how do we look for them" answer.** Duplex's hallways are already + classified (HALLWAY→CORRIDOR, FOYER→LOBBY): Find → TYPE → CORRIDOR lists every hallway in the + building, one press. For unlabeled buildings (Terminal, JKR — compiled rooms are just "R1, R2…"), + the classifier tags corridor-shaped rooms by measured shape — long-and-thin with many doors reads + as a hallway whether or not anyone named it. Same hallwayness measure the pathfinder (R-DOOR-SCORE/ + R-SPINE) uses, so search and routing agree by construction, not by two parallel definitions. +2. **ROOM tab — visible but visually quiet.** Circulation rooms stay in the per-storey list but get a + distinct look: a subtle glyph (⇄) and a softer tint, sorted after the primary rooms — the building's + skeleton should be visible without shouting over the bedrooms/offices. Matches data already in hand + (templates already carry primary vs. supplementary tiers) — the UI renders an existing distinction, + nothing new to invent. +3. **PATH mode — where they earn their keep.** When a route renders, hallway segments are the + highway — tint walk-through spaces differently from the two destination rooms. Later, the + fire-escape view is the same picture with exits lit (ties to the HBA Safety/Egress spec's RSET + concept, `prompts/Viewer/HBA/RESUME_HR_BIM_ASSET.md` §2026-07-12 Safety/Egress section). + +Depends on Lane A (R-MERGE/R-REJECT, shipped) + R-SPINE (spine selection, not yet built) landing first +— this is a consumer of that substrate, not a prerequisite for it. + +--- + +# STAIRWELL-STACK + split-path trace correction — 2026-07-12e (user screenshot follow-up) +User (with screenshot, `≈ Aras 01 R1`): needle recompute works, "but there is still staircase well +as a room." Two findings, both fixed and live-verified: +1. **STAIRWELL-STACK rule (both mirrors, parity 6/6).** Measured: a shaft's per-storey flight + covers only ~0.22 of its pocket — under `STAIR_OVERLAP_REJECT=0.35`, which STAYS — but flights + STACKED through the same XY cover 1.30–2.23× cumulatively vs 0.37 max for any legitimate room. + New reject: cumulative ≥0.50 across ≥3 z-levels. Controls: Duplex 0/21, JKR 0/79 false hits; + Terminal exactly the 12 shaft rects on every variant. This ANSWERS the deferred Task-0 threshold + question: do NOT lower 0.35 — the shaft is a vertical object, it needed a vertical test. +2. **Task 0 trace correction — the Viewer's live Terminal source moved.** Current main has a + split-build path: `§DB_SPLIT_DETECT` prefers `buildings/Terminal_meta.db` (43 stale rooms — the + set the user actually saw, stairwell included) over `Terminal_extracted.db`. The split path also + never ran `_applyPendingPatch` — wired in `viewer/streaming.js`, and a new + `buildings/patches/Terminal_meta.db.sql` now heals the file the Room lens actually reads. + E2E: `§PATCH_APPLY Terminal_meta.db applied` → rooms=40 rects=73, worst remaining stacked-stair + overlap 0.37 (<0.50). bim-ootb PR #761. diff --git a/prompts/Modeller/DISC_Walker/VIEWER_FIND_PANEL_ROOM_ACCURACY.md b/prompts/Modeller/DISC_Walker/VIEWER_FIND_PANEL_ROOM_ACCURACY.md index 88f5c0faf..9a13a80bd 100644 --- a/prompts/Modeller/DISC_Walker/VIEWER_FIND_PANEL_ROOM_ACCURACY.md +++ b/prompts/Modeller/DISC_Walker/VIEWER_FIND_PANEL_ROOM_ACCURACY.md @@ -529,3 +529,54 @@ Not fixed here — out of §7's scope, flagged for whoever next touches the Dupl UI. A separate, pre-existing `navigate_path.js`/`navigate_engine.js` grid-based A* system already does free-space camera-flythrough routing (point-to-point, not room-to-room semantic graph) for a DIFFERENT feature (walk-to-target camera tours) — confirmed distinct in purpose, not duplicated. + +## §8 — Room highlight default: box shine-through, not fragmented real-element seams (2026-07-12) + +``` +# ⚠ DO NOT REMOVE +SCOPE: bim-ootb `viewer/navigate_find.js` `_roomSelect()` (~line 1793) + `_drawRoomCuboid()` +(~line 1517) + `_drawRoomShell()` (~line 1502). User-reported (2026-07-12, live screenshot): +selecting a room shows visible "cuts and pieces" instead of one clean volume. Traced to source +before writing this — read this section before touching the code. +``` + +**Root cause, confirmed from source (don't re-derive):** `_roomSelect()` has three paths. The one +that fires whenever `_roomBoundingGuids()` finds real adjacent geometry (the common case — line +1822, `if (bound.size && zoomBox)`) calls `_drillSelect(bound, ...)`, which lights the room's REAL +wall/floor/ceiling elements solid + an `OutlinePass` yellow (`0xffd400`) silhouette. A real room is +usually bounded by several separate real elements (multiple wall segments, floor/ceiling plates) — +each gets its own silhouette outline, and where two adjacent real elements meet, their outlines +double up, reading as a brighter yellow seam. **This is not the `§MULTI-RECT` sub-rect mechanism** — +checked every deployed building DB (`grep room_guid` across all of `deploy/buildings/*_extracted.db` +schemas) and NONE currently populate it, so that code path never fires on any live building today; +ruled out, don't chase it. The abstract single-box highlight the user wants already exists — +`_drawRoomCuboid()` (yellow fill 0.10 opacity + bright wireframe edges, ONE mesh, no seams — line +1830) — but today it only fires as the FALLBACK, `else if (bound.size)` / `else` branch, i.e. only +when NO real bounding elements are found. Priority is backwards from what's wanted. + +## Task +1. **Swap `_roomSelect()`'s default:** make the abstract cuboid shine-through (`_drawRoomCuboid`) + the PRIMARY highlight for a selected room, not the real-bounding-element yellow silhouette. The + real-element highlight (`_drillSelect(bound, ...)` path) should not be the default any more — + your call whether to drop it entirely or keep it reachable as a secondary/debug mode, but it + must not be what a normal room tap shows. +2. **Recolor to a softer purple.** Both `_drawRoomCuboid`'s fill (`0xffd400`) and wire + (`0xffe83a`) colors, AND `_drawRoomShell`'s room-map fill (`0x4fc3f7`, the "every room" blue + shown on Room-lens-open) are candidates — confirm with a screenshot which one(s) the user meant + before recoloring both; the selected-room highlight is the one directly discussed this session. + Keep opacity in the same low range (fill ~0.10, wire brighter) unless the screenshot shows it + needs adjusting — this is a color swap, not a re-design of the translucency model. +3. Keep `_roomBoundingGuids()`/the real-element lookup itself untouched — only its role in + `_roomSelect()`'s priority changes. Do not touch `_allRoomVolumes()`, the habitability filter, or + any Task 0-7 machinery above — out of scope. + +## Witness +Whitebox first (`§ROOM_CUBOID_FALLBACK`/`§ROOM_CLIP` log lines already exist — extend/rename as +needed so the log states which highlight mode actually rendered), then a live screenshot on a +multi-wall-segment room (HHS or Duplex) showing ONE clean purple volume, no visible seams, where the +old code would have shown the fragmented yellow-silhouette look from the reported screenshot. + +## DONE WHEN +A room tap shows the single translucent purple box/wireframe by default (no real-element seam +artifacts), verified live on a building where the old path previously showed fragmented real +geometry, § log confirms which highlight mode fired. diff --git a/prompts/TRILOGY_STALE_CODE_AUDIT.md b/prompts/TRILOGY_STALE_CODE_AUDIT.md new file mode 100644 index 000000000..46c554b3b --- /dev/null +++ b/prompts/TRILOGY_STALE_CODE_AUDIT.md @@ -0,0 +1,397 @@ + +# TRILOGY STALE-CODE AUDIT — mark dead/superseded code across Modeller+Viewer+ERP (2026-07-12, Fable one-shot) + +``` +# ⚠ DO NOT REMOVE +SCOPE: bim-ootb `modeller/`, `viewer/`, `erp/` (the "trilogy" — 277 top-level files, ~177K lines, +confirmed this session) — top-level files only (not node_modules/tests/build). Generalizes +`viewer/2d.html`'s pilot case (below) into a full trilogy sweep. User's own framing (2026-07-12): +"make the prompt more general... should it be done by Fable one shot, to review the whole codebase +in use by trilogy only, and mark which are not or stale for removal?" Answer worked out below — +**one-shot for DISCOVERY/MARKING only, NOT for deletion.** Read this whole file before running +anything. PUSH PAUSE LIFTED for this repo — commit locally, push the REPORT when done (docs-only, +no PR ceremony needed for a marked-report commit). Actual code removal is explicitly OUT of this +task's scope (see "Why discovery and deletion are split," below) — do not delete files in this pass +even if a finding looks obvious. +``` + +## Why discovery and deletion are split (read before objecting to the scope) +This project's own standing discipline, used consistently all session (DiscWalk branch closeout, +FIND_PANEL_PLANT_ROOM_GATE_FIX, WALKER_FIXTURE_RENDER investigation): **investigation tasks report, +they don't act** — a separate pass (often a different session, always independently verified) does +the actual change, once a human/Manager has reviewed the findings. At trilogy scale (277 files), a +single one-shot pass that BOTH discovers AND deletes has no review checkpoint before something +real is destroyed — that's the wrong risk shape for this size of change, however good the evidence +looks in the moment. One-shot IS appropriate for the discovery/marking half (see feasibility below); +it is not appropriate for bundling deletion into the same unreviewed pass. + +## Feasibility check (done before writing this, not assumed) +- 54 Playwright spec files exist (`tests/specs/*.spec.js`) — a real, substantial exercise of the live + apps, confirmed this session. Running the full suite with coverage collection once is mechanically + tractable in a single session (each spec is `@fast`-tagged or a normal integration test, not a + multi-hour job). +- Three real entry points anchor the "trilogy": `modeller/modeller.html`, `viewer/viewer.html`, + `erp/idempiere.html` (there are also `index.html`/`index2.html`/`gallery.html`/`LargeCity.html` at + the repo root — landing/launcher pages that route INTO the trilogy; treat these as additional + entry points too, not part of the trilogy being audited). + +## Method — same higher-leverage approach as the 2d.html pilot, applied trilogy-wide +**Two complementary passes, run both — they catch different things:** + +1. **Static reachability (highest-confidence signal, do this FIRST — it's cheap and decisive).** + From the entry points above, trace every `