From b8ea644cb4145d7c949ce8efffcd2f63df54b80e Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 14:45:54 +0800 Subject: [PATCH 001/244] =?UTF-8?q?docs:=20correct=20guide-screenshot=20ve?= =?UTF-8?q?rdict=20=E2=80=94=20reattempt=20succeeded?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RESUME_DISC_WALKER_ENVELOPE_BOUND.md | 86 ++++++++++++------- 1 file changed, 57 insertions(+), 29 deletions(-) diff --git a/prompts/Modeller/DISC_Walker/RESUME_DISC_WALKER_ENVELOPE_BOUND.md b/prompts/Modeller/DISC_Walker/RESUME_DISC_WALKER_ENVELOPE_BOUND.md index 2b3fa15e1..a3ea49c00 100644 --- a/prompts/Modeller/DISC_Walker/RESUME_DISC_WALKER_ENVELOPE_BOUND.md +++ b/prompts/Modeller/DISC_Walker/RESUME_DISC_WALKER_ENVELOPE_BOUND.md @@ -1,38 +1,66 @@ # RESUME — disc_walker: area-scaled n_measured + envelope-bound placement (RouteWalker alignment) -# ▶▶▶▶▶▶▶ ENTRY POINT (2026-07-10 EVENING, LATEST — supersedes every block below, including the §LIVEWIRE -# one right under this). Full context in ONE read, no need to walk the rest of this 1900+ line file first. +# ▶▶▶▶▶▶▶▶ ENTRY POINT (2026-07-10 NIGHT, LATEST — supersedes every block below). Full context in ONE read. +# THE GOAL, stated plainly by the user: get the Modeller actually working — not "verify branches" as an end +# in itself. See bim-compiler memory `project_modeller_vision_lock.md` for the product NORTH STAR this serves. # -# **State: 6 branches built, independently re-verified (not trusted on report), and PUSHED. ZERO merged to -# `origin/main` in either repo.** That's the actual next gate, not a code gap: -# - bim-ootb `fable/modeller-lod400-livewire` @ `670bf0f` + bim-compiler `fable/meshdb-livewire` @ `d75d76e09` -# — schedule-walk DEFAULT-FLIP + LOD400 device meshes. Took 3 verification rounds to actually reproduce -# from a clean checkout — rounds 1-2's green claims leaned on an uncommitted scratch-worktree repair. -# - bim-ootb `fix/grid-tilt-guard` @ `c485560` — PR #720/#721's rotation guard extended to X/Y tilt AND the -# Modeller's real second plan axis (`'y'`, not `'z'` — Z-up vs the guards' original Y-up assumption). -# - bim-ootb `fix/dw-rot-units` @ `2a02de8` — legacy disc-walk commit path wrote yaw in radians into a -# degrees field (confirmed live-visible, 88° error on a real SampleHouse fixture, before fixing). -# - bim-ootb `fable/dwprobe-dedup` @ `52fea0e` — `__dwPixelProbe` was defined twice in `modeller.html` -# (silently shadowed), + a witness crash-on-uncaught-rejection fix, + a NODE_PATH env dependency removed. -# - bim-ootb `fix/terminal-oracle-source` @ `c1b4f9e` — 2 witnesses fetched the wrong file for their real-MEP -# oracle (`Terminal_ARC.db`, 0 MEP rows since the deliberate embed-8 ARC-only strip) instead of the file -# their OWN header comments already named (`Terminal_meta.db`, untouched, real data) — stale path, not a -# data gap. 5/3→7/1, 9/1→10/10. +# **MERGED TO MAIN TODAY (the room-mode core is now live, not just verified-on-a-branch):** +# - bim-ootb `main` @ `29519e4` ← `fable/modeller-lod400-livewire` (schedule-walk DEFAULT-FLIP + LOD400 meshes) +# - bim-compiler `master` @ `06fa5613e` ← `fable/meshdb-livewire` (same lane's engine/rules side) +# Both independently re-verified 3 rounds deep before merge (rounds 1-2's green claims leaned on an +# uncommitted scratch-worktree repair — see the marathon memory for the full "verify from a genuinely fresh +# worktree" lesson). Merge itself was clean, no conflicts, confirmed cheap before landing. # -# **SampleCastle rooms: CLOSED, not a gap** — see `SAMPLECASTLE_REAL_ROOMS_RECONCILE.md`'s own closure note. -# disc_walker needs zero room/`IfcSpace` data on SC; runs clean via `duplex_rules.db` + `substrate()`'s -# direct storey derivation from `elements_meta`. The whole prior investigation was the wrong branch of the -# problem. If the live app fails on SC, suspect rules-file wiring (dwInit defaulting to `terminal_rules.db`), -# not missing data. +# **STILL UNMERGED, verified+pushed, no known blocker other than nobody's opened the PR yet:** +# `fix/grid-tilt-guard` @ `c485560` (rotation guard now covers X/Y tilt + the Modeller's real y-axis) · +# `fix/dw-rot-units` @ `2a02de8` (radians-in-degrees fixture-rotation bug, confirmed live-visible before fix) · +# `fable/dwprobe-dedup` @ `52fea0e` (3 commits: probe dedup, crash-on-uncaught-rejection fix, NODE_PATH removal) · +# `fix/terminal-oracle-source` @ `c1b4f9e` (2 witnesses were reading the wrong oracle file, not a data gap). # -# **Genuinely still open:** Terminal PLB disc-walk untested (ELEC/FP/ACMV/STR all graded, PLB wasn't — in -# progress); old signed op-log rows still carry pre-fix wrong-radians values (unsafe to blind-migrate, needs -# per-row re-derivation from source data); `cat[0]` legacy-commit fallback for hash-less placements; W5 -# ratchet toward RSS-exact; pillar 4's space-scoped UI trigger (piece 3, never bounded, needs a real Sonnet -# planning pass before anyone picks it up). +# **NEW, SIGNIFICANT, NOT YET FIXED — §TE-ARC-DATUM:** every disc-walk number quoted all session for +# Terminal (ELEC/FP/ACMV/PLB/STR) was graded against the WRONG substrate — an in-memory copy of +# `deploy/buildings/Terminal_extracted.db`, not the actual shipped `~/bim-ootb/modeller/Terminal_ARC.db`. +# Walking the REAL shipped file collapses every discipline 2-20× (e.g. ELEC 744→390, PLB 888→252) because +# the two files sit ~15m apart on the z-axis (`Terminal_ARC.db` spans −15.66…27.09, the extraction spans +# −1.01…42.10) — rule z-bands were mined in the extraction's frame and don't line up with the shipped file's +# cells. Independently reproduced exactly, including the collapse numbers from a from-scratch probe script. +# **This bug is ALREADY LIVE on `origin/main` today** (embed-8 shipped the shifted `Terminal_ARC.db` earlier) +# — merging the room-mode branches above did NOT introduce or worsen it. Full diagnosis + 3-option fix +# proposal in this file's own `§TE-ARC-DATUM` section (added by the same investigation) — not started. # -# Full detail + the "verify from a genuinely fresh worktree" lesson (learned 3 times this session): bim-compiler -# memory `project_disc_walker_grid_guard_marathon_2026-07-10.md`. +# **Screenshot/visual-correctness check: RE-ATTEMPTED, mostly SUCCEEDED.** Local-only branch +# `fable/combined-guide-shots` (worktree `/tmp/wt-combined-guide`, still not pushed) went through 2 rounds — +# the first (found by proactively checking git activity, never explicitly reported) produced broken shots +# (washed-out/zoomed wrong, camera-inside-a-dome); a SECOND, later pass (properly reported, capture script +# committed as `modeller/tests/guide_shots_combined.js`) fixed this. Both final PNGs independently reviewed +# (real pixel bytes, not the caption): `duplex_elec_lod400_walk.png` shows a real, legible LOD400 ceiling-fan +# mesh (visible blades/canopy/rod, not a box) — one caveat, only ONE fixture is clearly visible, not the +# "two distinct fixtures" claimed, worth a note not a blocker. `samplehouse_elec_rotation_fix.png` (plan +# view) shows fixtures elongated flush along the wall's diagonal run, consistent with the rot-units fix +# (pre-fix they'd sit crossways) — credible though not pixel-measured. **Net: visual correctness is real and +# mostly confirmed for these 2 cases, not the earlier "confirmed FAILED" verdict** — that verdict was based +# on the first round's now-superseded broken attempt at the same file path; don't cite it as still current. +# Regression across all 4 merged branches in this worktree: clean (12/12, 6/6, 29/29, 6/6, 34/34, 21/21, 8/8). +# New, separate, named-not-fixed finding from this pass: `xrayReveal` in `modeller.html` only glasses a +# group's DIRECT mesh children, so ARC-fetch residents report `glass=0` — fixtures glow but structure never +# actually goes glass; the capture script worked around it with a full-traverse pass, the app itself has the gap. +# +# **SampleCastle rooms: CLOSED, not a gap.** disc_walker needs zero room/`IfcSpace` data on SC — runs clean +# via `duplex_rules.db` + `substrate()`'s direct storey derivation from `elements_meta`. See +# `SAMPLECASTLE_REAL_ROOMS_RECONCILE.md`'s own closure note. **BUT: a separate, NEW "room injection feature" +# is being spec'd right now** (a different Sonnet session, direct with the user, spec not yet delivered as of +# this writing) — do not assume this is the same closed thread; read whatever spec doc that session produces +# before touching anything room-related. +# +# **Genuinely still open, smaller:** Terminal PLB disc-walk now graded (`witness_terminal_nospaces.js` T6/T7, +# 7/7 — but see §TE-ARC-DATUM above, it's graded on the wrong substrate too); old signed op-log rows still +# carry pre-fix wrong-radians values (unsafe to blind-migrate); `cat[0]` legacy-commit fallback for hash-less +# placements; W5 ratchet toward RSS-exact; pillar 4's space-scoped UI trigger (piece 3, never bounded). +# +# Full detail, exact commit chain, and the "verify from a genuinely fresh worktree" lesson (learned 3 times +# this session): bim-compiler memory `project_disc_walker_grid_guard_marathon_2026-07-10.md`. Management/ +# collaboration style for whoever picks this up: bim-compiler memory `feedback_act_autonomously_dont_ask.md` +# (consolidated 2026-07-10 — read it once, it's the definitive version). # ▶▶▶▶▶▶ ENTRY POINT (2026-07-10 PM, LATEST — Fable5 worker closeout; read this FIRST, supersedes every # block below as the starting point). GEOMETRY-HELL LANE: DELIVERED ON BRANCH, AWAITING WATCHDOG REVIEW + From 94cb321ec665c74bab59d6dd8467f2d551202d12 Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 14:54:23 +0800 Subject: [PATCH 002/244] =?UTF-8?q?docs:=20add=20prompts/MANAGER.md=20?= =?UTF-8?q?=E2=80=94=20cross-session=20review/admin=20role?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prompts/MANAGER.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 prompts/MANAGER.md diff --git a/prompts/MANAGER.md b/prompts/MANAGER.md new file mode 100644 index 000000000..514f86c35 --- /dev/null +++ b/prompts/MANAGER.md @@ -0,0 +1,31 @@ +# ⚠ DO NOT REMOVE — Scope guard: MANAGER (cross-session review/admin role) +# Scope: the user runs multiple sessions (Fable5 workers, Sonnet sessions) in parallel and relays their +# reports here. This session's job is MANAGER — review what's put in front of it, verify before +# trusting, manage the branches/merges, and housekeep (PROGRESS.md, memory, lane files). The user is +# the visionary/architect: they set direction and decide what's built. Do not re-derive that role or +# restate it back to them — assume it and act. +# Read first: CLAUDE.md + feedback_act_autonomously_dont_ask.md (bim-compiler memory, consolidated +# 2026-07-10 — the definitive management-style reference, don't ask the user to re-explain 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. +- **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). Merge/push + verified work without asking; hold merge-to-main for an explicit go-ahead unless the user says + something equivalent to "if cheap, just do it," which is a real go-ahead, not a hedge to double-check. +- **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. + +## ▶ 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. + +## ▶ DELIVERABLE +Verified verdicts, merged/pushed work, current housekeeping — reported plainly, no process narration. From 08d5462098145447beec7b092ee486bcd72f6b95 Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 15:01:55 +0800 Subject: [PATCH 003/244] =?UTF-8?q?docs(disc=5Fwalker):=20ROOM=5FINJECTION?= =?UTF-8?q?=5FHYBRID=20hardened=20for=20Fable=20handoff=20=E2=80=94=20Mode?= =?UTF-8?q?ller-only=20scope?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renumber WalkerDoctrine.md's duplicate §13 to §14 (rooms rule) and fix the citation. Drop the dead SAMPLECASTLE_REAL_ROOMS_RECONCILE.md anchor (lives only on an unmerged branch, not this one). Reframe the Viewer Find Panel Room Lens reference as a one-way, read-only borrow-from source for the Modeller's ARC-import step, name its unfiltered query as a live false positive, and explicitly scope Task 5 to Modeller-only — no Viewer files touched. Co-Authored-By: Claude Sonnet 5 --- docs/internal/WalkerDoctrine.md | 15 ++ .../DISC_Walker/ROOM_INJECTION_HYBRID.md | 216 ++++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md diff --git a/docs/internal/WalkerDoctrine.md b/docs/internal/WalkerDoctrine.md index b3a41be7b..8808bcb4c 100644 --- a/docs/internal/WalkerDoctrine.md +++ b/docs/internal/WalkerDoctrine.md @@ -432,3 +432,18 @@ real 23 columns/174 beams/9 members, Duplex's real MEP network via the already-p geometric-touch oracle), build the mesh/geometry-level comparison there FIRST — that is where "true RSS" (mesh-to-mesh, not just count/position) is actually achievable, and where it should be proven before ever being assumed to generalize to a building (like Terminal) that has no such reference at all. + +## §14 — Rooms are auto-infused into every ARC, always (2026-07-10) + +**Rule:** every building we extract (embedded/"ready-made") or a user imports must get real room data +(`spatial_structure`/`IfcSpace`) baked in automatically, as a normal step of extraction — not a manual +follow-up script, not optional. + +- If the source IFC has real `IfcSpace` entities: extract them for real (`extractIFC2DB.js` already reads + `IfcSpace` into `elements_meta` — the gap is downstream steps like the ARC-only strip dropping them before + shipping). Fix: carry `spatial_structure` through every strip/consolidate step, same as any other ARC table. +- If the source IFC has none: fall back to `compile_rooms.py` (wall-enclosure flood-fill, already built, + currently a manual script) and label those rooms `≈`/approximate — never presented as real, never fed to + `disc_walker.js`'s schedule-mode `spacesOf()` (which already correctly excludes `≈` rows). +- Applies to every building, no exceptions: 7 of today's 8 embedded `_ARC.db` have zero rooms (only Terminal + has real ones baked in) — this rule closes that gap going forward, for extraction and import alike. diff --git a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md new file mode 100644 index 000000000..c60d35404 --- /dev/null +++ b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md @@ -0,0 +1,216 @@ + +# ROOM INJECTION — hybrid rule (real rooms drive placement, guessed rooms are display-only) + task log + +``` +# ⚠ DO NOT REMOVE +SCOPE: Standing rule + task tracker for auto-injecting room data into every ARC (extracted or +user-imported), and for closing the "shipped `_ARC.db` has no rooms" gap. Read this before touching +disc_walker.js room handling, compile_rooms.py, or the Modeller Outliner. Read the log of whichever +task is IN PROGRESS below before concluding anything about its status — exit code is not evidence. +ANCHORS: docs/internal/WalkerDoctrine.md §14 (the rule, canonical) · +SAMPLECASTLE_REAL_ROOMS_RECONCILE.md (closed — SC needs no rooms to walk; lives only on unmerged bim-compiler +branch `docs/session-closeout-2026-07-10`, NOT on this branch — don't expect to open it here, the conclusion +is restated in full at §1 below) · +prompts/Modeller/DISC_Walker/RESUME_DISC_WALKER_ENVELOPE_BOUND.md (§LIVEWIRE, the schedule-driven walk +this rule feeds) · disc_walker.js `spacesOf()` (both repos — bim-ootb `modeller/`, bim-compiler `build/`) · +deploy/dev/navigate_find.js `_buildRoomTree()` (the Viewer Find Panel's existing "Room axis" query pattern — +BORROW-FROM source for the Modeller's ARC-import injection step, see Task 5's prior-art note — but it has +NO habitability filter yet, so borrow the pattern only once Task 5's filter is attached, not standalone). +``` + +## §1 — The decision (settled 2026-07-10, user-confirmed, do not re-litigate) + +Every ARC — extracted by us or imported by a user — gets rooms injected automatically, always, split by +source reliability, never by building-name special-case: + +- **Real `IfcSpace` in the source IFC** → extract for real, feeds `disc_walker.js`'s per-room schedule-mode + placement (`dwWalk(disc, bdb, name, {schedule:true})`). Auto, no toggle, no manual step. +- **No real `IfcSpace`** → `compile_rooms.py`'s wall-enclosure flood-fill also runs automatically, but + ONLY to populate a Modeller Outliner "Rooms" display category (visual/wow-factor). It must NEVER feed + placement — `spacesOf()` already filters these rows out by design (`guid NOT LIKE 'RM\_%'`, `name NOT + LIKE '%≈%'`) because they're ~24% recall against ground truth (Duplex: 5/21 correct) — feeding them to + placement would make results worse than the no-rooms fallback, not better. +- **DISC Walk itself never breaks either way** — verified directly this session: the legacy density walk + (storey-based, no rooms needed at all) already runs cleanly with real results on a 0-room building + (`SampleCastle_ARC.db`, ACMV 14 / ELEC 325 / PLB 101 placed, zero refusals). Rooms only improve + precision on top of an already-working baseline; they are not a prerequisite for the walk to function. + +Full reasoning trail: this doc's own earlier scoping was SampleCastle-specific +(`SAMPLECASTLE_REAL_ROOMS_RECONCILE.md`) and concluded SC needs no rooms to walk — that investigation is +CLOSED, not a gap. This file supersedes it for the general (all-buildings, all-imports) room-injection +question. + +## §2 — Current state, verified directly (sqlite3, this session, not inferred) + +| Shipped `modeller/*_ARC.db` (bim-ootb, 8 total) | rooms | +|---|---| +| Terminal | **43 real rooms already baked in** | +| Duplex, SampleCastle, SampleHouse, Clinic, Garage, HHS, Hospital | **0** — no `spatial_structure` table | + +Canonical `deploy/buildings/*_extracted.db` (bim-compiler, 32 total) — only 2 carry real, portable room +data not yet ported into their shipped `_ARC.db`: **Duplex (21 real `IfcSpace` rows)**, **HHS_Office_Federated +(14 real `IfcSpace` rows)**. Every other canonical extract (SampleCastle included) has none — SampleCastle's +own true source IFC is untraceable (see the superseded SC doc §3); the only "real rooms" ever produced +under the SampleCastle name were mislabeled Schependomlaan data (same doc §2) — not reused here, would +reintroduce a known mislabel. + +## §3 — Task log + +### Task 1 — Port Duplex real rooms into shipped `_ARC.db`, verify schedule-mode engages, strip non-habitable rows +**Status: AGENT KILLED MID-TASK by user 2026-07-10 (task was proceeding on stale premises — user's call, not +a technical failure). Needs a fresh session/agent (Fable) to pick up from the exact state below — do not +restart from scratch, do not re-verify things already confirmed.** + +**Original scope (given to the killed agent):** port `spatial_structure` from canonical +`Duplex_extracted.db`/`HHS_Office_Federated_extracted.db` into shipped `Duplex_ARC.db`/`HHS_ARC.db` +(bim-ootb, worktree-only, hook-blocked on the shared checkout), prove `dwWalk(..., {schedule:true})` +engages, push (not merge). + +**What actually happened, in order, all confirmed directly (not agent-reported, independently checked +where noted):** +1. Original brief wrongly included HHS as "14 real rooms" — corrected mid-flight (see Task 5 below): all + 14 of HHS's `spatial_structure` rows are 100% synthetic `compile_rooms.py` output (`RM_`/`≈`/`COMPILED` + tagged), not real. Sent the agent a correction dropping HHS from scope entirely, Duplex-only from that + point. +2. Duplex's real 21 rows include one non-habitable entry, `R301 | Roof | object_type='Roof' | size + 7.97×16.97×3.0m` — flagged to the agent, told to filter it (and audit for any other non-habitable + `object_type` values) before porting. +3. **Before the agent could act on either correction, it reported back a prior-state finding: + `Duplex_ARC.db`'s `spatial_structure` was ALREADY present, unfiltered (Roof row included), on bim-ootb + branch `fable/modeller-lod400-livewire` @ commit `670bf0f`** — pushed to `origin`, NOT merged to `main`. + This predates this task entirely; it's a leftover from the earlier §LIVEWIRE work + (`project_disc_walker_grid_guard_marathon_2026-07-10.md`), not something this agent or task introduced. +4. **HHS confirmed untouched/clean** — md5-identical to the `main`-checkout copy, no `spatial_structure` + table added. No bad data was written anywhere by this task. +5. Agent was killed (user command) immediately after step 3's finding, before it stripped the Roof row. + +**Exact state for the next session to resume from:** +- `main` (both repos): unaffected, nothing to undo there. +- bim-ootb branch `fable/modeller-lod400-livewire` @ `670bf0f`: has `Duplex_ARC.db` with a real but + UNFILTERED 21-row `spatial_structure` (20 genuine rooms + 1 `Roof` void that must NOT be treated as a + room). **This branch cannot merge to `main` as-is** — the Roof row needs stripping first. +- HHS: leave alone entirely, in every branch. Not part of this task. +- Not yet done, still needed: (a) strip the `Roof` row (and confirm no other non-habitable `object_type` + hides in the same 21) from `670bf0f`'s `Duplex_ARC.db`, (b) prove `dwWalk(..., {schedule:true})` engages + on the cleaned 20-room set specifically (not the tainted 21 — a witness run against unfiltered data would + prove nothing about whether the filter works), (c) push the fix as a new commit on top of `670bf0f` (or a + fresh branch off it — Fable's call), (d) this task's original guid/coordinate-overlap verification step + was never confirmed either way by the killed agent — still needs doing, don't assume it passed. +- This task and Task 5 (room recognition) are now the same piece of work in practice — the Roof-filtering + needed here IS Task 5's habitability classifier, applied to its first real case. Fable should treat them + as one assignment, not sequence them as two. + +### Task 2 — Wire `compile_rooms.py` into extraction/import as an automatic step (tagged `≈`, display-only) +**Status: NOT STARTED.** Currently a manual script (`compile_rooms.py --write`), never called from any +build/embed/import path (confirmed via grep this session — zero automatic callers). Needs wiring into +whichever step runs `extractIFC2DB.js` (both the embed/"ready-made" build step for our own residents, and +whatever live path handles a user's IFC import) so it runs automatically whenever a source IFC has no real +`IfcSpace`. + +### Task 3 — New Modeller Outliner "Rooms" category (the display/wow-factor surface) +**Status: NOT STARTED, NOT YET SPECCED.** No existing room display anywhere in the Modeller (checked — +`grep -rl room` across `modeller/*.js` only matches `disc_walker.js` itself). Pattern to follow: the +existing Disc-tab-follower shape already used for STR and walked-fixtures categories +(`bom_tree_outliner.js` / `dw_instances_outliner.js` / `str_walker_outliner.js`) — a "Rooms" category would +sit alongside those, sourced from whichever rooms (real or `≈`-tagged) exist in the open building's +`spatial_structure` table. + +### Task 4 — Carry real `IfcSpace`/`spatial_structure` through the ARC-only strip step +**Status: NOT STARTED.** The strip (`b93ca13`, "cascade-deleted to discipline='ARC' only") never explicitly +targeted rooms for removal, but the loss is a real, live regression path for any FUTURE re-strip or +re-embed — `spatial_structure` needs to be an explicit survivor of that cascade, same as any other ARC +table, or this whole rule regresses again the next time a resident gets re-baked. + +### Task 5 — Room RECOGNITION: real IfcSpace still needs a habitability filter before it's trusted +**Status: NOT STARTED. Assigned to Fable (execution lane) — see §5.** + +Found live, this session, by direct query (not theoretical): "real" `spatial_structure` data is not +automatically safe to treat as "a room." + +- **HHS_Office_Federated_extracted.db's 14 `spatial_structure` rows are NOT real** — corrected from this + doc's own earlier §2/§3, which wrongly counted them as real portable room data. Direct check: + `SELECT COUNT(*), SUM(guid LIKE 'RM\_%'), SUM(name LIKE '%≈%'), SUM(object_type='COMPILED') FROM + spatial_structure WHERE type='IfcSpace'` → **14/14/14/14** — every row is `compile_rooms.py` synthetic + output (`RM_` guid, `≈` name, `COMPILED` object_type), not extraction. Task 1 was corrected mid-flight to + drop HHS entirely — Duplex is the only building with confirmed real, portable room data right now. +- **Even Duplex's genuinely real 21 rows aren't all habitable rooms.** Found: `R301 | Roof | object_type= + 'Roof' | size 7.97×16.97×3.0m` — a roof void, correctly extracted as a real `IfcSpace`, but not a room + a DISC discipline should schedule-place fixtures into the way it would a Bathroom or Bedroom. The other + 20 rows all look legitimately habitable (Foyer/Living Room/Kitchen/Bathroom/Bedroom/Hallway/Utility/ + Stair) but this was a spot-check, not an exhaustive audit — the same non-habitable pattern (roof, shaft, + plant room, external/podium space, parapet/sill space — real `IfcSpace` entities architects sometimes + tag for non-occupiable volumes) could exist in ANY future real extraction, not just Duplex. + +**The actual problem to solve:** a reliable habitability classifier for `spatial_structure`/`elements_meta` +`IfcSpace` rows — real-vs-synthetic is already solved (`RM_`/`≈`/`COMPILED` tagging, don't touch that part). +What's missing is real-vs-non-habitable-real: telling a genuine Bathroom from a genuine Roof/Shaft/Sill +space when BOTH are honestly-extracted `IfcSpace` data with no synthetic tag to lean on. + +**Starting points for whoever (Fable) picks this up:** +- `object_type` (Duplex's case) / `LongName` (per `spacesOf()`'s own comment, the space-TYPE key) is the + first signal — but it's free text from the source IFC, not a controlled vocabulary; needs a maintained + exclude-list (Roof, Shaft, Void, Plant, External, Podium, Sill, Parapet, Balcony-if-not-enclosed, etc.) + reviewed against real data across multiple buildings, not guessed from one building's 21 rows. + Existing code already has ONE piece of this pattern: `compile_rooms.py`'s `STAIR_EXCLUDE` logic + (rejects a compiled pocket if a stair footprint covers ≥35% of its area) — same shape of problem + (structurally-enclosed-but-not-a-room), solved once for the synthetic path; the real-`IfcSpace` path has + no equivalent filter yet. +- Geometry-based sanity checks may help independent of naming — e.g. a room whose z0/z1 band sits above + the building's topmost habitable storey, or whose footprint matches the building's roof/envelope + footprint rather than an interior partition, is a strong non-room signal regardless of what it's named. +- Whatever filter is built must run BEFORE real rooms are ported into any shipped `_ARC.db` (Task 1) AND + before `disc_walker.js`'s `spacesOf()` trusts them for placement — same enforcement point, so it likely + belongs as a shared helper both call, not two independent filters that can drift apart. +- Witness-first per project standing rule: prove the filter's precision/recall on Duplex's known-21 (where + the answer — 20 real rooms + 1 Roof — is already hand-verified above) before trusting it on any other + building's real `IfcSpace` data. + +**⚠ SCOPE: this task is MODELLER-ONLY. The Viewer is cited below strictly as a READ-ONLY reference/evidence +source — its own query pattern and its own bug. Do NOT touch, wire into, or modify anything under +`deploy/dev/navigate_find.js` or any other Viewer file for this task. If a future shared-helper refactor +ever pulls the Viewer in, that is a separate, explicitly-scoped task, not an implicit side-effect of this +one.** + +**Borrow-from source (read-only reference), AND confirmed prior art with the SAME unfixed gap (user flagged +this correctly from the start, this session — checked, confirmed real):** the Viewer's own Find Panel Room +Lens (`deploy/dev/navigate_find.js` `_buildRoomTree()`, wired to the Find Panel's "Room axis" pill +`#find-axis-room`, witnessed by `deploy/dev/tests/specs/41-room-volume-lens.spec.js`'s §LENS_PROBE/roomVol +checks) already queries and displays rooms today — `SELECT guid, name FROM spatial_structure WHERE +type='IfcSpace' AND center_x IS NOT NULL`. **Read this query as the pattern to follow when building the +Modeller's ARC-import auto-injection step (Task 1 / WalkerDoctrine.md §14)** — copy the shape of the query +into Modeller-side code, so every embedded ARC gets rooms the same way the Viewer already surfaces them, +instead of a second, divergent query being invented from scratch. This is a one-way READ: look at the +Viewer's code to know what to build in the Modeller; nothing in the Modeller build calls back into it. + +**Borrow the pattern, not the bug: the Viewer's query has ZERO habitability filtering, and is itself a live +false positive — evidence for why the Modeller's copy must NOT be built the same naive way.** +`type='IfcSpace' AND center_x IS NOT NULL` is just as true for Duplex's `R301 | Roof` row as for a genuine +Bedroom — the Viewer's Room Lens would show, and today DOES show, "Roof" as a room. This is a FALSE POSITIVE +for "habitable room," not a syntax bug — the query correctly finds a real, non-synthetic `IfcSpace`; it's +simply not a room a DISC discipline should schedule-place fixtures into, or a user should see in a room +list. If the Modeller's ARC-import step copies this query's shape WITHOUT also building Task 5's +habitability filter first, it reproduces the identical false positive on the Modeller side. **Sequencing: +Task 5's classifier must exist and be proven on Duplex's known-21 BEFORE the query pattern is copied into +ARC-import — filter first, then build the import step, not the query alone.** + +**Fixing the Viewer's own copy of this false positive is explicitly OUT OF SCOPE for this task.** It is +named here only as corroborating evidence that the false-positive risk is real and already observable, not +as a second deliverable. Once the Modeller-side classifier exists and is proven, porting it into the Viewer +is a distinct, future, separately-scoped task — do not fold it into Task 5's Modeller work. + +## §5 — Assignment + +Task 5 (room recognition/habitability classification) is assigned to **Fable** (execution lane — per this +project's model-allocation convention, Fable executes, Sonnet keeps the mastermind/memory-writing role). +Tasks 1-4 stay as logged above; Task 1 is currently being corrected mid-flight (see this doc's own Task 5 +findings — HHS dropped, Duplex needs the Roof-type row filtered before porting). + +## §4 — Guardrails + +- Never re-adopt the stale bim-ootb branch `origin/feat/samplecastle-real-rooms` or its DB payload — it's + Schependomlaan mislabeled as SampleCastle (see the superseded SC doc §2/§5). Not relevant to Tasks 1-4 + above (Duplex/HHS have real, unambiguous canonical data of their own) but still a live landmine if anyone + goes hunting for "existing real-room work" and finds that branch. +- `compile_rooms.py` output must never lose its `≈`/`RM_` tagging on the way into any shipped DB — that + tag is the only thing keeping `spacesOf()`'s exclusion correct. Any wiring work (Task 2) must preserve it. +- Related memory: `project_room_injection_split_decision.md`, `project_disc_walker_grid_guard_marathon_2026-07-10.md`. From cc2841b1f5c1d489c069c45ec4cfe50ae86ad364 Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 15:04:27 +0800 Subject: [PATCH 004/244] =?UTF-8?q?docs(manager):=20anchor=20THE=20GOAL=20?= =?UTF-8?q?to=20VISION-LOCK=20=E2=80=94=20"working"=20was=20undefined?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MANAGER.md said "get the actual thing working" without citing what working means, letting review verdicts drift toward generic pass/fail instead of the actual product bar. Point at RESUME_GRAPH_MODELLER_INTEGRATION.md §VISION-LOCK's five sentences. Co-Authored-By: Claude Sonnet 5 --- prompts/MANAGER.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/prompts/MANAGER.md b/prompts/MANAGER.md index 514f86c35..858331c58 100644 --- a/prompts/MANAGER.md +++ b/prompts/MANAGER.md @@ -27,5 +27,10 @@ U ARE TO REVIEW OTHER SESSIONS PUT BEFORE U BY THE USER. MANAGE AND HOUSEKEEP. 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." + ## ▶ DELIVERABLE Verified verdicts, merged/pushed work, current housekeeping — reported plainly, no process narration. From 3a8dcdea6d426d699304fbee3545a6e723bd06a6 Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 15:36:51 +0800 Subject: [PATCH 005/244] =?UTF-8?q?docs(disc=5Fwalker):=20correct=20=C2=A7?= =?UTF-8?q?2=20=E2=80=94=20Terminal's=2043=20rooms=20are=20synthetic,=20no?= =?UTF-8?q?t=20real?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Direct query (object_type=COMPILED, ≈-named) proves Terminal has zero real IfcSpace rooms — the doc previously miscounted them as "real rooms already baked in." Same correction applied to HHS's 14 rows (already caught by Task 5 but §2's table hadn't been updated to match). Adds the full 8-building room-count table: only Duplex has real, habitability-filtered data, and it isn't merged to main yet. Co-Authored-By: Claude Sonnet 5 --- .../DISC_Walker/ROOM_INJECTION_HYBRID.md | 113 ++++++++++++++++-- 1 file changed, 100 insertions(+), 13 deletions(-) diff --git a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md index c60d35404..fa1bfb90b 100644 --- a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md +++ b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md @@ -42,24 +42,38 @@ question. ## §2 — Current state, verified directly (sqlite3, this session, not inferred) -| Shipped `modeller/*_ARC.db` (bim-ootb, 8 total) | rooms | -|---|---| -| Terminal | **43 real rooms already baked in** | -| Duplex, SampleCastle, SampleHouse, Clinic, Garage, HHS, Hospital | **0** — no `spatial_structure` table | +**⚠ CORRECTION (2026-07-10, later same day, MANAGER review):** Terminal's 43 rows were originally recorded +below as "real rooms already baked in" — that was WRONG, caught by direct query. `SELECT object_type, +COUNT(*) FROM spatial_structure WHERE type='IfcSpace' GROUP BY object_type` on the shipped `Terminal_ARC.db` +returns `COMPILED|43` — every row is `compile_rooms.py` synthetic flood-fill output (name pattern `≈ Aras +01 R1` etc.), not a real `IfcSpace`. Harmless in practice (`spacesOf()`'s `≈`/synthetic exclusion already +keeps them out of placement, same as HHS's 14), but the record was factually wrong and is fixed here. + +| Shipped `modeller/*_ARC.db` (bim-ootb, 8 total) | rooms | real or synthetic | +|---|---|---| +| Terminal | 43 | **ALL synthetic** (`object_type=COMPILED`, `≈`-named) — never real, corrected above | +| Duplex | 0 on `main`; **20** on unmerged branch `fable/modeller-lod400-livewire` @ `2821b8e` | real, habitability-filtered (Task 5 done — R301 Roof stripped) | +| SampleCastle, SampleHouse, Clinic, Garage, HHS, Hospital | 0 | no `spatial_structure` table | Canonical `deploy/buildings/*_extracted.db` (bim-compiler, 32 total) — only 2 carry real, portable room -data not yet ported into their shipped `_ARC.db`: **Duplex (21 real `IfcSpace` rows)**, **HHS_Office_Federated -(14 real `IfcSpace` rows)**. Every other canonical extract (SampleCastle included) has none — SampleCastle's +data: **Duplex (21 real `IfcSpace` rows, now 20 post-filter on the branch above)**, **HHS_Office_Federated +(14 rows — ALSO corrected here: these are 100% synthetic `compile_rooms.py` output, not real; see Task 5's +own finding in §3 below, which superseded this section's original wrong count but this table hadn't been +fixed to match until now)**. Every other canonical extract (SampleCastle included) has none — SampleCastle's own true source IFC is untraceable (see the superseded SC doc §3); the only "real rooms" ever produced under the SampleCastle name were mislabeled Schependomlaan data (same doc §2) — not reused here, would -reintroduce a known mislabel. +reintroduce a known mislabel. **Bottom line: of 8 shipped buildings, only Duplex has real, filtered room +data, and it isn't merged to `main` yet — the other 6 (excluding Terminal/HHS's synthetic-only sets) have +zero room data at the SOURCE, which is a data-availability gap, not something the habitability classifier +can fix.** ## §3 — Task log ### Task 1 — Port Duplex real rooms into shipped `_ARC.db`, verify schedule-mode engages, strip non-habitable rows -**Status: AGENT KILLED MID-TASK by user 2026-07-10 (task was proceeding on stale premises — user's call, not -a technical failure). Needs a fresh session/agent (Fable) to pick up from the exact state below — do not -restart from scratch, do not re-verify things already confirmed.** +**Status: ✅ DONE 2026-07-10 (Fable, combined with Task 5 — see §6 results). Witness: W-ROOM-HAB 5/5 + +W-DW-LIVEWIRE 12/12, bim-ootb `fable/modeller-lod400-livewire` @ `2821b8e` (pushed, not merged; the Roof +row is stripped so the branch's earlier merge-blocker is cleared). Historical resume-state below kept for +the record.** **Original scope (given to the killed agent):** port `spatial_structure` from canonical `Duplex_extracted.db`/`HHS_Office_Federated_extracted.db` into shipped `Duplex_ARC.db`/`HHS_ARC.db` @@ -122,7 +136,12 @@ re-embed — `spatial_structure` needs to be an explicit survivor of that cascad table, or this whole rule regresses again the next time a resident gets re-baked. ### Task 5 — Room RECOGNITION: real IfcSpace still needs a habitability filter before it's trusted -**Status: NOT STARTED. Assigned to Fable (execution lane) — see §5.** +**Status: ✅ DONE 2026-07-10 (Fable) — `spaceHabitable()` shared classifier in bim-ootb +`modeller/disc_walker.js`, wired into `spacesOf()` (both real-source paths) and exported for port/injection +steps. Proven on Duplex's hand-verified known-21: 20/20 habitable kept, exactly R301 Roof excluded +(`label:ROOF`; independent `zband:8.91>6.67` geometry signal verified by falsifier). W-ROOM-HAB 5/5 — +see §6 results. Follow-up (named, not done): port to the diverged source copy bim-compiler +`build/disc_walker.js`, and into the Viewer (separately-scoped per this doc).** Found live, this session, by direct query (not theoretical): "real" `spatial_structure` data is not automatically safe to treat as "a room." @@ -202,8 +221,76 @@ is a distinct, future, separately-scoped task — do not fold it into Task 5's M Task 5 (room recognition/habitability classification) is assigned to **Fable** (execution lane — per this project's model-allocation convention, Fable executes, Sonnet keeps the mastermind/memory-writing role). -Tasks 1-4 stay as logged above; Task 1 is currently being corrected mid-flight (see this doc's own Task 5 -findings — HHS dropped, Duplex needs the Roof-type row filtered before porting). +Tasks 1+5 were executed as one assignment and are ✅ DONE 2026-07-10 (see §6 RESULTS); Tasks 2–4 remain +NOT STARTED as logged above. + +## §6 — Execution spec: Task 1 + Task 5 as one assignment (2026-07-10, Fable session — spec-first) + +**Deliverable:** new commit(s) on top of bim-ootb `fable/modeller-lod400-livewire` @ `670bf0f` +(existing worktree `/tmp/wt-fable-livewire`, verified clean this session), pushed NOT merged. +Ground truth re-verified directly before speccing: `Duplex_ARC.db` `spatial_structure` = 26 rows +(1 IfcBuilding + 4 IfcBuildingStorey + 21 IfcSpace); among the 21, exactly one non-habitable: +`0pNy6pOyf7JPmXRLgxs3sW | R301 | object_type='Roof'`. Substrate envelope (element_transforms): +x −2.42..11.22, y −22.31..4.51, z −1.55..6.67. + +**S1 — Shared classifier `spaceHabitable(space, env)`** in worktree `modeller/disc_walker.js` +(exported on the API so port/injection scripts and `spacesOf()` call the SAME function — the single +enforcement point Task 5 requires): +- Input: `{label, x0..z1}` (the `spacesOf()` shape); `env` = substrate bbox from `element_transforms`. +- **Signal A (label, primary):** normalize label same as `_spaceTypeFor` (UPPER, spaces→`_`, strip + trailing numbering), word-match against the maintained exclude-list taken verbatim from Task 5's + starting points: ROOF, SHAFT, VOID, PLANT/PLANT_ROOM, EXTERNAL, PODIUM, SILL, PARAPET, BALCONY. + List grows only by review against real extractions — never guessed further. +- **Signal B (geometry, secondary):** space `z1 > env.z1 + 0.25` → non-habitable. Verified against + the known-21 BEFORE coding: R301 z1=8.91 vs envelope 6.67 (fires, +2.24 m); all 20 genuine rooms + z1 ≤ 5.61 (never fires). The spec's suggested footprint-vs-envelope signal was measured and + REJECTED for now: R301's footprint is only 0.37 of the envelope — no threshold provable on real + data; not shipped (non-invent). +- Returns `{ok:true}` or `{ok:false, why:'label:ROOF' | 'zband:8.91>6.67'}`. Real-vs-synthetic + (`RM_`/`≈`) handling untouched. +- `spacesOf()` applies it to BOTH real sources (elements_meta path and spatial_structure path) and + §-logs every exclusion (`§SPACE-NONHAB`) — defense stays live even if a tainted DB ever ships. + +**S2 — Data strip (Task 1a):** delete non-habitable IfcSpace rows from worktree +`modeller/Duplex_ARC.db`, selected BY the classifier verdicts (not a hand-coded guid), expected = +exactly R301. The IfcBuildingStorey row also named "Roof" stays (it's a storey, not a space). HHS +untouched everywhere, per Task 1. + +**S3 — Witness `modeller/tests/witness_room_hab.js` (W-ROOM-HAB, node-side, §-log-first).** Issues +each check exposes: **H1 PRECISION/RECALL** — classifier on the pre-strip known-21 → exactly 20 ok + +R301 excluded, which signal fired logged (proves Task 5 on the hand-verified answer). **H2 FALSIFIER** +— rerun with the exclude-list emptied → R301 must classify habitable via label (only zband may still +catch it), proving H1's pass depends on the real list, not harness bias. **H3 GUID-OVERLAP (Task 1d)** +— no duplicate guids inside spatial_structure; no collision with elements_meta guids. **H4 +COORD-OVERLAP (Task 1d)** — every habitable room bbox intersects the substrate bbox in x, y AND z. +**H5 STRIP** — after `--strip`, DB holds exactly 20 IfcSpace rows, no Roof space, storey rows intact. + +**S4 — Live proof (Task 1b):** update `witness_dw_livewire.js` L0's expected space count 21→20 +(cites this §), rerun the FULL W-DW-LIVEWIRE suite — L1 SCHED-LIVE must engage `placeSchedule` on +the cleaned 20-room set with placements > 0, all checks green, zero pageerror; log saved and read. + +**S5 — Push** the commit(s) and verify `git rev-list --count origin/fable/modeller-lod400-livewire..HEAD` = 0. + +**Out of scope (explicit):** all Viewer files (`deploy/dev/**`); HHS in every branch; Tasks 2–4; +the diverged bim-compiler `build/disc_walker.js` copy (1731 vs 2188 lines — porting `spaceHabitable` +there is a named follow-up, not part of this branch's commit). + +**RESULTS (2026-07-10, same session, logs read not inferred — `/tmp/wt-fable-livewire/logs/ +w_room_hab_proof.log`, `w_room_hab_strip.log`, `w_dw_livewire_rerun.log`):** +- **W-ROOM-HAB 5/5** — H1: known-21 → 20 ok + exactly R301 excluded (`label:ROOF`). H2 falsifier: + exclude-list emptied → label signal vanishes, independent `zband:8.91>6.67` still fires; both signals + disarmed → R301 classifies ok (H1's pass is the list's, not the harness's). H3: 0 duplicate guids, + 0 elements_meta collisions. H4: 20/20 habitable bboxes ∩ substrate bbox in x,y,z. H5 (post `--strip`, + delete driven by classifier verdicts): 20 IfcSpace / 0 Roof spaces / 4 storeys + 1 building intact + (the IfcBuildingStorey named "Roof" survived, as specced). +- **W-DW-LIVEWIRE 12/12** on the cleaned DB — L0 20/20; L1 SCHED-LIVE Duplex ELEC `placeSchedule` + engaged: placed=102, spaces=19/20, skippedSpaces=0, lod400Refused=2 (EMERGENCY_LIGHT — honest + LOD400-LAW refusals, pre-existing); L6 PLB placed=18 (6/20 spaces, avoid live); L2/L5/L7 and + Terminal (L3 placed=390) / SampleCastle (L4 fallback placed=325) all green, zero pageerror. +- **Shipped:** bim-ootb `fable/modeller-lod400-livewire` @ `2821b8e` (parent `670bf0f`), pushed, + `rev-list origin..HEAD` = 0. Files: `modeller/disc_walker.js` (+`spaceHabitable`/`_substrateEnv`, + `spacesOf()` filter + `§SPACE-NONHAB` log, API export), `modeller/Duplex_ARC.db` (R301 stripped), + `modeller/tests/witness_room_hab.js` (new), `modeller/tests/witness_dw_livewire.js` (L0 21→20). ## §4 — Guardrails From e8153c0a13d4df8d969ada07ba74dba9d05987c4 Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 15:45:05 +0800 Subject: [PATCH 006/244] =?UTF-8?q?docs(disc=5Fwalker):=20=C2=A72=20?= =?UTF-8?q?=E2=80=94=20source-IFC=20check=20finds=20SampleHouse=20extracti?= =?UTF-8?q?on=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Direct grep of internal/sources/Ifc4_SampleHouse.ifc finds 4 real IFCSPACE entities (Living room, Bedroom, Entrance hall, Roof) that never made it into the shipped SampleHouse_ARC.db (0 rooms) — an extraction gap, not a source-data gap as previously assumed. Clinic/Garage/Hospital/HHS have no source IFC in this checkout to check either way, so their "0 rooms" state is unverified, not confirmed-no-data. Co-Authored-By: Claude Sonnet 5 --- .../Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md index fa1bfb90b..d493d3500 100644 --- a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md +++ b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md @@ -55,6 +55,18 @@ keeps them out of placement, same as HHS's 14), but the record was factually wro | Duplex | 0 on `main`; **20** on unmerged branch `fable/modeller-lod400-livewire` @ `2821b8e` | real, habitability-filtered (Task 5 done — R301 Roof stripped) | | SampleCastle, SampleHouse, Clinic, Garage, HHS, Hospital | 0 | no `spatial_structure` table | +**Source IFCs on disk (`internal/sources/`) — note: the ARC is a DERIVED artifact of IFC metadata, so +knowing the path alone fixes nothing; it only matters if it turns up an EXTRACTION bug (source has real +rooms, ARC doesn't) vs a genuine SOURCE gap (no rooms exist to extract). Checked directly, this session:** +- `Ifc2x3_Duplex_Architecture.ifc` — 21 `IFCSPACE` entities (matches the 21 already ported/filtered). +- `Ifc4_SampleHouse.ifc` — **4 real `IFCSPACE` entities** (Living room, Bedroom, Entrance hall, Roof) — + but shipped `SampleHouse_ARC.db` has 0. **This is an EXTRACTION bug, not a data gap** — the source has + real rooms and they were dropped somewhere in the pipeline. Small enough (4 rooms, 1 of them a Roof the + habitability filter would exclude anyway → 3 usable) to be a cheap next win once someone re-runs + extraction on this source with `spatial_structure` capture verified. +- Clinic, Garage, Hospital, HHS — **no source IFC present in this checkout's `internal/sources/` at all** — + can't be checked either way locally; the "0 rooms" state for these is unverified-source, not confirmed-no-data. + Canonical `deploy/buildings/*_extracted.db` (bim-compiler, 32 total) — only 2 carry real, portable room data: **Duplex (21 real `IfcSpace` rows, now 20 post-filter on the branch above)**, **HHS_Office_Federated (14 rows — ALSO corrected here: these are 100% synthetic `compile_rooms.py` output, not real; see Task 5's From d66723912d6c520275012b089c0f0bf699e2d6c3 Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 15:46:51 +0800 Subject: [PATCH 007/244] docs(disc_walker): add Task 6 (SampleHouse extraction bug), reassign forward work to Sonnet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 6: 3 usable real rooms exist in SampleHouse's source IFC but never reached the shipped ARC — an extraction bug, not a data gap. Per user directive, all further work on this doc goes to Sonnet, not Fable, from here on; Tasks 1+5's Fable history is left as an accurate record, not rewritten. Co-Authored-By: Claude Sonnet 5 --- .../DISC_Walker/ROOM_INJECTION_HYBRID.md | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md index d493d3500..10fa50ca2 100644 --- a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md +++ b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md @@ -231,10 +231,22 @@ is a distinct, future, separately-scoped task — do not fold it into Task 5's M ## §5 — Assignment -Task 5 (room recognition/habitability classification) is assigned to **Fable** (execution lane — per this -project's model-allocation convention, Fable executes, Sonnet keeps the mastermind/memory-writing role). -Tasks 1+5 were executed as one assignment and are ✅ DONE 2026-07-10 (see §6 RESULTS); Tasks 2–4 remain -NOT STARTED as logged above. +Task 5 (room recognition/habitability classification) WAS assigned to Fable (execution lane) and Tasks 1+5 +were executed as one assignment, ✅ DONE 2026-07-10 (see §6 RESULTS) — that history stands as-is. + +**⚠ FROM 2026-07-10 (later same day), ALL FURTHER WORK ON THIS DOC GOES TO SONNET, NOT FABLE (user +directive — no reason logged, just apply it).** Tasks 2–4 remain NOT STARTED. New: + +### Task 6 — SampleHouse extraction bug: 3 real, usable rooms exist in source but never reached the ARC +**Status: NOT STARTED. Assigned to Sonnet.** +Found this session (§2): `internal/sources/Ifc4_SampleHouse.ifc` contains 4 real `IFCSPACE` entities +(`Living room`, `Bedroom`, `Entrance hall`, `Roof`) but shipped `SampleHouse_ARC.db` has 0 rows in +`spatial_structure` — the extraction pipeline dropped them somewhere between source and shipped ARC. This +is an EXTRACTION gap, not a source-data gap (unlike Clinic/Garage/Hospital/HHS, which have no source IFC +in this checkout to check either way). Scope: find where `extractIFC2DB.js` (or whichever step runs on +this source) fails to carry `IfcSpace` through to `spatial_structure` for this specific file, fix it, run +Task 5's `spaceHabitable()` classifier on the result (expect Roof excluded, 3 kept), re-embed. Small blast +radius — one building, 4 rows. ## §6 — Execution spec: Task 1 + Task 5 as one assignment (2026-07-10, Fable session — spec-first) From 62090bd737c9e9bc8afd63107b804708ee0a586d Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 16:28:21 +0800 Subject: [PATCH 008/244] =?UTF-8?q?feat(compile=5Frooms):=20=C2=A7DOOR-RES?= =?UTF-8?q?CUE=20=E2=80=94=20recover=20real=20small=20rooms=20(toilets/uti?= =?UTF-8?q?lity)=20below=20MIN=5FAREA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User-flagged, verified before fixing: HHS ("large U-shape office block, lots of office rooms with doors") only compiled 2 rooms; restrooms/utility rooms were being missed across every building, not just HHS. Instrumented the flood-fill to log ALL candidate pockets (not just accepted ones) — confirmed this was NOT a mis-tuned MIN_AREA=4.0 threshold hiding rooms that were actually detected: most sub-threshold pockets are <1.0 m^2 wall-joint/cavity artefacts (noise), but a real population of 1.0-4.0 m^2 pockets IS a genuine small room (restroom/closet/ riser) wrongly dropped by the same blanket cutoff. The user's hint ("rooms has doors" / "look for clues that define a toilet, utility that house MEP") pointed at the fix: MEP fixture data isn't reliable (ARC-only discipline strip already removed sanitary/plumbing elements from 4 of 5 buildings), but door adjacency is a real, non-invented structural signal already in every ARC.db — a genuine small room a person uses always has a door; a wall cavity never does. Measured before shipping: for HHS/Clinic/Hospital/ SampleCastle, pockets <1.0 m^2 are rarely door-adjacent (noise); pockets in [1.0, 4.0) m^2 are door-adjacent in the large majority (real small rooms) — the two size bands are different populations, not an arbitrary split. - storey_doors() + _door_adjacent(): per-storey real IfcDoor centres, 1.0m buffer. - flood_rooms(): a pocket in [DOOR_RESCUE_MIN_AREA=1.0, MIN_AREA=4.0) m^2 is now accepted iff door-adjacent; still-tagged ≈/RM_/COMPILED (spacesOf()'s placement-exclusion untouched) but predefined_type='INTERNAL_SMALL' (vs 'INTERNAL') for traceability of which path found it. - Re-ran --write on the 5 gap buildings: HHS 2→5, Clinic 113→190, Hospital 142→213, SampleCastle 25→47, Garage unchanged (0 door-adjacent small pockets found). - W-ROOM-INJECT-ALL8 rerun 32/32 on bim-ootb worktree: placement-eligibility still 0 for every synthetic-only building — door-rescued rooms are exactly as display-only as any other compiled room, never reach schedule placement. Doc: prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md updated with the finding + fix. Co-Authored-By: Claude Sonnet 5 --- .../DISC_Walker/ROOM_INJECTION_HYBRID.md | 70 +++++++++++++++---- scripts/compile_rooms.py | 58 ++++++++++++--- 2 files changed, 105 insertions(+), 23 deletions(-) diff --git a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md index 10fa50ca2..952c14497 100644 --- a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md +++ b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md @@ -53,7 +53,8 @@ keeps them out of placement, same as HHS's 14), but the record was factually wro |---|---|---| | Terminal | 43 | **ALL synthetic** (`object_type=COMPILED`, `≈`-named) — never real, corrected above | | Duplex | 0 on `main`; **20** on unmerged branch `fable/modeller-lod400-livewire` @ `2821b8e` | real, habitability-filtered (Task 5 done — R301 Roof stripped) | -| SampleCastle, SampleHouse, Clinic, Garage, HHS, Hospital | 0 | no `spatial_structure` table | +| SampleHouse | 0 on `main`; **3** on unmerged branch `fable/modeller-lod400-livewire` @ `06b6605` | real, habitability-filtered (Task 6 done — "4 - Roof" stripped) | +| SampleCastle, Clinic, Garage, HHS, Hospital | 0 | no `spatial_structure` table | **Source IFCs on disk (`internal/sources/`) — note: the ARC is a DERIVED artifact of IFC metadata, so knowing the path alone fixes nothing; it only matters if it turns up an EXTRACTION bug (source has real @@ -127,11 +128,26 @@ where noted):** as one assignment, not sequence them as two. ### Task 2 — Wire `compile_rooms.py` into extraction/import as an automatic step (tagged `≈`, display-only) -**Status: NOT STARTED.** Currently a manual script (`compile_rooms.py --write`), never called from any -build/embed/import path (confirmed via grep this session — zero automatic callers). Needs wiring into -whichever step runs `extractIFC2DB.js` (both the embed/"ready-made" build step for our own residents, and -whatever live path handles a user's IFC import) so it runs automatically whenever a source IFC has no real -`IfcSpace`. +**Status: DATA-EFFECT DONE 2026-07-10 (Sonnet), AUTOMATION still NOT STARTED.** Ran +`compile_rooms.py --write` manually (a one-time repair, same treatment level as Tasks 1/6's manual ports) +against the 5 shipped residents that had ZERO `spatial_structure` at all: SampleCastle (25), HHS (2), +Clinic (113), Garage (5), Hospital (142) — each compiled from that building's OWN real wall/door geometry +already in its `elements_meta`/`element_transforms` (deterministic flood-fill, not invented). Combined with +Duplex (20 real) + SampleHouse (3 real) + Terminal (43 synthetic, pre-existing), **all 8 shipped +`*_ARC.db` now carry room data — §1's "every ARC, always" bar is met for the current data.** Witness +`witness_room_injection_all8.js` (W-ROOM-INJECT-ALL8) 32/32: coverage, tag-purity (no partial RM_/≈/COMPILED +tagging on any of the 5), zero elements_meta guid collisions, and — the one that matters most — the REAL +`spacesOf()` runtime filter returns 0 placement-eligible rows for all 5 synthetic-only buildings (proves +display-only rooms can never leak into schedule placement). Shipped bim-ootb +`fable/modeller-lod400-livewire` @ `a8955f2` (pushed, `rev-list origin..HEAD`=0). +**Still NOT DONE — this task's own automation half:** wiring `compile_rooms.py` as a step INSIDE +`extractIFC2DB.js` (or whichever path runs it) so a FUTURE re-extraction or a user's live IFC import gets +this for free, instead of needing another manual repair pass like this one. Currently a manual script +(`compile_rooms.py --write`), never called from any build/embed/import path (confirmed via grep — +zero automatic callers, still true after this session's manual run). This is the regression risk Task 4 +already named for the strip cascade, generalized: without automation, ANY future re-embed (the exact kind +that caused Task 6's SampleHouse gap, via `6068fab`'s finalize_all_8.js never carrying `spatial_structure` +forward) can silently wipe this same data again. ### Task 3 — New Modeller Outliner "Rooms" category (the display/wow-factor surface) **Status: NOT STARTED, NOT YET SPECCED.** No existing room display anywhere in the Modeller (checked — @@ -238,15 +254,39 @@ were executed as one assignment, ✅ DONE 2026-07-10 (see §6 RESULTS) — that directive — no reason logged, just apply it).** Tasks 2–4 remain NOT STARTED. New: ### Task 6 — SampleHouse extraction bug: 3 real, usable rooms exist in source but never reached the ARC -**Status: NOT STARTED. Assigned to Sonnet.** -Found this session (§2): `internal/sources/Ifc4_SampleHouse.ifc` contains 4 real `IFCSPACE` entities -(`Living room`, `Bedroom`, `Entrance hall`, `Roof`) but shipped `SampleHouse_ARC.db` has 0 rows in -`spatial_structure` — the extraction pipeline dropped them somewhere between source and shipped ARC. This -is an EXTRACTION gap, not a source-data gap (unlike Clinic/Garage/Hospital/HHS, which have no source IFC -in this checkout to check either way). Scope: find where `extractIFC2DB.js` (or whichever step runs on -this source) fails to carry `IfcSpace` through to `spatial_structure` for this specific file, fix it, run -Task 5's `spaceHabitable()` classifier on the result (expect Roof excluded, 3 kept), re-embed. Small blast -radius — one building, 4 rows. +**Status: ✅ DONE 2026-07-10 (Sonnet). Witness: W-ROOM-HAB-SH 6/6, bim-ootb +`fable/modeller-lod400-livewire` @ `06b6605` (parent `2821b8e`, pushed, `rev-list origin..HEAD`=0).** + +**Root cause — checked directly before writing any code, per this task's own instruction:** NOT the same +cause as Task 4's `b93ca13` cascade. Direct diff of `modeller/SampleHouse_extracted.db` across `b93ca13` +(`git show b93ca13^:...` vs `git show b93ca13:...`) shows `spatial_structure` UNCHANGED — 7 rows (1 +`IfcBuilding` + 2 `IfcBuildingStorey` + 4 `IfcSpace`) both before and after that commit. The strip did not +touch rooms for SampleHouse at all. The actual cause is the LATER commit `6068fab` ("embed 8 ARC-only +buildings + shared mesh.db resident registry", one day after `b93ca13`) — its consolidation pipeline +(`embed8_scripts/finalize_all_8.js`, fed by an intermediate `_all.db` merge step that ran only in `/tmp` +and was never preserved, per `74c27b371`'s own commit note) produced every shipped `*_ARC.db` WITHOUT a +`spatial_structure` table at all — confirmed a general embed-pipeline gap, not SampleHouse-specific: +`main`'s `Duplex_ARC.db` has the identical 0-row gap (matching this doc's own §2 table, pre-Task-1). So +Task 4 (hardening the strip cascade) would NOT have fixed this — the table survived that cascade fine and +was only lost one commit later, in the ARC.db schema the embed step writes. + +**Fix:** ported `spatial_structure` verbatim from `SampleHouse_extracted.db` (source is intact, verified +above) into the shipped `SampleHouse_ARC.db` (had none), ran Task 5's `spaceHabitable()` against the known-4 +— `4 - Roof` excluded (`label:ROOF`), 3 kept (`1 - Living room`, `2 - Bedroom`, `3 - Entrance hall`) — +stripped the Roof space by classifier verdict (the `IfcBuildingStorey` named "Roof" survives, same +convention as Duplex's R301). Notable, reported honestly rather than assumed: unlike Duplex, this +building's independent zband geometry signal does NOT also fire for the Roof space — SampleHouse_ARC.db's +own real ARC roof members/plates reach z1=4.50, taller than the Roof space's z1=3.50 — so the label signal +alone is what excludes it here. `witness_room_hab_samplehouse.js` (W-ROOM-HAB-SH): PORT 0→7, H1 +precision/recall (3 ok + exactly 1 excluded), H2 falsifier (reports the zband asymmetry above, not forced +to match Duplex's pattern), H3 guid-overlap, H4 coord-overlap, H5 strip (3 IfcSpace/0 Roof/2 storeys+1 +building) — 6/6, logs read at `/tmp/wt-fable-livewire/logs/w_room_hab_sh_proof.log` and +`w_room_hab_sh_strip.log`. + +**Not done (explicitly out of this task's small scope):** re-running the full W-DW-LIVEWIRE browser suite +to prove `dwWalk(..., {schedule:true})` engages placement on SampleHouse specifically — the task's stated +witness bar was the before/after room counts, which is met; live-engagement proof would need the puppeteer +harness and wasn't asked for here. ## §6 — Execution spec: Task 1 + Task 5 as one assignment (2026-07-10, Fable session — spec-first) diff --git a/scripts/compile_rooms.py b/scripts/compile_rooms.py index 7c67ae27f..d8ed875ad 100644 --- a/scripts/compile_rooms.py +++ b/scripts/compile_rooms.py @@ -28,6 +28,17 @@ # overlaps. IfcStair% LIKE also covers IfcStairFlight. (User: "staircase is also marked as room".) STAIR_LIKE = ("IfcStair%", "IfcRamp%") STAIR_OVERLAP_REJECT = 0.35 # drop a pocket if a stair footprint covers ≥35% of its area +# §DOOR-RESCUE: a pocket under MIN_AREA is usually a wall-joint/cavity artefact of the grid flood-fill +# (measured: on real buildings these cluster <1.0 m^2). But a genuine small room — a toilet, riser, +# store, utility closet — is real even at 1.5-3.9 m^2, and the one structural clue a wall-cavity never +# has is a DOOR: every enclosed room a person uses has one, a cavity doesn't. So a pocket in +# [DOOR_RESCUE_MIN_AREA, MIN_AREA) is accepted ONLY if a real IfcDoor centre sits on/against its +# rectangle (never invented — same real extracted door positions already used as WALL_LIKE blockers). +# Verified on real data before shipping (HHS/Clinic/Hospital/SampleCastle): area<1.0 m^2 pockets are +# door-adjacent in only a handful of cases (noise); 1.0-4.0 m^2 pockets are door-adjacent in the large +# majority (real small rooms) — the two bands are not the same population. +DOOR_RESCUE_MIN_AREA = 1.0 # m^2 — below this, even a door-adjacent pocket is treated as noise +DOOR_RESCUE_BUFFER = 1.0 # m — how far a door centre may sit from the pocket rect and still count # §APPROX: these rooms are COMPILED from wall enclosure (flood-fill), NOT extracted IfcSpace. # Validated ~5/21 recall on ground-truth Duplex → treat as APPROXIMATE. Labelled '≈' + COMPILED. @@ -54,6 +65,20 @@ def storey_stairs(c): by.setdefault(st or "Unknown", []).append((cx, cy, bx, by_)) return by +def storey_doors(c): + """Per-storey door centres (cx,cy) — the §DOOR-RESCUE clue for genuine small rooms.""" + rows = c.execute( + "SELECT m.storey, t.center_x,t.center_y FROM elements_meta m " + "JOIN element_transforms t ON t.guid=m.guid WHERE m.ifc_class LIKE 'IfcDoor%' " + "AND t.center_x IS NOT NULL").fetchall() + by = {} + for st, cx, cy in rows: + by.setdefault(st or "Unknown", []).append((cx, cy)) + return by + +def _door_adjacent(rx0, ry0, rx1, ry1, doors, buf=DOOR_RESCUE_BUFFER): + return any(rx0 - buf <= dx <= rx1 + buf and ry0 - buf <= dy <= ry1 + buf for dx, dy in doors) + def _stair_overlap_frac(rx0, ry0, rx1, ry1, stairs): """Largest fraction of room rect [rx0,ry0,rx1,ry1] covered by any single stair footprint.""" room_area = max(1e-6, (rx1 - rx0) * (ry1 - ry0)) @@ -66,8 +91,9 @@ def _stair_overlap_frac(rx0, ry0, rx1, ry1, stairs): best = max(best, (ox * oy) / room_area) return best -def flood_rooms(walls, stairs=None): +def flood_rooms(walls, stairs=None, doors=None): stairs = stairs or [] + doors = doors or [] xs0 = min(w[0] - w[3] / 2 for w in walls); xs1 = max(w[0] + w[3] / 2 for w in walls) ys0 = min(w[1] - w[4] / 2 for w in walls); ys1 = max(w[1] + w[4] / 2 for w in walls) pad = RES * 2 @@ -128,9 +154,16 @@ def iy(y): return min(ny - 1, max(0, int((y - ys0) / RES))) if 0 <= a < nx and 0 <= b < ny and enclosed[a, b] and not seen[a, b]: seen[a, b] = True; st.append((a, b)) area = len(comp) * cell_area - if area < MIN_AREA or area > MAX_AREA_ABS or area > plan_area * MAX_AREA_FRAC: continue + if area > MAX_AREA_ABS or area > plan_area * MAX_AREA_FRAC: continue wx0 = xs0 + mni * RES; wx1 = xs0 + (mxi + 1) * RES wy0 = ys0 + mnj * RES; wy1 = ys0 + (mxj + 1) * RES + door_rescued = False + if area < MIN_AREA: + # §DOOR-RESCUE: below MIN_AREA, only a door-adjacent pocket in the small-room band + # survives — everything else this small is a wall-joint/cavity artefact, dropped. + if area < DOOR_RESCUE_MIN_AREA or not _door_adjacent(wx0, wy0, wx1, wy1, doors): + continue + door_rescued = True # §STAIR-EXCLUDE: a stair/ramp footprint covering this pocket → it's a circulation # shaft, not a room. Drop it (the lens was showing staircases as rooms). sf = _stair_overlap_frac(wx0, wy0, wx1, wy1, stairs) @@ -138,7 +171,8 @@ def iy(y): return min(ny - 1, max(0, int((y - ys0) / RES))) print(f" skip stair-shaft pocket area={round(area)} stair_overlap={sf:.0%}"); continue rooms.append({ "cx": (wx0 + wx1) / 2, "cy": (wy0 + wy1) / 2, "cz": cz, - "sx": wx1 - wx0, "sy": wy1 - wy0, "sz": max(bz, 2.0), "area": area}) + "sx": wx1 - wx0, "sy": wy1 - wy0, "sz": max(bz, 2.0), "area": area, + "door_rescued": door_rescued}) return rooms def main(): @@ -155,26 +189,30 @@ def main(): pass by = storey_walls(c) stairs_by = storey_stairs(c) + doors_by = storey_doors(c) # §STAIR-EXCLUDE: stair storey is often 'Unknown'/unassigned in the extract, and a stair is a # CONTINUOUS vertical shaft anyway — so test every room pocket against the UNION of all stair # footprints by XY (not per-storey). A staircase at an XY is circulation on whatever floor it cuts. all_stairs = [s for lst in stairs_by.values() for s in lst] - total = 0; allrooms = []; st_z = {} + total = 0; door_rescued_total = 0; allrooms = []; st_z = {} for st in sorted(by): ws = by[st] if len(ws) < 3: print(f" storey {st!r}: walls={len(ws)} (too few — skip)"); continue - rooms = flood_rooms(ws, all_stairs) + rooms = flood_rooms(ws, all_stairs, doors_by.get(st, [])) total += len(rooms) + rescued = sum(1 for r in rooms if r.get('door_rescued')) + door_rescued_total += rescued st_z[st] = sum(w[2] for w in ws) / len(ws) # storey z = mean wall centre-z - print(f" storey {st!r}: walls={len(ws)} → rooms={len(rooms)} areas={[round(r['area']) for r in rooms]}") + print(f" storey {st!r}: walls={len(ws)} doors={len(doors_by.get(st, []))} → rooms={len(rooms)} " + f"(door_rescued={rescued}) areas={[round(r['area']) for r in rooms]}") for k, r in enumerate(rooms): r["storey"] = st; r["guid"] = f"RM_{st}_{k+1}".replace(" ", "_") # §APPROX: '≈' marks the room as compiled/approximate in the lens label. # parent_guid → a compiled storey row (created below) so the Room lens groups per floor. r["name"] = f"≈ {st} R{k+1}"; r["parent"] = st_guid.get(st) or ("STC_" + st).replace(" ", "_") allrooms.append(r) - print(f"TOTAL compiled rooms = {total}") + print(f"TOTAL compiled rooms = {total} (door_rescued={door_rescued_total})") if not write: print("(dry run — pass --write to inject)"); return # ensure spatial_structure has bbox columns @@ -203,9 +241,13 @@ def _addcol(table, col, typ): # remove any prior compiled rooms (idempotent) c.execute("DELETE FROM spatial_structure WHERE type='IfcSpace' AND guid LIKE 'RM_%'") for r in allrooms: + # predefined_type distinguishes wall-enclosure rooms from §DOOR-RESCUE small rooms (toilets/ + # closets/utility) for traceability — object_type stays 'COMPILED' either way (the tag + # spacesOf()'s exclusion filter and every witness's tag-purity check actually key on). c.execute("INSERT INTO spatial_structure (guid,type,name,parent_guid,object_type,predefined_type," "center_x,center_y,center_z,size_x,size_y,size_z) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", - (r["guid"], "IfcSpace", r["name"], r["parent"], "COMPILED", "INTERNAL", + (r["guid"], "IfcSpace", r["name"], r["parent"], "COMPILED", + "INTERNAL_SMALL" if r.get("door_rescued") else "INTERNAL", r["cx"], r["cy"], r["cz"], r["sx"], r["sy"], r["sz"])) # rel_contained_in_space: elements whose XY centre falls inside a room (compiled) if not c.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='rel_contained_in_space'").fetchall(): From 06db5ae46fe605071731a501728ed4652181c9cd Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 16:28:37 +0800 Subject: [PATCH 009/244] =?UTF-8?q?docs(protocol):=20DB=20Storage=20Policy?= =?UTF-8?q?=20=E2=80=94=20stop=20new=20.db=20commits,=20GitHub=20LFS=20ban?= =?UTF-8?q?dwidth=20cap=20hit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub flagged 10GB/month LFS bandwidth usage. 59+ .db files already LFS-tracked (deploy/buildings/*, build/*.db) burn quota on every clone/fetch. Going forward: schema/rules DBs regenerate via migration/*.sql or their mining scripts; extracted/ derived building DBs distribute via OCI (already the dev/live channel), not git. Existing LFS-tracked files are left alone — this only stops new commits. Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 8f8488571..4f7ecb832 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -129,6 +129,28 @@ Before ending, update PROGRESS.md with: - **Pre-Flight Citation:** Before code changes, cite the spec: `// Implementing BBC.md §X.Y — Witness: W-NAME` - **Traceability:** Check `TestArchitecture.md` §Traceability Matrix before and after changes +## DB Storage Policy — GitHub LFS bandwidth (2026-07-10) +**GitHub emailed a 10GB/month LFS bandwidth-cap warning.** `.gitattributes` has a blanket `*.db filter=lfs` +rule and 59+ `.db` files are already LFS-tracked (`deploy/buildings/*_extracted.db`/`*_library.db`, +`build/*.db`, etc.) — every clone/fetch/checkout of a branch touching these re-downloads them through the +capped quota. **From now on: do NOT commit/force-add a `.db` file to git, even if `.gitattributes` would +route it through LFS.** Most DB paths are already `.gitignore`d (`/library/*`, `deploy/buildings/`, +`database/*.db`, `/build/erp/*.db`, etc. — see `.gitignore`) — that's correct, keep it that way, don't +`git add -f` past it. Two channels replace committing the binary: +- **Schema/rules/pattern DBs** (small, structurally regenerable — `duplex_rules.db`, `terminal_rules.db`, + `ERP.db` seed data): migration scripts in `migration/*.sql` (see Sacred Files below — `DV__rules.sql` + is already this pattern for mined rules) or the mining scripts that generate them + (`run_RosettaStones.sh`/`onboard_ifc.sh`/`project_rule_mesh_binding.py` etc.) — regenerate on demand, don't + version the binary. +- **Extracted/derived building DBs** (`deploy/buildings/*_extracted.db`/`*_library.db`, mesh/geo DBs — NOT + reproducible from a short SQL script, only from re-running extraction on the source IFC): distribute via + **OCI** (`deploy/OCI_UPLOAD.md` §RULES — the existing dev/live snapshot channel), not git/LFS. If a building + needs to travel with a branch for local dev, keep it gitignored and hand it off out-of-band (OCI, or a + read-only copy), never commit it. +- **Already-LFS-tracked files are sunk cost** — not retroactively purged as part of this rule (that's a + separate, disruptive history-rewrite decision, not taken here). This rule only stops the bleeding going + forward: no NEW `.db` commits. + ## Sacred Files (edit with extreme care) - `deploy/live/*` — PRODUCTION snapshot, never edit (see PRIME RULE) - `migration/*.sql` — append only, never modify existing migrations. EXEMPT: `DV__rules.sql` — regenerated From 0cc6f8e3ac1d84cc1fc8ce0407aad752b35f4bab Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 16:31:50 +0800 Subject: [PATCH 010/244] fix(compile_rooms): replace fitted door-rescue band with an abstract rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User pushback (correct): the previous §DOOR-RESCUE fix used DOOR_RESCUE_MIN_AREA=1.0 and a flat 1.0m buffer picked by eyeballing HHS/Clinic/Hospital's specific data — a fitted band, not a rule. This generalizes it: - The room definition is now architectural, not size-banded: an enclosed pocket is a room if it is big enough on its own (area >= MIN_AREA, unchanged) OR it has a real door AND isn't a bare rasterization sliver. No area number stands between "noise" and "small room" anymore. - Door adjacency buffer is now each door's OWN extracted footprint (half its real leaf/frame span) + one grid cell of slack (RES) -- self-scaling to whatever doors a building actually has, not a fixed metre guess that happened to work on 5 sample buildings. - The noise floor is NOISE_FLOOR_DIM = 3*RES (0.6m) -- a pocket narrower than a few grid cells in EITHER axis is rasterization noise by construction, a property of the flood-fill's own resolution, not an area threshold reverse-engineered from observed rooms. Re-verified: HHS 6, Clinic 195, Hospital 201, SampleCastle 53, Garage 5 (close to, not identical to, the earlier fitted-band numbers -- expected, since the rule is now geometry-derived rather than tuned to match them). W-ROOM-INJECT-ALL8 rerun 32/32 on bim-ootb worktree. Co-Authored-By: Claude Sonnet 5 --- scripts/compile_rooms.py | 56 ++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/scripts/compile_rooms.py b/scripts/compile_rooms.py index d8ed875ad..b5001d985 100644 --- a/scripts/compile_rooms.py +++ b/scripts/compile_rooms.py @@ -28,17 +28,21 @@ # overlaps. IfcStair% LIKE also covers IfcStairFlight. (User: "staircase is also marked as room".) STAIR_LIKE = ("IfcStair%", "IfcRamp%") STAIR_OVERLAP_REJECT = 0.35 # drop a pocket if a stair footprint covers ≥35% of its area -# §DOOR-RESCUE: a pocket under MIN_AREA is usually a wall-joint/cavity artefact of the grid flood-fill -# (measured: on real buildings these cluster <1.0 m^2). But a genuine small room — a toilet, riser, -# store, utility closet — is real even at 1.5-3.9 m^2, and the one structural clue a wall-cavity never -# has is a DOOR: every enclosed room a person uses has one, a cavity doesn't. So a pocket in -# [DOOR_RESCUE_MIN_AREA, MIN_AREA) is accepted ONLY if a real IfcDoor centre sits on/against its -# rectangle (never invented — same real extracted door positions already used as WALL_LIKE blockers). -# Verified on real data before shipping (HHS/Clinic/Hospital/SampleCastle): area<1.0 m^2 pockets are -# door-adjacent in only a handful of cases (noise); 1.0-4.0 m^2 pockets are door-adjacent in the large -# majority (real small rooms) — the two bands are not the same population. -DOOR_RESCUE_MIN_AREA = 1.0 # m^2 — below this, even a door-adjacent pocket is treated as noise -DOOR_RESCUE_BUFFER = 1.0 # m — how far a door centre may sit from the pocket rect and still count +# §DOOR-RESCUE (abstract rule, not a fitted band): the definition of "room" is architectural, not a +# size threshold — an enclosed pocket is a room IFF it has a DOOR (how a person enters/exits it). A +# wall cavity, duct or structural void never has one. MIN_AREA alone is a blunt proxy for this that +# only works by accident when small rooms happen to be rare; it wrongly drops real small rooms +# (toilets, risers, store/utility closets) that a strict area cutoff can't tell apart from noise. +# So below MIN_AREA, door presence — not a second area number picked by eyeballing any one building — +# is the actual test. Two supporting checks are geometry-derived, not observed-data-fitted: +# - the adjacency buffer is each DOOR's OWN extracted footprint (half its real leaf/frame span) plus +# one grid cell of rasterization slack — self-scaling to whatever doors this building actually has, +# never a fixed metre guess; +# - NOISE_FLOOR_DIM rejects a pocket whose rect is narrower than a few grid cells in EITHER axis — +# that is a property of the flood-fill's own resolution (a 1-2 cell sliver is rasterization noise +# by construction, regardless of which building produced it), not a threshold tuned to this data. +NOISE_FLOOR_DIM = 3 * RES # m — a pocket narrower than this in x OR y is a grid artefact, not a room +DOOR_BUFFER_SLACK = RES # m — rasterization slack added on top of each door's own real footprint # §APPROX: these rooms are COMPILED from wall enclosure (flood-fill), NOT extracted IfcSpace. # Validated ~5/21 recall on ground-truth Duplex → treat as APPROXIMATE. Labelled '≈' + COMPILED. @@ -66,18 +70,23 @@ def storey_stairs(c): return by def storey_doors(c): - """Per-storey door centres (cx,cy) — the §DOOR-RESCUE clue for genuine small rooms.""" + """Per-storey door (cx,cy,bx,by) — the §DOOR-RESCUE clue for genuine small rooms. Each door's + OWN real footprint is carried through so adjacency self-scales to that door, not a guessed metre.""" rows = c.execute( - "SELECT m.storey, t.center_x,t.center_y FROM elements_meta m " - "JOIN element_transforms t ON t.guid=m.guid WHERE m.ifc_class LIKE 'IfcDoor%' " - "AND t.center_x IS NOT NULL").fetchall() + "SELECT m.storey, t.center_x,t.center_y, COALESCE(t.bbox_x,0), COALESCE(t.bbox_y,0) " + "FROM elements_meta m JOIN element_transforms t ON t.guid=m.guid " + "WHERE m.ifc_class LIKE 'IfcDoor%' AND t.center_x IS NOT NULL").fetchall() by = {} - for st, cx, cy in rows: - by.setdefault(st or "Unknown", []).append((cx, cy)) + for st, cx, cy, bx, by_ in rows: + by.setdefault(st or "Unknown", []).append((cx, cy, bx, by_)) return by -def _door_adjacent(rx0, ry0, rx1, ry1, doors, buf=DOOR_RESCUE_BUFFER): - return any(rx0 - buf <= dx <= rx1 + buf and ry0 - buf <= dy <= ry1 + buf for dx, dy in doors) +def _door_adjacent(rx0, ry0, rx1, ry1, doors): + for dx, dy, dbx, dby in doors: + buf = max(dbx, dby) / 2 + DOOR_BUFFER_SLACK # this door's own span, not a fixed guess + if rx0 - buf <= dx <= rx1 + buf and ry0 - buf <= dy <= ry1 + buf: + return True + return False def _stair_overlap_frac(rx0, ry0, rx1, ry1, stairs): """Largest fraction of room rect [rx0,ry0,rx1,ry1] covered by any single stair footprint.""" @@ -157,11 +166,14 @@ def iy(y): return min(ny - 1, max(0, int((y - ys0) / RES))) if area > MAX_AREA_ABS or area > plan_area * MAX_AREA_FRAC: continue wx0 = xs0 + mni * RES; wx1 = xs0 + (mxi + 1) * RES wy0 = ys0 + mnj * RES; wy1 = ys0 + (mxj + 1) * RES + # §DOOR-RESCUE (abstract test, applies uniformly — not a size band): a pocket is a room if + # it is big enough to obviously be one on its own (area >= MIN_AREA, the original rule, + # unchanged for the common case) OR it has a real door AND isn't a bare rasterization sliver + # (NOISE_FLOOR_DIM, a grid-resolution property, not a fitted area number). door_rescued = False if area < MIN_AREA: - # §DOOR-RESCUE: below MIN_AREA, only a door-adjacent pocket in the small-room band - # survives — everything else this small is a wall-joint/cavity artefact, dropped. - if area < DOOR_RESCUE_MIN_AREA or not _door_adjacent(wx0, wy0, wx1, wy1, doors): + dims_ok = (wx1 - wx0) >= NOISE_FLOOR_DIM and (wy1 - wy0) >= NOISE_FLOOR_DIM + if not (dims_ok and _door_adjacent(wx0, wy0, wx1, wy1, doors)): continue door_rescued = True # §STAIR-EXCLUDE: a stair/ramp footprint covering this pocket → it's a circulation From d3eef1765f61eb4080ac8eeaf12f8a8aa6767787 Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 16:33:04 +0800 Subject: [PATCH 011/244] =?UTF-8?q?docs(disc=5Fwalker):=20record=20both=20?= =?UTF-8?q?door-rescue=20passes=20=E2=80=94=20fitted=20band=20superseded?= =?UTF-8?q?=20by=20abstract=20rule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DISC_Walker/ROOM_INJECTION_HYBRID.md | 53 +++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md index 952c14497..62e93e201 100644 --- a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md +++ b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md @@ -130,16 +130,49 @@ where noted):** ### Task 2 — Wire `compile_rooms.py` into extraction/import as an automatic step (tagged `≈`, display-only) **Status: DATA-EFFECT DONE 2026-07-10 (Sonnet), AUTOMATION still NOT STARTED.** Ran `compile_rooms.py --write` manually (a one-time repair, same treatment level as Tasks 1/6's manual ports) -against the 5 shipped residents that had ZERO `spatial_structure` at all: SampleCastle (25), HHS (2), -Clinic (113), Garage (5), Hospital (142) — each compiled from that building's OWN real wall/door geometry -already in its `elements_meta`/`element_transforms` (deterministic flood-fill, not invented). Combined with -Duplex (20 real) + SampleHouse (3 real) + Terminal (43 synthetic, pre-existing), **all 8 shipped -`*_ARC.db` now carry room data — §1's "every ARC, always" bar is met for the current data.** Witness -`witness_room_injection_all8.js` (W-ROOM-INJECT-ALL8) 32/32: coverage, tag-purity (no partial RM_/≈/COMPILED -tagging on any of the 5), zero elements_meta guid collisions, and — the one that matters most — the REAL -`spacesOf()` runtime filter returns 0 placement-eligible rows for all 5 synthetic-only buildings (proves -display-only rooms can never leak into schedule placement). Shipped bim-ootb -`fable/modeller-lod400-livewire` @ `a8955f2` (pushed, `rev-list origin..HEAD`=0). +against the 5 shipped residents that had ZERO `spatial_structure` at all: SampleCastle, HHS, Clinic, +Garage, Hospital — each compiled from that building's OWN real wall/door geometry already in its +`elements_meta`/`element_transforms` (deterministic flood-fill, not invented). Combined with Duplex (20 +real) + SampleHouse (3 real) + Terminal (43 synthetic, pre-existing), **all 8 shipped `*_ARC.db` now +carry room data — §1's "every ARC, always" bar is met for the current data.** + +**§DOOR-RESCUE (user-flagged, fixed twice this session — record both passes so the reasoning isn't +lost):** user checked HHS ("large U-shape office block, lots of office rooms with doors") against the +first pass's count and correctly called it wrong — 2 rooms for a whole office floor. Root cause verified +(not assumed): NOT a mis-tuned `MIN_AREA=4.0` hiding detected-but-rejected rooms — instrumenting the +flood-fill to log every candidate (not just accepted ones) showed HHS's floors are ~91% "exterior- +reachable" in the flood-fill; there's genuinely very little enclosing wall data to divide the floor with +(76-84 wall-like elements over a ~66×54m floor, vs. Hospital's 582 producing 32 real rooms on a +comparable floor). Separately, restrooms/utility rooms were being dropped by the SAME blanket +`MIN_AREA` cutoff on every building, not just HHS — the user's hint ("rooms has doors") pointed at the +fix: a real small room always has a door, a wall cavity never does. +- **First pass (superseded):** added a fixed `DOOR_RESCUE_MIN_AREA=1.0` / `1.0m` buffer band — picked by + eyeballing HHS/Clinic/Hospital's specific candidate-pocket data. User pushback (correct): "we suppose + to be abstract rules based room generation, not hard-coded." +- **Second pass (shipped):** replaced the fitted band with an architectural rule applied uniformly, not + size-banded — a pocket is a room if big enough alone (`area >= MIN_AREA`, unchanged) **OR** it has a + real door and clears a grid-resolution noise floor. Both supporting numbers are now geometry-derived, + not observed-data-fitted: the door-adjacency buffer is each door's OWN extracted footprint (half its + real leaf/frame span) + one grid cell of rasterization slack (`RES`), and the noise floor + (`NOISE_FLOOR_DIM = 3×RES = 0.6m`) rejects any pocket narrower than a few grid cells in either axis — + a property of the flood-fill's own resolution, true for any building, not a number reverse-engineered + from this one's rooms. `predefined_type='INTERNAL_SMALL'` marks door-rescued rows for traceability; + `object_type='COMPILED'` (what every tag-purity/placement-exclusion check keys on) is unchanged. +- **MEP-fixture clue considered, rejected:** checked whether sanitary/plumbing fixture presence (a more + direct "this is a toilet" signal) could help — found only Clinic still carries `IfcFlowTerminal` + (102 rows); HHS/Hospital/Garage/SampleCastle have zero MEP fixture data at all (already stripped by the + ARC-only discipline strip, `b93ca13`). Not usable as a general signal — door adjacency is the one clue + that survives ARC-only extraction on every building. +- Final counts (bim-compiler `scripts/compile_rooms.py` @ `0cc6f8e3a`): SampleCastle 25→53, HHS 2→6, + Clinic 113→195, Hospital 142→201, Garage unchanged at 5 (no door-adjacent small pockets found there). + +Witness `witness_room_injection_all8.js` (W-ROOM-INJECT-ALL8) 32/32 (rerun after both passes): coverage, +tag-purity (no partial RM_/≈/COMPILED tagging on any of the 5), zero `elements_meta` guid collisions, and +— the one that matters most — the REAL `spacesOf()` runtime filter returns 0 placement-eligible rows for +all 5 synthetic-only buildings (proves display-only rooms, door-rescued or not, can never leak into +schedule placement). Shipped bim-ootb `fable/modeller-lod400-livewire` @ `18fc9bc` (parent `a8955f2`, +pushed, `rev-list origin..HEAD`=0) + bim-compiler `fable/meshdb-livewire` @ `0cc6f8e3a` (pushed). + **Still NOT DONE — this task's own automation half:** wiring `compile_rooms.py` as a step INSIDE `extractIFC2DB.js` (or whichever path runs it) so a FUTURE re-extraction or a user's live IFC import gets this for free, instead of needing another manual repair pass like this one. Currently a manual script From 5208299070f883420d1b222c948ae7d7fcb6ae73 Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 17:07:06 +0800 Subject: [PATCH 012/244] =?UTF-8?q?fix(compile=5Frooms):=20=C2=A7DOOR-NOT-?= =?UTF-8?q?ROOM=20=E2=80=94=20lift/elevator=20doors=20aren't=20room=20evid?= =?UTF-8?q?ence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tested SEAL=1 (narrower wall-dilation) to try to recover HHS's WC-stall spaces the user found named in German ("WC Trennwand") — rejected: doesn't fix HHS (the WC block's OWN enclosing walls, not just its internal stall partitions, are simply absent from this extraction, a resolution/ data-completeness issue SEAL can't touch) AND it fragments real rooms elsewhere into spurious 1-2 sq.m splinters in Hospital (142->253, mostly noise) and SampleCastle -- a regression, not an improvement. Reverted to SEAL=2 (already-shipped, verified value unchanged). Separately, user asked whether doors have standard dimensions we could use as a smell test. Checked: real hinged doors cluster 0.7-1.1m (single leaf) / 1.2-2.7m (double leaf/wide entrance) across all 5 buildings' door data -- consistent, no anomaly, EXCEPT SampleCastle: 28 IfcDoor rows at 0.5m width, all named 'liftdeur' (Dutch: elevator door). Real, correctly-classified doors -- but they lead to a lift shaft, not a room, and 2 of them were rescuing actual elevator-shaft fragments as fake compiled "rooms" (verified directly: room count with vs without lift doors as the door-rescue signal differs). Same shape of problem as the existing §STAIR-EXCLUDE pattern -- excluded via a maintained, multi-language door-name keyword list (liftdeur/lift/elevator/aufzug/ fahrstuhl/hoist), not a width cutoff (a lift door's width isn't reliably distinct from a narrow single-leaf door's). SampleCastle 53->51 (the 2 lift-shaft fakes removed); HHS/Clinic/Hospital/Garage unchanged (no lift doors in their door-rescue signal). W-ROOM-INJECT-ALL8 rerun 32/32. Co-Authored-By: Claude Sonnet 5 --- scripts/compile_rooms.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/scripts/compile_rooms.py b/scripts/compile_rooms.py index b5001d985..a8adc4705 100644 --- a/scripts/compile_rooms.py +++ b/scripts/compile_rooms.py @@ -43,6 +43,17 @@ # by construction, regardless of which building produced it), not a threshold tuned to this data. NOISE_FLOOR_DIM = 3 * RES # m — a pocket narrower than this in x OR y is a grid artefact, not a room DOOR_BUFFER_SLACK = RES # m — rasterization slack added on top of each door's own real footprint +# §DOOR-NOT-ROOM: a door that leads to a SHAFT, not a habitable room, must not be used as the +# §DOOR-RESCUE "this pocket is a room" signal — same shape of problem as §STAIR-EXCLUDE (a real, +# correctly-classified element that still isn't evidence of a room). Found on real data (SampleCastle): +# 28 IfcDoor rows named 'liftdeur' (Dutch: lift/elevator door), width 0.5m — real doors, but 2 of them +# were rescuing actual elevator-shaft fragments as fake "rooms". Name-keyword match (multi-language, +# reviewed against real extractions, same discipline as NONHAB_TYPES) — not a width cutoff, since a +# lift door's width alone isn't reliably distinct from a narrow single-leaf door's. +NON_ROOM_DOOR_NAMES = ("liftdeur", "lift", "elevator", "aufzug", "fahrstuhl", "hoist") +def _is_room_door(name): + n = (name or "").lower() + return not any(k in n for k in NON_ROOM_DOOR_NAMES) # §APPROX: these rooms are COMPILED from wall enclosure (flood-fill), NOT extracted IfcSpace. # Validated ~5/21 recall on ground-truth Duplex → treat as APPROXIMATE. Labelled '≈' + COMPILED. @@ -73,11 +84,12 @@ def storey_doors(c): """Per-storey door (cx,cy,bx,by) — the §DOOR-RESCUE clue for genuine small rooms. Each door's OWN real footprint is carried through so adjacency self-scales to that door, not a guessed metre.""" rows = c.execute( - "SELECT m.storey, t.center_x,t.center_y, COALESCE(t.bbox_x,0), COALESCE(t.bbox_y,0) " + "SELECT m.storey, m.element_name, t.center_x,t.center_y, COALESCE(t.bbox_x,0), COALESCE(t.bbox_y,0) " "FROM elements_meta m JOIN element_transforms t ON t.guid=m.guid " "WHERE m.ifc_class LIKE 'IfcDoor%' AND t.center_x IS NOT NULL").fetchall() by = {} - for st, cx, cy, bx, by_ in rows: + for st, name, cx, cy, bx, by_ in rows: + if not _is_room_door(name): continue # §DOOR-NOT-ROOM: lift/elevator doors aren't room evidence by.setdefault(st or "Unknown", []).append((cx, cy, bx, by_)) return by From e5695ef48f349719c92ad78b33f3b60ec42e9e64 Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 17:08:28 +0800 Subject: [PATCH 013/244] docs(disc_walker): record SEAL=1 rejection + lift-door exclusion passes, HHS WC gap flagged open --- .../DISC_Walker/ROOM_INJECTION_HYBRID.md | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md index 62e93e201..85c94a8ef 100644 --- a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md +++ b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md @@ -163,15 +163,46 @@ fix: a real small room always has a door, a wall cavity never does. (102 rows); HHS/Hospital/Garage/SampleCastle have zero MEP fixture data at all (already stripped by the ARC-only discipline strip, `b93ca13`). Not usable as a general signal — door adjacency is the one clue that survives ARC-only extraction on every building. -- Final counts (bim-compiler `scripts/compile_rooms.py` @ `0cc6f8e3a`): SampleCastle 25→53, HHS 2→6, - Clinic 113→195, Hospital 142→201, Garage unchanged at 5 (no door-adjacent small pockets found there). - -Witness `witness_room_injection_all8.js` (W-ROOM-INJECT-ALL8) 32/32 (rerun after both passes): coverage, +- **Third pass — user found HHS's doors/walls are German-named (Revit export), e.g. `Türelement + 2-flg - Drehflügel - Glas` (2-leaf glazed swing-door) and `WC Trennwand 5.0` (WC partition wall, + 50mm) — real evidence the building genuinely has a restroom, checked directly. But the specific + WC block (walls at x 9.8-10.5, y 35.5-37.4) turned out NOT to be among the compiled rooms even + after door-rescue: instrumenting the flood-fill grid cell-by-cell showed the ENTIRE region comes + back fully wall-blocked pre-flood — the individual stall gaps (~0.9-1.0m between 50mm partitions) + are real but too narrow for `SEAL=2`'s 0.4m dilation to leave any free cell for the flood-fill to + find, let alone enclose. Tested `SEAL=1` (0.2m) as the fix, per user's chosen path (re-verify all + 5, no regression before shipping) — **rejected on direct evidence**: it did NOT recover HHS's WC + (still 6 rooms — the WC block's own walls separating it from the general office floor, not just + its internal stall partitions, are simply absent from this extraction; a data-completeness gap, + not a dilation-tuning one) AND it fragmented real rooms elsewhere into spurious 1-2 m² splinters + (Hospital 142→253 mostly noise, SampleCastle inflated too) — reverted to `SEAL=2`, unchanged. + HHS's WC gap stays open; the honest cause is now on record instead of papered over. +- **Fourth pass — user asked whether doors have standard real-world dimensions usable as a "smell + test."** Checked door-width distributions across all 5 buildings against known hinged-door + conventions (~0.7-1.1m single leaf, ~1.2-2.7m double leaf/wide entrance) — all 4 buildings with + door data are clean and consistent with this range, EXCEPT SampleCastle: 28 `IfcDoor` rows at + 0.5m, all named `liftdeur` (Dutch: elevator door) — real, correctly-classified doors, but they + lead to a lift shaft, not a room. Verified directly: 2 of them were rescuing actual elevator-shaft + fragments as fake compiled "rooms." Same shape of problem as the existing §STAIR-EXCLUDE pattern + — fixed with a maintained, multi-language door-name exclusion list (`liftdeur/lift/elevator/ + aufzug/fahrstuhl/hoist`), not a width cutoff (a lift door's width isn't reliably distinct from a + narrow single-leaf door's). SampleCastle 53→51 (the 2 fakes removed); other 4 buildings unchanged. +- Final counts (bim-compiler `scripts/compile_rooms.py` @ `520829907`): SampleCastle 25→**51**, + HHS 2→**6**, Clinic 113→**195**, Hospital 142→**201**, Garage unchanged at 5. + +Witness `witness_room_injection_all8.js` (W-ROOM-INJECT-ALL8) 32/32 (rerun after every pass): coverage, tag-purity (no partial RM_/≈/COMPILED tagging on any of the 5), zero `elements_meta` guid collisions, and — the one that matters most — the REAL `spacesOf()` runtime filter returns 0 placement-eligible rows for all 5 synthetic-only buildings (proves display-only rooms, door-rescued or not, can never leak into -schedule placement). Shipped bim-ootb `fable/modeller-lod400-livewire` @ `18fc9bc` (parent `a8955f2`, -pushed, `rev-list origin..HEAD`=0) + bim-compiler `fable/meshdb-livewire` @ `0cc6f8e3a` (pushed). +schedule placement). Shipped bim-ootb `fable/modeller-lod400-livewire` @ `6880a3c` (parent `a8955f2`, +pushed, `rev-list origin..HEAD`=0) + bim-compiler `fable/meshdb-livewire` @ `520829907` (pushed). + +**Known open gap, on record (not silently dropped):** HHS's WC/restroom block is still not compiled +as a room — its stall partitions are real (`WC Trennwand`) but the block's own enclosing walls aren't +in this extraction, and the stall gaps are narrower than the flood-fill's grid resolution can resolve +even at minimum safe dilation. A real fix needs either a finer grid `RES` (cost: much slower flood-fill +on large floors) or a different technique entirely (e.g. door-density-driven space partitioning) — not +attempted here; flagged as a named follow-up, not silently left looking "done." **Still NOT DONE — this task's own automation half:** wiring `compile_rooms.py` as a step INSIDE `extractIFC2DB.js` (or whichever path runs it) so a FUTURE re-extraction or a user's live IFC import gets From f87f41ed72995a9bc2eee3e26752f5fb1499bba7 Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 17:16:09 +0800 Subject: [PATCH 014/244] =?UTF-8?q?feat(compile=5Frooms):=20=C2=A7DOOR-PAR?= =?UTF-8?q?TITION=20=E2=80=94=20every=20door=20leads=20to=20a=20room,=20ev?= =?UTF-8?q?en=20where=20walls=20can't?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User's repeated, correct point: HHS has 43/39/34 real doors across 3 floors (verified directly, German-named "Türelement... Drehflügel" = swing-door elements) -- "each door must be to a room." Flood-fill (even with §DOOR-RESCUE) only ever found 4/2/0 rooms on those floors because HHS's extraction is missing the walls that would divide the floor into individual offices at all (91%+ of each floor floods as one exterior-reachable blob) -- door-rescue only widens acceptance for pockets flood-fill ALREADY finds enclosed; it can't help where nothing is enclosed to begin with. New technique for where wall-enclosure structurally cannot work: partition the storey's free space by NEAREST DOOR (multi-source BFS through real free cells, real walls still block) -- each door claims whatever space no other door reaches first, exactly the "every door leads to a room" principle stated directly, not an area/threshold proxy for it. Gate (measured, not fitted per-building): use door-partition only where flood-fill's OWN result (with door-rescue already applied) finds fewer rooms than DOOR_SHORTFALL_RATIO=0.15 of the storey's real door count -- HHS's floors find 0-11% of their door count via flood-fill; every other building's WORKING floors find 20-100%+ of theirs (Garage's sparsest working floor: 5/8=62%; Hospital's sparsest: 1/5=20%) -- the ratio sits below every failing floor and above every working one, verified directly so it never overrides an already-functioning result (Garage's 5-room floor and Hospital's 1-room floor both correctly kept their flood-fill result unchanged). Result: HHS 6->105 (36/31/29/9 per floor -- in the "tens per floor" range the user predicted from door counts alone). Clinic/Hospital/Garage/SampleCastle counts unchanged except Clinic's near-empty 'Unknown' storey (0->2, same gate correctly firing there too). predefined_type='INTERNAL_DOORPART' marks these rows for traceability; object_type='COMPILED' (what tag-purity/placement-exclusion checks key on) unchanged. Co-Authored-By: Claude Sonnet 5 --- scripts/compile_rooms.py | 109 +++++++++++++++++++++++++++++++++++---- 1 file changed, 98 insertions(+), 11 deletions(-) diff --git a/scripts/compile_rooms.py b/scripts/compile_rooms.py index a8adc4705..9474b6df4 100644 --- a/scripts/compile_rooms.py +++ b/scripts/compile_rooms.py @@ -199,6 +199,81 @@ def iy(y): return min(ny - 1, max(0, int((y - ys0) / RES))) "door_rescued": door_rescued}) return rooms +# §DOOR-PARTITION: on some real buildings (HHS confirmed) wall-enclosure flood-fill structurally +# can't find rooms — most of the floor floods as one exterior-reachable blob regardless of area/door +# filtering, because the walls that would divide individual rooms simply aren't in this extraction. +# The gate for "walls can't do this, fall back" is the DIRECT, abstract test the user named: compare +# what flood-fill (with door-rescue already applied) actually found against how many real doors this +# storey has — every door leads to a room, so a storey whose flood-fill result is a small fraction of +# its door count has failed, full stop, regardless of which building it is. Measured before picking +# the ratio: HHS's floors find 0-11% of their door count via flood-fill; every other building's +# working floors find 25-100%+ (Garage's sparsest working floor: 5 rooms / 8 doors = 62%; Hospital's +# sparsest: 1 room / 5 doors = 20%) — DOOR_SHORTFALL_RATIO=0.15 sits below every working floor's own +# ratio and above every one of HHS's, so it never overrides an already-functioning floor (verified: +# Garage's genuine 5-room floor and Hospital's genuine 1-room floor both correctly keep flood-fill's +# result, not door-partition's coarser one). Where flood-fill DOES fail this test, partition the +# storey's free space by NEAREST DOOR (multi-source BFS through real free cells, real walls still +# block) — each door claims whatever space no other door reaches first, same as how a real occupant +# would experience the floor from that door. Fully derived from real door + wall positions, still +# deterministic and reproducible; a different compile technique for where enclosure-based compiling +# structurally cannot work, not an invention. +DOOR_SHORTFALL_RATIO = 0.15 # flood-fill finding fewer rooms than this fraction of doors = has failed + +def partition_by_doors(walls, doors, stairs): + if not doors: return [] + from collections import deque + xs0 = min(w[0] - w[3] / 2 for w in walls); xs1 = max(w[0] + w[3] / 2 for w in walls) + ys0 = min(w[1] - w[4] / 2 for w in walls); ys1 = max(w[1] + w[4] / 2 for w in walls) + pad = RES * 2; xs0 -= pad; ys0 -= pad; xs1 += pad; ys1 += pad + nx = max(4, int(math.ceil((xs1 - xs0) / RES))); ny = max(4, int(math.ceil((ys1 - ys0) / RES))) + blocked = np.zeros((nx, ny), dtype=bool) + def ix(x): return min(nx - 1, max(0, int((x - xs0) / RES))) + def iy(y): return min(ny - 1, max(0, int((y - ys0) / RES))) + for cx, cy, cz, bx, by_, bz in walls: + i0, i1 = ix(cx - bx / 2), ix(cx + bx / 2); j0, j1 = iy(cy - by_ / 2), iy(cy + by_ / 2) + blocked[i0:i1 + 1, j0:j1 + 1] = True + free = ~blocked + cz = float(np.mean([w[2] for w in walls])); bz = float(np.mean([w[5] for w in walls])) + + owner = -np.ones((nx, ny), dtype=int) + q = deque() + for di, (dcx, dcy, dbx, dby) in enumerate(doors): + ci, cj = ix(dcx), iy(dcy) + seed = None + for r in range(7): # expand outward (~1.4m) to find a free cell to seed this door from + ring = [(ci + da, cj + db) for da in range(-r, r + 1) for db in range(-r, r + 1) + if max(abs(da), abs(db)) == r] + for a, b in ring: + if 0 <= a < nx and 0 <= b < ny and free[a, b] and owner[a, b] == -1: + seed = (a, b); break + if seed: break + if seed is None: continue + owner[seed] = di; q.append(seed) + + while q: + i, j = q.popleft() + for di_, dj_ in ((1,0),(-1,0),(0,1),(0,-1)): + a, b = i + di_, j + dj_ + if 0 <= a < nx and 0 <= b < ny and free[a, b] and owner[a, b] == -1: + owner[a, b] = owner[i, j]; q.append((a, b)) + + cell_area = RES * RES; plan_area = nx * ny * cell_area + rooms = [] + for di in range(len(doors)): + cells = np.argwhere(owner == di) + if len(cells) == 0: continue + area = len(cells) * cell_area + mni, mnj = cells.min(axis=0); mxi, mxj = cells.max(axis=0) + wx0 = xs0 + mni * RES; wx1 = xs0 + (mxi + 1) * RES + wy0 = ys0 + mnj * RES; wy1 = ys0 + (mxj + 1) * RES + if (wx1 - wx0) < NOISE_FLOOR_DIM or (wy1 - wy0) < NOISE_FLOOR_DIM: continue + if area > MAX_AREA_ABS or area > plan_area * MAX_AREA_FRAC: continue + if _stair_overlap_frac(wx0, wy0, wx1, wy1, stairs) >= STAIR_OVERLAP_REJECT: continue + rooms.append({"cx": (wx0 + wx1) / 2, "cy": (wy0 + wy1) / 2, "cz": cz, + "sx": wx1 - wx0, "sy": wy1 - wy0, "sz": max(bz, 2.0), "area": area, + "door_rescued": False, "door_partitioned": True}) + return rooms + def main(): if len(sys.argv) < 2: print(__doc__); return @@ -218,25 +293,36 @@ def main(): # CONTINUOUS vertical shaft anyway — so test every room pocket against the UNION of all stair # footprints by XY (not per-storey). A staircase at an XY is circulation on whatever floor it cuts. all_stairs = [s for lst in stairs_by.values() for s in lst] - total = 0; door_rescued_total = 0; allrooms = []; st_z = {} + total = 0; door_rescued_total = 0; door_partition_total = 0; allrooms = []; st_z = {} for st in sorted(by): ws = by[st] if len(ws) < 3: print(f" storey {st!r}: walls={len(ws)} (too few — skip)"); continue - rooms = flood_rooms(ws, all_stairs, doors_by.get(st, [])) + doors = doors_by.get(st, []) + rooms_flood = flood_rooms(ws, all_stairs, doors) + # §DOOR-PARTITION gate: flood-fill found far fewer rooms than this storey has real doors — + # it has structurally failed here, fall back to nearest-door partitioning (never overrides + # an already-working floor — see the ratio derivation above). + if doors and len(rooms_flood) < DOOR_SHORTFALL_RATIO * len(doors): + rooms = partition_by_doors(ws, doors, all_stairs) + method = f"door-partition (flood-fill only found {len(rooms_flood)}/{len(doors)} doors)" + else: + rooms = rooms_flood + method = "flood-fill" total += len(rooms) rescued = sum(1 for r in rooms if r.get('door_rescued')) - door_rescued_total += rescued + partitioned = sum(1 for r in rooms if r.get('door_partitioned')) + door_rescued_total += rescued; door_partition_total += partitioned st_z[st] = sum(w[2] for w in ws) / len(ws) # storey z = mean wall centre-z - print(f" storey {st!r}: walls={len(ws)} doors={len(doors_by.get(st, []))} → rooms={len(rooms)} " - f"(door_rescued={rescued}) areas={[round(r['area']) for r in rooms]}") + print(f" storey {st!r}: walls={len(ws)} doors={len(doors)} [{method}] → rooms={len(rooms)} " + f"(door_rescued={rescued} door_partitioned={partitioned}) areas={[round(r['area']) for r in rooms]}") for k, r in enumerate(rooms): r["storey"] = st; r["guid"] = f"RM_{st}_{k+1}".replace(" ", "_") # §APPROX: '≈' marks the room as compiled/approximate in the lens label. # parent_guid → a compiled storey row (created below) so the Room lens groups per floor. r["name"] = f"≈ {st} R{k+1}"; r["parent"] = st_guid.get(st) or ("STC_" + st).replace(" ", "_") allrooms.append(r) - print(f"TOTAL compiled rooms = {total} (door_rescued={door_rescued_total})") + print(f"TOTAL compiled rooms = {total} (door_rescued={door_rescued_total} door_partitioned={door_partition_total})") if not write: print("(dry run — pass --write to inject)"); return # ensure spatial_structure has bbox columns @@ -265,13 +351,14 @@ def _addcol(table, col, typ): # remove any prior compiled rooms (idempotent) c.execute("DELETE FROM spatial_structure WHERE type='IfcSpace' AND guid LIKE 'RM_%'") for r in allrooms: - # predefined_type distinguishes wall-enclosure rooms from §DOOR-RESCUE small rooms (toilets/ - # closets/utility) for traceability — object_type stays 'COMPILED' either way (the tag - # spacesOf()'s exclusion filter and every witness's tag-purity check actually key on). + # predefined_type distinguishes which compile technique found each room (wall-enclosure vs + # §DOOR-RESCUE small room vs §DOOR-PARTITION) for traceability — object_type stays 'COMPILED' + # either way (the tag spacesOf()'s exclusion filter and every tag-purity check key on). + ptype = "INTERNAL_DOORPART" if r.get("door_partitioned") else \ + "INTERNAL_SMALL" if r.get("door_rescued") else "INTERNAL" c.execute("INSERT INTO spatial_structure (guid,type,name,parent_guid,object_type,predefined_type," "center_x,center_y,center_z,size_x,size_y,size_z) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", - (r["guid"], "IfcSpace", r["name"], r["parent"], "COMPILED", - "INTERNAL_SMALL" if r.get("door_rescued") else "INTERNAL", + (r["guid"], "IfcSpace", r["name"], r["parent"], "COMPILED", ptype, r["cx"], r["cy"], r["cz"], r["sx"], r["sy"], r["sz"])) # rel_contained_in_space: elements whose XY centre falls inside a room (compiled) if not c.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='rel_contained_in_space'").fetchall(): From d7b1c8498b38276bf0c58bd371445c9d0d9599ad Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 17:17:20 +0800 Subject: [PATCH 015/244] =?UTF-8?q?docs(disc=5Fwalker):=20record=20=C2=A7D?= =?UTF-8?q?OOR-PARTITION=20pass=20=E2=80=94=20HHS=206=E2=86=92105=20rooms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DISC_Walker/ROOM_INJECTION_HYBRID.md | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md index 85c94a8ef..2ee1939c2 100644 --- a/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md +++ b/prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md @@ -187,22 +187,38 @@ fix: a real small room always has a door, a wall cavity never does. — fixed with a maintained, multi-language door-name exclusion list (`liftdeur/lift/elevator/ aufzug/fahrstuhl/hoist`), not a width cutoff (a lift door's width isn't reliably distinct from a narrow single-leaf door's). SampleCastle 53→51 (the 2 fakes removed); other 4 buildings unchanged. -- Final counts (bim-compiler `scripts/compile_rooms.py` @ `520829907`): SampleCastle 25→**51**, - HHS 2→**6**, Clinic 113→**195**, Hospital 142→**201**, Garage unchanged at 5. +- **Fifth pass — user would not let the HHS number go ("how many times must i tell you", "each door + must be to a room", "and told u not to stop until u solved it") — correctly: 6 rooms for 3 floors + averaging ~39 real doors each was never a defensible answer, it was a symptom of flood-fill's + precondition (wall enclosure) not being satisfiable from HHS's data, not a size/threshold problem + door-rescue could patch further. Built §DOOR-PARTITION: where flood-fill (with door-rescue applied) + finds far fewer rooms than the storey has real doors, partition the storey's FREE space by NEAREST + DOOR — multi-source BFS through free cells, real walls still block, each door claims whatever space + no other door reaches first (literally "every door leads to a room," not a proxy for it). Gate + (`DOOR_SHORTFALL_RATIO=0.15`) is measured, not fitted per-building: HHS's floors find 0-11% of their + door count via flood-fill; every other building's WORKING floors find 20-100%+ (Garage's sparsest: + 5/8=62%; Hospital's sparsest: 1/5=20%) — the ratio sits cleanly between "failing" and "working," + verified directly so it never overrides an already-functioning floor. `predefined_type= + 'INTERNAL_DOORPART'` marks these rows for traceability. +- Final counts (bim-compiler `scripts/compile_rooms.py` @ `f87f41ed7`): SampleCastle 25→51, + HHS 2→**105** (36/31/29/9 per floor — the "10s × floors" range the user predicted from door counts + alone), Clinic 113→197, Hospital 142→201, Garage unchanged at 5 (its one working floor's 5 real + rooms correctly preserved, not overridden). Witness `witness_room_injection_all8.js` (W-ROOM-INJECT-ALL8) 32/32 (rerun after every pass): coverage, tag-purity (no partial RM_/≈/COMPILED tagging on any of the 5), zero `elements_meta` guid collisions, and — the one that matters most — the REAL `spacesOf()` runtime filter returns 0 placement-eligible rows for -all 5 synthetic-only buildings (proves display-only rooms, door-rescued or not, can never leak into -schedule placement). Shipped bim-ootb `fable/modeller-lod400-livewire` @ `6880a3c` (parent `a8955f2`, -pushed, `rev-list origin..HEAD`=0) + bim-compiler `fable/meshdb-livewire` @ `520829907` (pushed). - -**Known open gap, on record (not silently dropped):** HHS's WC/restroom block is still not compiled -as a room — its stall partitions are real (`WC Trennwand`) but the block's own enclosing walls aren't -in this extraction, and the stall gaps are narrower than the flood-fill's grid resolution can resolve -even at minimum safe dilation. A real fix needs either a finer grid `RES` (cost: much slower flood-fill -on large floors) or a different technique entirely (e.g. door-density-driven space partitioning) — not -attempted here; flagged as a named follow-up, not silently left looking "done." +all 5 synthetic-only buildings, HHS's 105 door-partitioned rows included (proves display-only rooms, +however they were compiled, can never leak into schedule placement). Shipped bim-ootb +`fable/modeller-lod400-livewire` @ `790b069` (parent `a8955f2`, pushed, `rev-list origin..HEAD`=0) + +bim-compiler `fable/meshdb-livewire` @ `f87f41ed7` (pushed). + +**Still open, on record:** the door-partition technique gives HHS a plausible per-door room COUNT and +rough footprint, but not a true wall-bounded shape (no walls exist in the data to bound it with) — it's +a Voronoi-style nearest-door claim, one step more approximate than flood-fill's wall-enclosed rooms. +Good enough for the Outliner "Rooms" display category (still 100% excluded from schedule placement, +verified above); a real fix for HHS's actual wall/room geometry would need re-extraction with the +partition walls properly captured, not attempted here. **Still NOT DONE — this task's own automation half:** wiring `compile_rooms.py` as a step INSIDE `extractIFC2DB.js` (or whichever path runs it) so a FUTURE re-extraction or a user's live IFC import gets From e072816462be965b7761fd7b6954719f664a87b4 Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 22:40:17 +0800 Subject: [PATCH 016/244] =?UTF-8?q?docs(disc=5Fwalker):=20add=20VIEWER=5FF?= =?UTF-8?q?IND=5FPANEL=5FROOM=5FACCURACY.md=20=E2=80=94=20port=20habitabil?= =?UTF-8?q?ity+door-partition=20results=20to=20Find=20Panel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../VIEWER_FIND_PANEL_ROOM_ACCURACY.md | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 prompts/Modeller/DISC_Walker/VIEWER_FIND_PANEL_ROOM_ACCURACY.md diff --git a/prompts/Modeller/DISC_Walker/VIEWER_FIND_PANEL_ROOM_ACCURACY.md b/prompts/Modeller/DISC_Walker/VIEWER_FIND_PANEL_ROOM_ACCURACY.md new file mode 100644 index 000000000..b717ebd79 --- /dev/null +++ b/prompts/Modeller/DISC_Walker/VIEWER_FIND_PANEL_ROOM_ACCURACY.md @@ -0,0 +1,103 @@ + +# VIEWER FIND PANEL — bring the Room axis up to the Modeller's accuracy bar + +``` +# ⚠ DO NOT REMOVE +SCOPE: Viewer-only (deploy/dev/navigate_find.js). Read this before touching the Room axis / Room +Lens / _buildRoomTree() / _roomLensOn(). Read ROOM_INJECTION_HYBRID.md in full first — this task +PORTS its settled conclusions into the Viewer, it does not re-derive them. Read the log of +whichever step is IN PROGRESS below before concluding anything about its status. +ANCHORS: prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md (the Modeller-side work this ports +— §1 the hybrid rule, §6 spaceHabitable(), §3 Task 6/§DOOR-PARTITION the compile techniques) · +bim-ootb modeller/disc_walker.js `spaceHabitable()`/`_substrateEnv()` (the classifier to port, +exported on the API already) · bim-ootb scripts/compile_rooms.py (§DOOR-RESCUE/§DOOR-PARTITION, +already wired into every shipped `*_ARC.db`'s `spatial_structure`) · deploy/dev/navigate_find.js +`_buildRoomTree()`/`_roomLensOn()` (the code to fix). +``` + +## §0 — Why this exists + +This session hardened the Modeller's room compilation from a 0-6-room state to real, filtered +per-building room data (SampleHouse 3, Duplex 20, Terminal 43, SampleCastle 51, HHS 105, Clinic +197, Garage 5, Hospital 201 — full table in the session's chat/memory). The Viewer's own Room axis +(`deploy/dev/navigate_find.js` `_buildRoomTree()`) is a SEPARATE, independent query against the +same `spatial_structure` table and has NONE of that hardening. It was already flagged once as +prior art with the same unfixed gap (`ROOM_INJECTION_HYBRID.md` §3 Task 5's "Borrow-from source" +note) — this prompt is the deferred, now-due follow-up. + +## §1 — Confirmed gaps, read directly from the current code (not assumed) + +`_buildRoomTree()` (`navigate_find.js` line ~519) and `_roomLensOn()` (line ~460) both query: +```sql +SELECT guid, name FROM spatial_structure WHERE type='IfcSpace' AND center_x IS NOT NULL +``` +Checked directly against this line, three concrete, fixable gaps: + +1. **No habitability filter.** `spaceHabitable()` already exists, is exported, and is proven + (W-ROOM-HAB 5/5, W-ROOM-HAB-SH 6/6) — the Viewer's query doesn't call it at all. Today this + means Duplex's list would include R301 Roof if it weren't already stripped from the shipped + DB — but the classifier logic itself, not just today's already-stripped data, needs to be the + actual gate here, or a future re-embed silently reintroduces the same false positive this doc's + §5 already named as a live, observed bug. +2. **No real-vs-synthetic distinction shown to the user.** The query doesn't discriminate `RM_`/`≈` + rows from real ones — SampleHouse's 3 real rooms and HHS's 105 anonymous door-partitioned + pockets currently render identically in the tree/lens, with no visual or textual cue that one + is ground-truth and the other is a low-confidence approximation. A user has no way to know + which is which. +3. **No storey/floor grouping.** The tree is a single flat, alphabetically-sorted list. This was a + minor UX gap at low room counts; it is now a real usability problem — Hospital (201 rooms across + 7 levels) and HHS (105 across 3 levels + unassigned) would render as one long undifferentiated + list with no floor structure, when `spatial_structure`'s own `IfcBuildingStorey` rows (real for + Duplex/SampleHouse, `STC_`-prefixed compiled ones for the other 6 — see `compile_rooms.py`'s own + storey-row write path) already carry the grouping key via `parent_guid`. + +## §2 — Task list (work top-to-bottom, same WORK-TO-ZERO discipline as every prompts/#.md file) + +### Task 1 — Port `spaceHabitable()` into the Viewer's Room axis +**Status: NOT STARTED.** +`disc_walker.js`'s `spaceHabitable(space, env)` is exported and proven — the Viewer needs its own +`_substrateEnv()`-equivalent (bbox query already exists in a similar shape elsewhere in +`navigate_find.js`, check first before writing a new one) and a call to the SAME classifier logic +(port the function verbatim or share it — check whether the Viewer already vendors a copy of +`disc_walker.js`'s helpers before deciding, don't duplicate silently). Witness: prove on Duplex — +19-20 real rooms shown (whatever's currently in the shipped DB), zero non-habitable rows leak +through, same H1-style precision/recall proof pattern as `witness_room_hab.js`. + +### Task 2 — Visually/textually distinguish real vs synthetic rooms in the tree/lens +**Status: NOT STARTED, NOT YET SPECCED.** +Once real (SampleHouse/Duplex) and synthetic (`≈`-tagged, the other 6) rooms can appear side by +side in one Viewer session (a user might open either), the tree/lens must make the distinction +visible — e.g. a muted icon/label suffix for `≈`-tagged rows, or a confidence tier in the tooltip. +Follow whatever the Modeller Outliner's own "Rooms" category ends up doing (`ROOM_INJECTION_HYBRID.md` +Task 3, itself still NOT STARTED) rather than inventing a second, divergent visual language — if +Task 3 lands first, port ITS pattern here; if this lands first, keep the distinction simple (one +label suffix) so Task 3 can adopt it instead of the reverse. + +### Task 3 — Group the Room tree by storey +**Status: NOT STARTED.** +Query `spatial_structure`'s `IfcBuildingStorey` rows (both real and `STC_`-prefixed compiled ones) +and nest the Room axis under them via `parent_guid`, same shape as any other grouped axis this +Viewer already renders (check `_buildRoomTree`'s sibling group-builders for the existing nesting +idiom before inventing a new one). Witness: HHS should render as 4 storey groups (Level 1/2/3 + +Unassigned) totalling 105, not one flat list. + +### Task 4 — Witness the whole Room axis end-to-end, whitebox-first +**Status: NOT STARTED.** +Per project standing rule (`docs/TestArchitecture.md` §Browser Testing), the primary proof is +`§`-tagged console log output read directly, not a new Playwright spec — extend +`41-room-volume-lens.spec.js`'s existing wiring checks only if a genuine wiring/deploy regression +needs catching; value-level proof (habitability filter correctness, storey grouping counts) belongs +in a `§`-logged whitebox check, same discipline as `witness_room_hab.js`/`witness_room_hab_samplehouse.js`. + +## §3 — Guardrails (do not re-litigate) + +- **This is a Viewer-only task.** Do not touch `modeller/disc_walker.js`, `scripts/compile_rooms.py`, + or any `*_ARC.db` file for this work — those are DONE and proven on the Modeller side; this task + is a one-way PORT of their logic into the Viewer's independent query, not a shared-code refactor + unless Task 1 finds the Viewer already vendors a shared helper module (check first). +- **Read-only borrow, not a live dependency.** Copy the classifier's logic/shape into the Viewer; + do not make the Viewer import from `bim-ootb/modeller/` at runtime (different bundle/deploy + target) unless an existing shared-module pattern already does this — verify before assuming. +- **Non-invention holds here too.** The Viewer must show what the data actually says (including its + approximateness) — do not upgrade a `≈`-tagged synthetic room to look authoritative just because + it's now visible; Task 2 exists specifically to prevent that misreading. From fa9cf0a28d3166a5cc239b2c0c77c55a935f4e4f Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 22:53:15 +0800 Subject: [PATCH 017/244] =?UTF-8?q?docs(disc=5Fwalker):=20=C2=A7TE-ARC-DAT?= =?UTF-8?q?UM=20merged=20(PR=20#40)=20=E2=80=94=20entry=20point=20for=20bi?= =?UTF-8?q?m-ootb=20port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records the merge (b202eb44b) and hands off the one remaining task: port rule_frame_ref mining + walk-time zOff to bim-ootb's own disc_walker.js and terminal_rules.db — the fix is bim-compiler-only, the actual Modeller app still has the collapse bug live until this ports over. Co-Authored-By: Claude Sonnet 5 --- .../RESUME_DISC_WALKER_ENVELOPE_BOUND.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/prompts/Modeller/DISC_Walker/RESUME_DISC_WALKER_ENVELOPE_BOUND.md b/prompts/Modeller/DISC_Walker/RESUME_DISC_WALKER_ENVELOPE_BOUND.md index a3ea49c00..e64e320f3 100644 --- a/prompts/Modeller/DISC_Walker/RESUME_DISC_WALKER_ENVELOPE_BOUND.md +++ b/prompts/Modeller/DISC_Walker/RESUME_DISC_WALKER_ENVELOPE_BOUND.md @@ -1,5 +1,48 @@ # RESUME — disc_walker: area-scaled n_measured + envelope-bound placement (RouteWalker alignment) +# ▶▶▶▶▶▶▶▶▶ ENTRY POINT (2026-07-10 LATER STILL — supersedes every block below, including the one right +# under it). MANAGER-verified + MERGED. Read this first, one task remains. +# +# **§TE-ARC-DATUM: FIXED, VERIFIED, MERGED.** bim-compiler `master` @ `b202eb44b` (PR #40, squash-merged). +# Fix: `build/project_rule_mesh_binding.py` now stamps `rule_frame_ref` (per-class mean-z refs in the band +# frame); `build/disc_walker.js`'s `placeMeasured()` measures `zOff` at WALK TIME as the median per-class +# delta vs whatever substrate it's actually walking (≥3 shared classes, else legacy-fallback, logged) — +# instead of unconditionally adding the old baked constant `z_datum_offset=14.593`, which only fit the +# extraction's own site frame. Root cause was a pure translation between `Terminal_ARC.db` and +# `Terminal_extracted.db` (dx=545.6, dy=51.2, dz=14.66, 35,552/35,552 GUIDs matched, 94%+ within 0.01 of +# dz=14.66) — not a rotation, not per-storey drift. Independently re-verified from a fresh worktree before +# merge: mining-copy unchanged (744/888 placed, zOff reproduces 14.593 MAD=0.000), new T8 proof on the REAL +# shipped substrate (ELEC 860/638 ratio 1.35 + 36/19 ratio 1.89, PLB 869/739 ratio 1.18 + 100/111 ratio +# 0.90, zero envelope/z-band/hash violations), falsifier (drop `rule_frame_ref`) collapses 1865→642 exactly +# matching the old broken numbers, 3 sibling witnesses unchanged, clean merge, no regressions. Full +# verification trail: MANAGER session transcript 2026-07-10 (this doc's job isn't to re-paste it — trust the +# merge, don't re-verify from scratch). +# +# **⛔ ONE TASK LEFT: this fix is bim-compiler-ONLY. The bug is STILL LIVE in the actual Modeller app +# (bim-ootb) today** — bim-ootb has its OWN diverged copy of `disc_walker.js` (`modeller/disc_walker.js`, +# not `build/disc_walker.js`) and its OWN shipped `terminal_rules.db`, neither touched by `b202eb44b`. +# Walking the real `~/bim-ootb/modeller/Terminal_ARC.db` today still collapses (ELEC 744→390, PLB 888→252) +# exactly as before this fix, because the Modeller never got it. +# +# **Task: port `§DW-DATUM` to bim-ootb.** +# 1. Port the same two changes to bim-ootb's own copies: `project_rule_mesh_binding.py` (or wherever +# bim-ootb's rule-mining lives — check if it shares the script via a build step, or has its own copy) to +# stamp `rule_frame_ref` into bim-ootb's shipped `terminal_rules.db`; `modeller/disc_walker.js`'s +# `placeMeasured()` to measure `zOff` at walk time, mirroring `build/disc_walker.js`'s `b202eb44b` diff +# exactly (diff it first: `git show b202eb44b -- build/disc_walker.js build/project_rule_mesh_binding.py` +# in bim-compiler) — don't reimplement from the description above, port the actual code. +# 2. Re-stamp bim-ootb's shipped `terminal_rules.db` with the new `rule_frame_ref` table/columns (re-run the +# mining step against bim-ootb's own data, or copy the stamped table across if the underlying rule data +# is identical — check which is correct before assuming). +# 3. Add the T8-equivalent witness in bim-ootb (mirror `scripts/witness_terminal_nospaces.js`'s T8, or find +# bim-ootb's own equivalent witness file) proving the SHIPPED `Terminal_ARC.db` now places correctly — +# same numbers as the bim-compiler proof (ELEC ~860/638, PLB ~869/739) are a reasonable target, but +# re-measure on bim-ootb's actual data rather than assuming identical output. +# 4. Worktree-only (bim-ootb shared checkout is hook-blocked), push not merge, MANAGER re-verifies from a +# fresh worktree before merge same as this fix was. +# +# Scope: bim-ootb `modeller/` only, this specific datum-fix port — not a broader disc_walker refactor. +# # ▶▶▶▶▶▶▶▶ ENTRY POINT (2026-07-10 NIGHT, LATEST — supersedes every block below). Full context in ONE read. # THE GOAL, stated plainly by the user: get the Modeller actually working — not "verify branches" as an end # in itself. See bim-compiler memory `project_modeller_vision_lock.md` for the product NORTH STAR this serves. From bbf35d1b83523a3ba21c920a8f1b6f577035a726 Mon Sep 17 00:00:00 2001 From: red1oon Date: Fri, 10 Jul 2026 22:59:38 +0800 Subject: [PATCH 018/244] =?UTF-8?q?docs(protocol):=20Worktree=20Hygiene=20?= =?UTF-8?q?=E2=80=94=20the=20other=20LFS=20bandwidth=20drain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Committing DBs wasn't the only cost: 49 bim-ootb worktrees + a duplicate ~/Projects/bim-ootb clone were each pulling fresh 100-250MB mesh.db/geo.db LFS blobs. Real end users never touch git LFS at all (they hit the deployed static site) — 100% of the quota was dev-side worktree churn. Removed the duplicate clone (confirmed clean, 100+ commits stale) and pruned 25 safe (clean + fully-pushed) stale worktrees in bim-ootb. Protocol: check `git worktree list` before creating a new one, one clone per repo, prune on sight. Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 4f7ecb832..f16ee9dc2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -151,6 +151,28 @@ route it through LFS.** Most DB paths are already `.gitignore`d (`/library/*`, ` separate, disruptive history-rewrite decision, not taken here). This rule only stops the bleeding going forward: no NEW `.db` commits. +## Worktree Hygiene — the OTHER LFS bandwidth drain (2026-07-10, same root cause, different mechanism) +**Committing new DBs (above) was only half the bandwidth story.** The other half: bim-ootb's +`modeller/mesh.db` (120MB) + `modeller/*_geo.db` are LFS-tracked, and DOZENS of parallel worktrees +(peaked at 49 in bim-ootb, 20 in bim-compiler) each pull a fresh 100–250MB blob from GitHub the moment a +branch whose mesh/geo data isn't already in the local LFS cache gets checked out. **Real end users never +cost LFS bandwidth at all** — they hit the deployed static site (GH Pages / OCI), never `git clone` — so +100% of the quota is dev/agent-side worktree churn. A second, fully separate clone (`~/Projects/bim-ootb`, +its own independent LFS cache) doubled bandwidth on any overlapping blob and has been removed (2026-07-10, +confirmed clean + 100+ commits stale first). Rules going forward: +- **Before `git worktree add`, always run `git worktree list` first.** If a worktree already exists for + the branch/commit you need, reuse it (`cd` in, `git fetch`/`pull` if needed) — do NOT create a second one + at a different path. This applies to Agent-tool prompts too: any prompt that spawns a verification/build + agent must instruct it to check for and reuse an existing worktree before creating a fresh one. +- **One local clone per repo, no exceptions** — never `git clone` a second copy of `bim-ootb` or + `bim-compiler` alongside the primary checkout. +- **Prune on sight, not on a schedule.** When a worktree's branch is fully pushed (`git rev-list --count + origin/..` = 0) AND clean (`git status --short` empty), remove it + (`git worktree remove `) — don't let dozens accumulate "just in case." A worktree with unpushed + commits or uncommitted changes is NOT safe to prune — leave those alone, they're someone's in-progress work. +- **`.claude/worktrees/agent-*` are harness-managed** — never manually `git worktree remove` these; they're + the Claude Code tool's own isolation mechanism, not dev-created clutter. + ## Sacred Files (edit with extreme care) - `deploy/live/*` — PRODUCTION snapshot, never edit (see PRIME RULE) - `migration/*.sql` — append only, never modify existing migrations. EXEMPT: `DV__rules.sql` — regenerated From edb6a9a30d7d9af7d7c7a3bdcb8d510330e2b0ff Mon Sep 17 00:00:00 2001 From: red1oon Date: Sat, 11 Jul 2026 00:04:06 +0800 Subject: [PATCH 019/244] =?UTF-8?q?docs(protocol):=20LFS=20QUOTA=20EXHAUST?= =?UTF-8?q?ED=20=E2=80=94=20hard=20block,=20all-local,=20no=20exceptions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub confirmed 0 bandwidth budget remaining, not just approaching the cap. Escalates the earlier reduce-usage guidance to a hard block: no pushing any commit that touches LFS-tracked content, no fetching/checking-out branches whose LFS blobs aren't already locally cached, until the user confirms the quota has reset. Applies to every Agent-tool prompt that touches git, not just this session. Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index f16ee9dc2..a50f71bd1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -129,6 +129,28 @@ Before ending, update PROGRESS.md with: - **Pre-Flight Citation:** Before code changes, cite the spec: `// Implementing BBC.md §X.Y — Witness: W-NAME` - **Traceability:** Check `TestArchitecture.md` §Traceability Matrix before and after changes +## ⛔ LFS QUOTA EXHAUSTED — HARD BLOCK, ALL LOCAL, NO EXCEPTIONS (2026-07-11) +**GitHub confirmed the LFS bandwidth quota is now at 0 — not "approaching the cap," actually exhausted.** +This supersedes the softer "reduce usage" framing of the two sections below (still read them for the +mechanism/cleanup already done — this section is the escalation on top). Any git operation that needs to +push or fetch an LFS object against the remote will now fail or risk further billing exposure until the +user confirms the quota has reset or been topped up — **do not assume, ask before any LFS-touching push/ +fetch.** Until then: +- **NEVER push a commit whose diff touches ANY already-LFS-tracked file's content** — not just new `.db` + additions (already banned below), but ANY change to an existing tracked binary (a rules-DB update, a + regenerated mesh/geo file, anything matching `.gitattributes`' `filter=lfs` patterns). If a task needs to + change one, commit it LOCALLY only and stop there — do not `git push` that commit. Say so explicitly when + reporting the work, don't push-and-hope. +- **NEVER create a worktree/checkout a branch whose LFS blobs aren't already in the local cache** + (`.git/lfs/objects`) — that triggers a fetch against the exhausted quota. Before `git worktree add` or + `git checkout` of a branch you haven't already worked with locally, check whether its LFS content is + already cached; if unsure, ask rather than risk a failed/quota-consuming fetch. Branches already checked + out in an existing worktree are safe to keep using (their blobs are already local). +- **This applies to every Agent-tool prompt that touches git** — spawned verification/build agents must be + told explicitly not to push LFS-touching commits and not to fetch new LFS content, same as this session. +- **Non-LFS-tracked files (code, docs, most everything) are unaffected** — normal commit/push continues for + anything that isn't itself an LFS-tracked binary or doesn't modify one. + ## DB Storage Policy — GitHub LFS bandwidth (2026-07-10) **GitHub emailed a 10GB/month LFS bandwidth-cap warning.** `.gitattributes` has a blanket `*.db filter=lfs` rule and 59+ `.db` files are already LFS-tracked (`deploy/buildings/*_extracted.db`/`*_library.db`, From 7c01f4341567389f1afe760529b424960807f997 Mon Sep 17 00:00:00 2001 From: red1oon Date: Sat, 11 Jul 2026 00:08:27 +0800 Subject: [PATCH 020/244] =?UTF-8?q?docs(disc=5Fwalker):=20add=20COMPILE=5F?= =?UTF-8?q?ROOMS=5FTYPE=5FINFERENCE.md=20=E2=80=94=20room-function=20guess?= =?UTF-8?q?ing=20where=20no=20IfcSpace=20ever=20named=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../COMPILE_ROOMS_TYPE_INFERENCE.md | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 prompts/Modeller/DISC_Walker/COMPILE_ROOMS_TYPE_INFERENCE.md diff --git a/prompts/Modeller/DISC_Walker/COMPILE_ROOMS_TYPE_INFERENCE.md b/prompts/Modeller/DISC_Walker/COMPILE_ROOMS_TYPE_INFERENCE.md new file mode 100644 index 000000000..9607e4bdd --- /dev/null +++ b/prompts/Modeller/DISC_Walker/COMPILE_ROOMS_TYPE_INFERENCE.md @@ -0,0 +1,148 @@ + +# COMPILE_ROOMS TYPE INFERENCE — guessing room FUNCTION where no IfcSpace ever named it + +``` +# ⚠ DO NOT REMOVE +SCOPE: scripts/compile_rooms.py only. Read ROOM_INJECTION_HYBRID.md in full first — this task +extends the room-COUNT work already done there (§DOOR-RESCUE, §DOOR-PARTITION) into room-TYPE +guessing, a strictly harder, lower-confidence problem. Read the log of whichever step is IN +PROGRESS below before concluding anything about its status — a "confidence" number is not evidence +until it's been checked against real ground truth (§2 Task 0). +ANCHORS: prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md (§DOOR-RESCUE/§DOOR-PARTITION, the +compile techniques this extends) · scripts/compile_rooms.py (the file to modify) · +scripts/stamp_space_longnames.py (why SampleHouse/Duplex alone HAVE real types — read this before +assuming any inference technique below is needed for them; it isn't) · Duplex_ARC.db / SampleHouse +_ARC.db `spatial_structure.object_type` (the ONLY real, ground-truth room-type data in this repo — +the calibration source for every heuristic below, not a textbook number). +``` + +## §0 — Why this exists, and the hard limit it must respect + +This session found: SampleHouse (3 rooms) and Duplex (20 rooms) show real room types (Kitchen, +Bathroom 1, Foyer, ...) because a human typed that into the source IFC's `IfcSpace.Name` or +`LongName` when authoring the model — extracted verbatim, never computed +(`scripts/stamp_space_longnames.py`'s own doc-comment: "NON-INVENT: pure guid→LongName copy"). +The other 6 buildings (Terminal, SampleCastle, HHS, Clinic, Garage, Hospital) have ZERO real +`IfcSpace` rows to read a type from at all — every room compiled for them this session +(§DOOR-RESCUE/§DOOR-PARTITION) is pure geometry (walls, doors), which can prove a room EXISTS but +carries no information about what it's FOR. + +**The hard limit, stated up front so no task below quietly crosses it:** there is no way to +EXTRACT a type that was never authored. Everything in §1 is INFERENCE — a confidence-scored guess +from indirect real-data clues, not equivalent to Duplex/SampleHouse's ground truth, and must never +be presented, tagged, or consumed as if it were. This is not a loophole in the PRIME RULE +(EXTRACT OR COMPILE ONLY, never invent) — it's a new, explicitly-labeled THIRD category alongside +"real" and "synthetic-anonymous": **"synthetic-inferred,"** confidence-scored, never authoritative, +never feeding placement (same guardrail `spacesOf()` already enforces for every compiled room). + +## §1 — Candidate inference signals (evidence found this session, none yet built) + +Ranked by how directly they're grounded in real, checkable data already in these buildings' ARC.db: + +1. **Wall/door NAME keyword mining (strongest signal, already proven adjacent).** HHS's walls + carry real descriptive names in German: `WC Trennwand 5.0` (WC partition wall), `Basic Wall:MW + 11.5` (masonry, 115mm), `Basic Wall:STB 30.0` (reinforced concrete, 300mm). A room whose + enclosing/nearby walls match a maintained, multi-language keyword list (WC/Toilet/Bad/Damen/ + Herren/Behinderten, Electrical/Elektro, Plant/Technik, Storage/Lager, Meeting/Besprechung, ...) + can be labeled with that keyword as an INFERRED type. This is the same shape of technique as + `§DOOR-NOT-ROOM`'s `liftdeur` name-match, extended from "exclude" to "label." Confidence: HIGH + when a keyword hits (the wall's own name is real, extracted data) — but coverage will be LOW + (most walls/doors in these buildings are generic "Basic Wall"/"Türelement," no functional hint). + +2. **Door leaf-count/glazing from the door's own name.** HHS's doors encode `1-flg`/`2-flg` + (single/double leaf) and `Glas` (glazed) in their names. A wide glazed double-leaf door often + marks a more public space (lobby, meeting room, reception) vs. a narrow single-leaf door (private + office, WC). Weaker, more speculative signal than #1 — treat as a tie-breaker between candidate + types, never a sole classifier. + +3. **Area/shape calibrated against Duplex's OWN real ground truth — not a textbook number.** + Duplex already has 20 real, typed rooms with known areas (e.g. `Bathroom 1`/`Bathroom 2` sizes, + `Bedroom 1`/`Bedroom 2` sizes, `Kitchen`, `Living Room`). Build the size DISTRIBUTION per real + type from this data (the only real, in-repo, non-invented calibration source) and use it as a + Bayesian-ish prior for guessing a synthetic room's likely type band from its compiled area alone + — e.g. "a 3-5 m² compiled room is more consistent with Duplex's real bathrooms (X m²) than its + real bedrooms (Y m²)." Must be calibrated FROM this repo's real data, never from an external/ + generic "typical room sizes" table — that would reintroduce exactly the fitted-band mistake + already corrected once this session (`ROOM_INJECTION_HYBRID.md`'s door-rescue "abstract rule" + pass). Confidence: LOW-MEDIUM, coverage HIGH (every compiled room has an area) — the opposite + trade-off from #1. + +4. **Repetition/position pattern.** A block of near-identical-shaped rooms repeating along a + corridor (visible already in HHS Level 2's door-partition areas — several ~30 m² cells in a row) + is architecturally very likely a repeated single-function type (offices, patient rooms, cells) — + even without knowing WHICH function, flagging "these N rooms are one repeated type" is itself + useful, checkable information (cluster by shape similarity, not guessed per-room). The single + largest room per storey is very often an open-plan/hall/ward — a weak, storey-relative signal + only, never an absolute size rule (a "largest room" on a tiny floor isn't necessarily a hall). + +5. **Wall-thickness class.** HHS's own wall names already separate structural (`STB 30.0`, 300mm + reinforced concrete) from partition (`MW 11.5` 115mm masonry, `WC Trennwand 5.0` 50mm stud) — + thickness alone (even WITHOUT readable names, on buildings whose walls aren't descriptively + named) can distinguish a building's primary structural grid from subdivided office cells. Useful + as a structural-zone signal, not a room-function signal on its own — pair with #1/#3. + +**Explicitly rejected this session, don't re-investigate without new evidence:** MEP/sanitary +fixture presence (`IfcFlowTerminal` etc.) as a toilet signal — checked, unusable as a GENERAL +signal because the ARC-only discipline strip (`b93ca13`) already removed this data from 4 of 5 +synthetic buildings; only Clinic still has it. If Clinic-specific fixture-based labeling is ever +wanted, scope it as a Clinic-only enhancement, not a shared technique — don't build a general path +that silently no-ops on 4 of 5 buildings. + +## §2 — Task list (work top-to-bottom, same WORK-TO-ZERO discipline as every prompts/#.md file) + +### Task 0 — Build the calibration/validation harness FIRST, before any inference code +**Status: NOT STARTED.** Every technique in §1 needs a ground-truth check before shipping, same +discipline as `witness_room_hab.js`'s H1/H2 pattern: extract Duplex's 20 real (name, area, guid) +triples as the ONLY known-correct answer key in this repo, and build a witness harness that can +score ANY candidate inference function's precision/recall against it BEFORE that function is +trusted on a real synthetic building. No inference technique below ships without running through +this harness first. + +### Task 1 — Wall/door keyword-based type inference (§1 item 1) +**Status: NOT STARTED.** Build the maintained, multi-language keyword list (grow only by review +against real wall/door names actually found in these 8 buildings, same discipline as +`NONHAB_TYPES`/`NON_ROOM_DOOR_NAMES` — never guessed ahead of evidence). Attach as +`inferred_type`+`inferred_confidence` fields (new columns, do not overwrite `object_type` — +that column means "real, extracted" everywhere else in this codebase and must keep meaning that). +Witness: run against HHS/SampleCastle/Clinic/Garage/Hospital, report keyword-hit coverage % +(expect LOW — most walls won't match) and spot-check every hit by hand against the wall's actual +position (does a "WC"-keyword room actually sit where the WC cluster is?). + +### Task 2 — Area-band inference calibrated on Duplex's real ground truth (§1 item 3) +**Status: NOT STARTED.** Depends on Task 0's harness. Build the real size distribution from +Duplex's 20 typed rooms, use it as a soft classifier for synthetic rooms' `inferred_type` (multi- +candidate with confidence, e.g. "60% bathroom-band / 40% storage-band," never a single forced +label when the bands overlap). Witness: leave-one-out cross-validation on Duplex's own 20 rooms +first (hide each real room's type, see if the area-band alone would have guessed correctly) — +report the honest accuracy number, however low, before trusting it on synthetic buildings. + +### Task 3 — Repetition/shape-clustering signal (§1 item 4) +**Status: NOT STARTED, NOT YET SPECCED.** Needs a shape-similarity metric (bbox aspect ratio + +area, tolerance TBD) and a per-storey clustering step. Lower priority than Tasks 1-2 — this signal +answers "are these N rooms the same type as each other," not "what type," so it's only useful +paired with #1 or #2 assigning the actual label to the cluster. + +### Task 4 — Wire `inferred_type`/`inferred_confidence` into the Modeller Outliner + Find Panel +**Status: BLOCKED on `ROOM_INJECTION_HYBRID.md` Task 3 (Modeller "Rooms" outliner category, still +NOT STARTED) and `VIEWER_FIND_PANEL_ROOM_ACCURACY.md` Task 2 (real-vs-synthetic display, still NOT +STARTED).** Do not build display wiring before those land — an inferred-type label needs the SAME +"this is a guess, not ground truth" visual treatment those tasks are already speccing for +synthetic-vs-real; a second, divergent display convention here would fragment the UX. + +## §3 — Guardrails (do not re-litigate) + +- **`object_type` keeps its existing meaning across this whole codebase: "real, extracted."** Never + write an inferred guess into it. Use new columns (`inferred_type`, `inferred_confidence`) so + every existing consumer (`spacesOf()`, `spaceHabitable()`, every witness's tag-purity check) + stays correct without modification. +- **Never feeds placement.** Same enforcement point as `RM_`/`≈`/`COMPILED` — `spacesOf()`'s + exclusion already keeps every compiled room out of schedule placement regardless of any inferred + type; this task must not create a second path that lets an "inferred: Kitchen" room get treated + as real by anything downstream. +- **Calibrate from THIS repo's real data only (Duplex's 20 rooms), never a generic/textbook room- + size table.** That mistake was already made and corrected once this session (the door-rescue + area-band that turned out to be fitted to observed data rather than a principle) — don't reopen + it here by importing external "typical room area" numbers instead of this repo's own ground truth. +- **Report honest accuracy, including low numbers.** A technique that scores 30% precision on + Task 0's harness is still worth shipping AS A LOW-CONFIDENCE HINT if labeled honestly — the + failure mode to avoid is silently rounding a weak signal up to look authoritative. From 3c28ec1b16d9b700af17e3e4785000eb8bddb9f8 Mon Sep 17 00:00:00 2001 From: red1oon Date: Sat, 11 Jul 2026 00:23:35 +0800 Subject: [PATCH 021/244] =?UTF-8?q?fix(disc=5Fwalker):=20correct=20VIEWER?= =?UTF-8?q?=5FFIND=5FPANEL=5FROOM=5FACCURACY.md=20=E2=80=94=20right=20repo?= =?UTF-8?q?,=20right=20file,=20new=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User caught it: the doc anchored on bim-compiler/deploy/dev/navigate_find.js, which turns out to be a STALE, unmaintained fork (2859-line diff vs the live copy, no recent commits since this repo's initial migration). The actual, actively-developed Viewer code is bim-ootb/viewer/ navigate_find.js (3640 lines, commits as recent as pill-drawer/HBA work) -- same repo as Modeller's disc_walker.js, removing the cross-repo constraint the original doc assumed. Checked the live code directly and found it's further along than assumed (storey grouping + a Storey/Type toggle already exist) but also found a new, concrete bug: the Type toggle's `object_type || predefined_type` fallback never reaches predefined_type for compiled rooms, since object_type='COMPILED' is always truthy -- masks the INTERNAL/INTERNAL_SMALL/INTERNAL_DOORPART distinction compile_rooms.py now writes. Biggest correction: confirmed viewer/import_db_builder.js (the live "drop your own IFC" schema builder) never creates a spatial_structure table at all -- a user's own imported building gets ZERO room data today, real or synthetic. Scoped as its own Task 5, separate from the display-only fixes, since fixing it means porting compile_rooms.py's algorithm from Python to JS (no Python runs in a browser) -- not a quick win. Co-Authored-By: Claude Sonnet 5 --- .../VIEWER_FIND_PANEL_ROOM_ACCURACY.md | 181 ++++++++++-------- 1 file changed, 103 insertions(+), 78 deletions(-) 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 b717ebd79..70e450bd5 100644 --- a/prompts/Modeller/DISC_Walker/VIEWER_FIND_PANEL_ROOM_ACCURACY.md +++ b/prompts/Modeller/DISC_Walker/VIEWER_FIND_PANEL_ROOM_ACCURACY.md @@ -3,101 +3,126 @@ ``` # ⚠ DO NOT REMOVE -SCOPE: Viewer-only (deploy/dev/navigate_find.js). Read this before touching the Room axis / Room -Lens / _buildRoomTree() / _roomLensOn(). Read ROOM_INJECTION_HYBRID.md in full first — this task -PORTS its settled conclusions into the Viewer, it does not re-derive them. Read the log of -whichever step is IN PROGRESS below before concluding anything about its status. -ANCHORS: prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md (the Modeller-side work this ports -— §1 the hybrid rule, §6 spaceHabitable(), §3 Task 6/§DOOR-PARTITION the compile techniques) · -bim-ootb modeller/disc_walker.js `spaceHabitable()`/`_substrateEnv()` (the classifier to port, -exported on the API already) · bim-ootb scripts/compile_rooms.py (§DOOR-RESCUE/§DOOR-PARTITION, -already wired into every shipped `*_ARC.db`'s `spatial_structure`) · deploy/dev/navigate_find.js -`_buildRoomTree()`/`_roomLensOn()` (the code to fix). +SCOPE: bim-ootb `viewer/navigate_find.js` — the LIVE, actively-developed copy. ⚠ CORRECTED +2026-07-11: this doc originally anchored on `bim-compiler/deploy/dev/navigate_find.js` — that copy +is STALE/abandoned (no commits since the repo's initial migration, 1984 lines vs the live copy's +3640, a 2859-line diff). Do NOT edit the bim-compiler copy for this task; it is not what ships. +Modeller (`bim-ootb/modeller/disc_walker.js`) and Viewer (`bim-ootb/viewer/navigate_find.js`) are +BOTH in bim-ootb — same repo, same deploy target. This removes the earlier assumed cross-repo +constraint: a real shared JS module between them is now a live option, not just a copy-and-sync one. +Read this before touching the Room axis / Room Lens / `_buildRoomTree()` / `_roomLensOn()` / +`_allRoomVolumes()`. Read `ROOM_INJECTION_HYBRID.md` in full first — this task PORTS its settled +conclusions into the Viewer, it does not re-derive them. +ANCHORS: prompts/Modeller/DISC_Walker/ROOM_INJECTION_HYBRID.md (the Modeller-side work this ports) +· bim-ootb modeller/disc_walker.js `spaceHabitable()`/`_substrateEnv()` (the classifier to share/ +port, exported on the API already) · bim-ootb scripts/compile_rooms.py (§DOOR-RESCUE/§DOOR-PARTITION, +Python, offline-only — see §2 Task 5 for why this matters) · bim-ootb viewer/navigate_find.js +`_buildRoomTree()` (line ~1698) / `_roomLensOn()` (line ~1525) / `_allRoomVolumes()` (line ~1494, +the actual query to fix) · bim-ootb viewer/import_db_builder.js (the LIVE user-IFC-import schema +builder — confirmed it never creates `spatial_structure` at all, see §2 Task 5). ``` -## §0 — Why this exists +## §0 — Why this exists, and what's ALREADY better than assumed here -This session hardened the Modeller's room compilation from a 0-6-room state to real, filtered -per-building room data (SampleHouse 3, Duplex 20, Terminal 43, SampleCastle 51, HHS 105, Clinic -197, Garage 5, Hospital 201 — full table in the session's chat/memory). The Viewer's own Room axis -(`deploy/dev/navigate_find.js` `_buildRoomTree()`) is a SEPARATE, independent query against the -same `spatial_structure` table and has NONE of that hardening. It was already flagged once as -prior art with the same unfixed gap (`ROOM_INJECTION_HYBRID.md` §3 Task 5's "Borrow-from source" -note) — this prompt is the deferred, now-due follow-up. +This session hardened the shipped residents' room compilation from a 0-6-room state to real, +filtered per-building room data (SampleHouse 3, Duplex 20, Terminal 43, SampleCastle 51, HHS 105, +Clinic 197, Garage 5, Hospital 201). Checking the Viewer's ACTUAL live code (not the stale copy +this doc originally pointed at) found it's further along than assumed: `_buildRoomTree()` already +groups rooms by **Storey** (via `parent_guid`) with a **Storey/Type** sub-toggle, and the Type view +already reads `object_type`/`predefined_type` with a `(untyped)` non-invent fallback. So the storey- +grouping gap this doc originally named (old Task 3) is DONE — don't redo it. What's still genuinely +missing, confirmed directly against the live query functions: -## §1 — Confirmed gaps, read directly from the current code (not assumed) +## §1 — Confirmed gaps, read directly from the LIVE code (not assumed) -`_buildRoomTree()` (`navigate_find.js` line ~519) and `_roomLensOn()` (line ~460) both query: +`_allRoomVolumes()` (line ~1494) — the function `_roomLensOn()` actually calls to draw every room — +still queries with zero filtering: ```sql -SELECT guid, name FROM spatial_structure WHERE type='IfcSpace' AND center_x IS NOT NULL +SELECT guid, name, center_x, center_y, center_z, size_x, size_y, size_z +FROM spatial_structure WHERE type='IfcSpace' AND center_x IS NOT NULL AND size_x IS NOT NULL ``` -Checked directly against this line, three concrete, fixable gaps: +Three concrete, fixable gaps: 1. **No habitability filter.** `spaceHabitable()` already exists, is exported, and is proven - (W-ROOM-HAB 5/5, W-ROOM-HAB-SH 6/6) — the Viewer's query doesn't call it at all. Today this - means Duplex's list would include R301 Roof if it weren't already stripped from the shipped - DB — but the classifier logic itself, not just today's already-stripped data, needs to be the - actual gate here, or a future re-embed silently reintroduces the same false positive this doc's - §5 already named as a live, observed bug. -2. **No real-vs-synthetic distinction shown to the user.** The query doesn't discriminate `RM_`/`≈` - rows from real ones — SampleHouse's 3 real rooms and HHS's 105 anonymous door-partitioned - pockets currently render identically in the tree/lens, with no visual or textual cue that one - is ground-truth and the other is a low-confidence approximation. A user has no way to know - which is which. -3. **No storey/floor grouping.** The tree is a single flat, alphabetically-sorted list. This was a - minor UX gap at low room counts; it is now a real usability problem — Hospital (201 rooms across - 7 levels) and HHS (105 across 3 levels + unassigned) would render as one long undifferentiated - list with no floor structure, when `spatial_structure`'s own `IfcBuildingStorey` rows (real for - Duplex/SampleHouse, `STC_`-prefixed compiled ones for the other 6 — see `compile_rooms.py`'s own - storey-row write path) already carry the grouping key via `parent_guid`. + (W-ROOM-HAB 5/5, W-ROOM-HAB-SH 6/6) — `_allRoomVolumes()` doesn't call it. A future re-embed + that reintroduces a Roof/lift-shaft space would render it as a normal room, same false-positive + risk this doc's Modeller-side work already fixed once for real data. +2. **No real-vs-synthetic distinction — and the existing Type toggle has a specific bug that + masks it.** `_buildRoomTree()`'s Type grouping does `r[3] || r[4] || '(untyped)'` — + `object_type` (`r[3]`) first, `predefined_type` (`r[4]`) only as fallback. But EVERY compiled + (synthetic) room has `object_type='COMPILED'` — a non-empty string — so `predefined_type` + (`INTERNAL` / `INTERNAL_SMALL` / `INTERNAL_DOORPART`, the actual useful distinction + `compile_rooms.py` now writes) is NEVER reached. Result: switching to "Type" view dumps every + synthetic room from EVERY building into one "COMPILED" bucket, hiding exactly the real-vs- + approximate/door-rescued/door-partitioned distinction that exists in the data today. This is a + one-line-shaped fix (special-case `object_type==='COMPILED'` to fall through to + `predefined_type`) sitting on top of an already-good grouping mechanism — don't rebuild the + toggle, just fix its field-precedence for this one case. +3. **`elements_meta`/tag-purity checks aside, no visual cue that "COMPILED" means "approximate."** + Even once (2) is fixed, a user seeing "INTERNAL_DOORPART" as a group name has no idea that means + "nearest-door partition, no real walls found" — needs a plain-language label/tooltip, not the + internal tag verbatim. ## §2 — Task list (work top-to-bottom, same WORK-TO-ZERO discipline as every prompts/#.md file) -### Task 1 — Port `spaceHabitable()` into the Viewer's Room axis +### Task 1 — Port `spaceHabitable()` into `_allRoomVolumes()` **Status: NOT STARTED.** -`disc_walker.js`'s `spaceHabitable(space, env)` is exported and proven — the Viewer needs its own -`_substrateEnv()`-equivalent (bbox query already exists in a similar shape elsewhere in -`navigate_find.js`, check first before writing a new one) and a call to the SAME classifier logic -(port the function verbatim or share it — check whether the Viewer already vendors a copy of -`disc_walker.js`'s helpers before deciding, don't duplicate silently). Witness: prove on Duplex — -19-20 real rooms shown (whatever's currently in the shipped DB), zero non-habitable rows leak -through, same H1-style precision/recall proof pattern as `witness_room_hab.js`. +`disc_walker.js`'s `spaceHabitable(space, env)` is exported and proven. Since Modeller and Viewer +are now confirmed to be the SAME repo/deploy target (see header correction), evaluate a real shared +module (e.g. a small `room_habitability.js` both `disc_walker.js` and `navigate_find.js` load) before +defaulting to a copy-paste port — check the build/bundle setup first (are these files loaded via +plain `