Skip to content

Red Pill β€” Design Buildings From Grammar, Not From Scratch

← Back to the User Guide Β· Home

The Red Pill: You never start from a blank canvas. You start from a building you trust, extract its grammar, and design by interrupting its replay.

Status: Active development (S266–S270+) Entry point: Tap the Red Pill (capsule icon) in the viewer's icon pill Supersedes: BIM_Designer_Browser.md (viewer layer β€” still the foundation) Full spec: NEW_FROM_REFERENCE.md (1800-line technical spec)


1. The Big Idea

Every BIM modeller starts from a blank canvas or a parametric script. Neither reuses the spatial intelligence already embedded in real buildings.

The BIM Compiler already solves the inverse problem: IFC β†’ SQLite β†’ verified reconstruction across 21 buildings and 9 verification gates. The grammar is already extracted β€” BOM abstract sets, bay ratios, floor-to-floor heights, MEP densities.

Red Pill is the forward path: grammar β†’ new design.

Reference Building (IFC.db)
       β”‚
       β–Ό
  Grammar Extraction (BOM, bays, heights)
       β”‚
       β–Ό
  2D Grid (seeded from grammar)
       β”‚
       β–Ό
  4D Replay (construction phases, one discipline at a time)
       β”‚
       β–Ό  ← USER INTERRUPTS HERE: drag grid lines, alter proportions
       β”‚
       β–Ό
  NewIFC.db (materialized view, deletable, regenerable)

The original IFC.db never changes. The design is an event log applied to the grammar.


2. What Makes This Different

Red Pill Revit Grasshopper SketchUp
Starting point Real building grammar Blank template Parametric script Blank canvas
Constraint system Grid + BOM hierarchy Full parametric solver Node graph None
Server None (browser-only) Desktop (60GB) Desktop plugin Cloud/desktop
Round-trip Verified (Rosetta Stone gates) One-way IFC export No IFC Lossy IFC
Design method Interrupt a replay Place families Wire nodes Free draw
Deterministic Yes (same log β†’ same result) No Depends No

3. Architecture β€” Two Layers, One Direction

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  DESIGNER LAYER (new)                     β”‚
β”‚  Red Pill β†’ grammar β†’ grid β†’ replay β†’ commands β†’ save    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚ reads (never writes back)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              COMPILER LAYER (existing, unchanged)         β”‚
β”‚  IFC β†’ SQLite β†’ streaming viewer β†’ grid overlay β†’ ERP    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Rule: The compiler never imports designer code. The designer imports the compiler's APIs. The compiler does not know the designer exists.


4. Code Map β€” Where Things Live

Core Files (deploy/dev/)

File Lines Role
doc_canvas.js ~2090 Orchestrator. Red Pill UX, Gantt stepper (Next), grid rendering, timeline, design save/open. Thin caller β€” delegates state and recompose to modules.
grid_state.js ~335 Grid state. Owns all position/label/original tracking. Single source of truth for grid lines.
grid_recompose.js ~682 Recompose bridge. Engine lifecycle, bbox swizzle, command dispatch, BOM L1 recompose, delta tracking.
grid_kinematics.js ~670 Engine. Pure-math grid recomposition. No Three.js, no DB, no DOM. Takes element positions + grid deltas, returns transform commands.
bom_extract.js ~350 Grammar extractor. Walks m_bom table, produces envelope + storey + phase hierarchy. The JS port of 13 Java classes β€” one function, one query, one pass.
kernel_ops.js ~400 Event log. Every user action as a JSON command. Undo/redo by replay.
grid_overlay.js ~600 Grid rendering. AABBCC labels, bubble markers, bay dimensions, drag handles.
grid_dims.js ~300 Grid detection. Extracts structural grid from element positions.
measure.js ~1200 Clash detection + spatial index. R-tree, proximity checks, clearance rules.
scene.js ~3000 Viewer core. BatchedMesh rendering, DLOD streaming, 4D Time Machine.

Test Files (deploy/dev/tests/)

File Tests What It Proves
test_doc_canvas.js 73 Doc canvas UX, grid ops, BatchedMesh, BUG-1/BUG-4, Save/Open, grid guards
test_grid_kinematics.js 98 Engine classification: ATTACH/SPAN/EDGE/ROOF, bay-proportional, cascades
test_s268_recompose.js 63 Attach-map recompose + bay-proportional integration
test_grid_modules.js 114 Grid detection, overlay, drag, label generation

Total: 348 tests, all whitebox Β§-tagged.


5. The Grid Kinematics Engine

The engine is the mathematical core. It answers three questions per element:

  1. WHAT is attached? β†’ relation type (ATTACH, SPAN, EDGE_LEFT/RIGHT, ROOF_EAVE, ROOF_FLAT, INTERIOR)
  2. HOW may it move? β†’ action (TRANSLATE, SCALE, ROOF_VERTICES, ROOF_LIFT)
  3. WHAT cascades? β†’ wall height scales when roof lifts

Relation Types

Relation When Action
ATTACH Element centerline within 0.5m of grid TRANSLATE (rigid move)
SPAN Grid line inside element body SCALE (stretch)
EDGE_RIGHT Element's right edge at grid line SCALE or TRANSLATE depending on drag direction
EDGE_LEFT Element's left edge at grid line SCALE or TRANSLATE depending on drag direction
ROOF_EAVE Roof eave vertices near grid ROOF_VERTICES (eave moves, ridge fixed, linear interpolation)
ROOF_FLAT Flat roof slab spanning grid SCALE (like slab)
ROOF_LIFT Roof on Y-axis (ceiling) grid ROOF_LIFT (rigid vertical translate)
INTERIOR Not attached to any grid Bay-proportional repositioning

Design Invariants

  1. dragGrid() is pure: (positions, delta) β†’ commands. Never mutates input.
  2. Engine is stateless re kernel_ops. Parent replays log on load.
  3. Only attach-map elements get commands. Others via bay-proportional.
  4. O(K) per drag where K = attached count. Pre-indexed by grid ID.
  5. Coordinate swizzle: IFC bbox (Z-up) β†’ Three.js (Y-up) happens in the caller, not the engine.

Coordinate Transform

IFC (Z-up)          Three.js (Y-up)
  X (width)    β†’      X (same)
  Y (depth)    β†’      Z (into screen, negated)
  Z (height)   β†’      Y (up)

bbox_x  β†’  bboxX (same)
bbox_y  β†’  bboxZ (IFC depth β†’ Three Z)
bbox_z  β†’  bboxY (IFC height β†’ Three Y)

This swizzle is applied in _collectElementData() (grid_recompose.js). The engine operates entirely in Three.js coordinates.


6. The UX Flow β€” Red Pill Mode

Entry

Tap the Red Pill icon β†’ canvas clears β†’ building envelope appears as ghost wireframe.

Doc Pill Icons

# Icon Action
1 Home Return to viewer mode
2 Grid # Toggle 2D grid visibility (show/hide all RS-created lines)
3 Clock Time Machine replay
4 Next β€Ί Full BOM cascade β€” materialize entire building
5 Folder Load saved design from IndexedDB
6 Disk Save event log to IndexedDB
7 UBBL Compliance check (planned)
8 Scissors Section cut β€” same tool used by RS wall-click

6.1 Next = Full BOM Cascade

Single press materializes the entire BOM tree β€” all levels, all children, complete building. No level-by-level stepping (that was a development scaffold, retained as Shift+Next for debugging).

The BOM is a recipe. Next runs the recipe. If the output is wrong, the recipe is wrong.

  • materializeBomLevel() called recursively from root β†’ full tree
  • Β§BOM_NEXT logs full tree with element count
  • If cascade error: status bar shows "BOM cascade error β€” see F12 logs"
  • No fallback. If it breaks, fix the data. Hardened, no mercy.
  • Envelope completeness: roof covering, walls, floor slab β€” all outer elements present

6.2 RS Navigation β€” Click to Create Grid Lines

No automatic grid lines. Grid lines emerge from user interaction via Rosetta Stone (RS). Every line has RS provenance from creation β€” never heuristic.

Click target What happens
Floor slab Horizontal grid line at that floor's Y plane. The floor defines the axis.
Wall Vertical grid line at nearest inner wall + section cut at that plane. Cut exposes internals for deeper picks.

Section cut = RS navigation. The Scissors tool from the main pill performs the same operation. Clicking a wall is a shortcut β€” the cut happens automatically because you can't pick interior elements through exterior walls.

Interaction cycle:

State A: No focus
  β†’ click element β†’ grid line appears, co-linear elements highlight

State B: Focused on line (draggable)
  β†’ drag β†’ BOM cascade recompose (continuous, validated)
  β†’ click same line β†’ release focus β†’ back to State A
  β†’ click different element β†’ release current, create new line (shortcut)

State A: line persists (visible, passive), focus released

6.3 Zone Lighting + Cascade Colors

When RS focuses a grid line, the affected zone lights up β€” everything from the drag line to the opposite boundary. Elements behind the drag line stay unlit. The user sees exactly what WILL change before dragging.

Color Meaning
Yellow Will reposition (element moves, keeps size)
Green Original extent within element (pre-drag boundary)
Blue New area being created (extension beyond pre-drag)
Orange Area being removed (shrink direction)
Red Clash zone (two elements overlapping β€” informational, does not block drag)
Unlit Not affected by this drag

Green/blue (or green/orange) boundaries are split within a single mesh via vertex coloring. The split plane is the pre-drag position, frozen during the interaction. The user always sees where original ends and new begins.

Full color spec: BOM_ENGINE_SPEC.md Β§22.

6.4 Drag = BOM Cascade with Validation

  1. Drag RS line β†’ BOM walks parent chain β†’ each child recomposes per its m_bom_line rules
  2. fill_mode drives behavior: SPAN stretches, FIXED repositions, SLOPE preserves angle
  3. Split colors show green (original) / blue (new) / orange (removed) in real-time
  4. TC-1 (adjacency) and TC-2 (coverage) validate continuously during drag
  5. If invariant breaks β†’ line turns red, refuses to move further, status bar says WHY
  6. Red clash highlighting is allowed β€” user may intend to fix overlap later
  7. On release: colors clear, GRID_MOVE logged to kernel_ops

6.5 2D Toggle

The Grid # button toggles visibility of all RS-created grid lines. No grid creation logic β€” pure show/hide. Lines exist because RS created them (with provenance). The toggle is a display preference, not a mode switch.


7. The BOM β€” Recipe, Not Scatter

A BOM is a recipe: one parent, N children, each with a quantity. Each child can itself be a BOM β€” building β†’ floor β†’ room β†’ furniture β†’ leaf, recursively.

-- The JS BOM extractor runs ONE query:
SELECT bom.m_product_id, bom.name, line.m_product_id AS child_id,
       line.qty, meta.ifc_class, meta.guid,
       meta.center_x, meta.center_y, meta.center_z
FROM m_bom bom
JOIN m_bomline line ON line.m_bom_id = bom.m_bom_id
LEFT JOIN elements_meta meta ON meta.m_product_id = line.m_product_id
ORDER BY bom.m_bom_id, line.line

The result is a tree. The tree drives: - Envelope β€” AABB of root BOM children - Phases β€” grouped by storey Γ— discipline Γ— IFC class - Grid seeding β€” element positions β†’ grid line candidates


8. Command Vocabulary

Every user action is a reversible kernel_ops entry:

Command Trigger Undo
GRID_MOVE Drag grid line Restore old position
GRID_ADD Add grid line Remove line
GRID_DELETE Remove grid line Restore line
STOREY_HEIGHT Adjust floor-to-floor Restore old height
ELEMENT_PLACE Place element Remove element
ELEMENT_REMOVE Remove element Restore element
BOM_ROTATE Rotate parent + children Restore angle
GRID_ROTATE Rotate grid line Restore angle
BOOKMARK Mark timeline position Remove bookmark

The vocabulary is deliberately small. Each op is reversible and composable. A full design session produces < 50 KB of commands.


9. Data Architecture β€” No Cloning

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Reference.db   │────▢│  Event Log (JSON) β”‚
β”‚  (read-only)    β”‚     β”‚  (kernel_ops)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚ materialize
                                 β–Ό
                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                        β”‚  NewBuilding.db   β”‚
                        β”‚  (regenerable)    β”‚
                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  • Reference.db is never modified. Not even a single byte.
  • Event log is the primary storage. Small, diffable, shareable via URL hash.
  • NewBuilding.db is a materialized view β€” delete it and regenerate from grammar + log.

10. The Grid-Based Model β€” CUD + Attachment + Cascade

The grid is not decoration. It is the design model. Everything flows from two ideas:

  1. Grid lines are the user's design intent. Create, move, or delete a grid line and the building responds.
  2. BOM elements attach to grid lines. The attachment type determines how each element responds when its grid moves.

10.1 Grid Line CUD (Create / Update / Delete)

Grid lines are managed via three user actions, each logged as a kernel_op:

Action Trigger Kernel Op What Happens
Create Rosetta drag from template line, or double-click a wall/column GRID_ADD or GRID_CALIBRATE New grid line inserted at element position. Engine rebuilds attach map.
Update Select grid line β†’ drag to new position GRID_MOVE Engine computes incremental delta β†’ elements translate/scale/reshape.
Delete Drag grid line beyond envelope boundary, or double-click existing line GRID_DELETE Line removed. Attached elements lose constraint. Engine rebuilds.

Rosetta Stone is the primary Create tool. Three template lines (X, Y, Z) sit outside the envelope. When calibration mode is ON (gold lines), the user drags from a template β†’ a committed grid line appears at the drop position. This is the user's creative contribution β€” they decide where the structural bays are.

Double-click is the secondary Create/Delete tool. Click an element (wall/column) β†’ grid line appears at its position. Click near an existing line β†’ removes it. Toggle behavior.

10.2 BOM Attachment β€” Why Elements Follow Grid Lines

When the engine rebuilds (after grid CUD or phase change), it classifies every visible BOM element against every grid line. The classification is spatial β€” based on proximity between element geometry and grid position:

For each element E, for each grid line G:
  distance = |E.center[axis] - G.position|
  halfExtent = E.bbox[axis] / 2

  if distance < 0.5m         β†’ ATTACH  (centerline near grid)
  if G inside E body          β†’ SPAN    (grid cuts through element)
  if |E.rightEdge - G| < 0.1m β†’ EDGE_RIGHT
  if |E.leftEdge - G| < 0.1m  β†’ EDGE_LEFT
  if E is roof at eave height β†’ ROOF_EAVE
  if E is flat roof spanning G β†’ ROOF_FLAT
  if G is Y-axis ceiling grid  β†’ ROOF_LIFT
  else                         β†’ INTERIOR (bay-proportional)

This is the attach map β€” the engine's index of which elements are bound to which grid lines, and how. The attach map makes the grid a structural model, not just a drawing overlay.

10.3 Cascade β€” How One Drag Moves Many Elements

When the user drags grid line B from position 10m to 13m:

  1. ATTACH elements (columns at B) translate +3m rigidly
  2. SPAN elements (slabs spanning A–B) scale to match new bay width
  3. EDGE elements (walls with one edge at B) scale or translate depending on which edge
  4. ROOF_EAVE elements have eave vertices move +3m while ridge stays fixed β€” slope changes
  5. INTERIOR elements (furniture between A and B) reposition proportionally within the bay
  6. WALL_HEIGHT_SCALE cascade: if a Y-axis grid (ceiling) moves, walls attached to it grow taller

This is how one grid drag propagates through the BOM hierarchy. The BOM tells you which elements exist and where they sit in the recipe. The attach map tells you how each one responds to the grid change. Together they form the grid-based model.

10.4 Rosetta Stone β€” The User IS the Gate

In the Java compiler, Rosetta Stone is an automated verification gate (G5): does the reconstruction match the original?

In Red Pill mode, Rosetta Stone is a design tool: - Create grid lines by dragging from template lines (gold when ON, grey when OFF) - Calibrate by recording the offset between auto-detected and user-placed positions - Delete grid lines by dragging them beyond the envelope boundary

Each Rosetta Stone placement is a witnessed fact β€” a user-verified ground truth logged as GRID_CALIBRATE in kernel_ops. The user decides which grid lines matter. The engine makes every other element follow.


11. Implementation Status

11.1 Code Map

File Lines Role
doc_canvas.js 2214 Orchestrator: UX, phases, grid rendering, timeline, save/open
grid_state.js 353 Grid positions, labels, originals, deltas β€” single source of truth
grid_recompose.js 682 Engine bridge, bbox swizzle, command dispatch, BOM L1 recompose
grid_kinematics.js 672 Pure-math engine: 8 relation types, cascades, bay-proportional
bom_extract.js 350 Grammar extractor: one SQL query, BOM β†’ envelope + phases
kernel_ops.js 400 Event log: every user action as reversible JSON command
bom_walker.js 175 BOM tree traversal (JS port from Java)
verb_expand.js 190 Verb expansion math (FRAME, TILE, etc.)

11.2 Done (S266–S270, branch full)

Feature Sprint Evidence
Red Pill UX + Doc Pill S266 doc_canvas.js, 7 icons, activate/deactivate
JS BOM Extractor S266 bom_extract.js, one query, one pass
Gantt Stepper (Next/Prev) S267 Phase-by-phase, discipline filtering, timeline scrub
Grid Kinematics Engine S268 grid_kinematics.js, 98 tests, pure-math
BUG-1 Incremental Delta S270 _lastAppliedDeltas tracking, T52-T54
BUG-4 SCALE Commands S270 IFC→Three bbox swizzle, T55-T58
Ceiling Grid Auto-Place S270 Y-axis grid at eave height, Phase 3 trigger
Rosetta Stone CUD S273 Create via drag, Delete via envelope boundary, user grids preserved on scrub
Refactor: grid_state.js S270 Wired existing module, replaced 7 vars + 7 functions
Refactor: grid_recompose.js S270 Extracted engine bridge + BOM recompose (682 lines)
BOM Engine L1 Recompose S272 Phase 3+4, discipline rules, BomDiff commands
Design Save/Open S273 IndexedDB, kernel_ops replay
Y-axis Ceiling Drag UI S270c Click disc β†’ drag up/down β†’ ROOF_LIFT + WALL_HEIGHT_SCALE cascade

11.3 Tests

File Count Scope
test_doc_canvas.js 79 UX, grid ops, BatchedMesh, BUG-1/BUG-4, save/open, grid guards, ceiling drag
test_grid_kinematics.js 98 ATTACH/SPAN/EDGE/ROOF classification, bay-proportional, cascades
test_s268_recompose.js 63 Attach-map recompose + bay-proportional integration
test_grid_modules.js 114 Grid detection, overlay, drag, label generation
whitebox_regression.js 34/36 Split/IFC/offline/variance/ground (2 pre-existing)
test_bom_*.js (7 files) 438 BOM engine: strategies, constraints, diff, node, tree, grid, rules, deep
Total 354+34+438 All whitebox, Β§-tagged logs

11.4 Known Issues

ID Severity Description Status
BUG-1 Fixed Incremental delta double-counting on second drag T52-T54
BUG-2 Low Grid with no attachments should warn, not silently allow drag Open
BUG-3 Medium Next after drag materializes elements at original positions Open (Stage 2)
BUG-4 Fixed SCALE commands not firing (bbox axis swizzle wrong) T55-T58
CREEP-1 Low BOM recompose code in grid_recompose.js (180 lines, undocumented in spec) Accepted
CREEP-2 Low applyDrag() signature differs from spec (all-deltas vs per-grid) Accepted

11.5 Roadmap

Priority Feature Scope
~~P1~~ ~~Y-axis drag UI~~ ~~Done S270c β€” click disc β†’ drag β†’ cascade~~
P2 BUG-2 warning Status: "no attached elements β€” place elements first"
~~P3~~ ~~BUG-3 phase-aware recompose~~ ~~Done S270d β€” B1+B2 proven: materializeLevel() fires on Next, reads CURRENT AABBs. Β§BOM_NEXT logs confirmed.~~
P4 UBBL Validator bom_rules.js + disc_rules.json done (8 rules). B1 done (scripts loaded). UI wiring (4c) remaining.
~~P5~~ ~~Materialization~~ ~~Done (sw v653) β€” materialize.js db.export()s the edited in-memory db (B1-persisted element_transforms + replayed kernel_ops) as NewIFC.db into the import:// cache scheme; reference never written back. NON-INVENT (no coordinate computed). DocCanvas.materialize(). F9 closed: poc_redpill_materialize.js (W-REDPILL-MATERIALIZE) runs the G8-GOVERNANCE identity gate on the REAL exported FILE (1b) β€” M1 identity round-trip PASS (3504/3504, digest match, centroid 0.0mm), M3 edited session carries 882/882 governed moves into the file & still PASSES (ungoverned=0), M4 reference untouched, M5 deterministic.~~
P6 Share via URL ?ref=SampleHouse&ops=...

11.6 BOM-Driven Cascade Tasks (S272 engine ready, needs wiring)

The BOM engine (438 tests) provides the algebra. These tasks wire it to visible behaviour.

ID Task What the engine already does What's missing
~~B1~~ ~~Script tags~~ ~~Done S270d β€” 7 bom_engine/*.js scripts added to index.html + sw.js precache. All globals load in browser.~~ β€”
~~B2~~ ~~Wall→Window cascade~~ ~~Done S270d — materializeBomLevel() fires on Next, §BOM_NEXT level=1..3 confirmed. Column renamed bom_child_id → M_BOM_Line_ID (iDempiere convention).~~ —
B3 DISC switch β†’ MEP fresh route ROUTE strategy stub exists, route_walker.js exists ROUTE stub returns straight line. Wire: ROUTE(p) β†’ RouteWalker.walk(db, anchors, disc) (~10 lines)
B4 Tile recount on resize UNIFORM/PACKED strategies compute count from available space verb_expand.js TILE formula not connected to BOM engine
B5 Cross-DISC read-only cascade recompose() is stateless β€” reads current AABBs, doesn't care who moved them No code needed. DISC switch β†’ materializeLevel() β†’ children read current positions. Already wired in _materializeBomLevel().

Key insight (2026-05-24 watchdog review): "MEP rerouting after grid drag" (previously Deferred) is NOT needed. MEP never sees drag. User switches DISC β†’ MEP reads current structural positions β†’ RouteWalker fires once. One-way, user-triggered. No live coupling.

Spec alignment notes (review before editing docs): - BOM_ENGINE_SPEC.md Β§16 says ROUTE delegates to RouteWalker "in Phase 3" β€” Phase 3 is done but ROUTE is still a stub. Β§16 should say "Phase 5" or "B3 task". Fix after B3 is wired. - ~~RED_PILL.md Β§11.5 P3 (BUG-3) β€” CLOSED. B1+B2 wired and Β§BOM_NEXT logs prove materializeLevel() reads current AABBs.~~ - BOM_ENGINE_SPEC.md Β§10 file layout lists bom_rules.js and disc_rules.json β€” both exist and pass tests, but Β§6.3 DiscRuleProvider still shows // TODO: loadFromDB. Acceptable β€” JSON-first is the current path, DB-load is v2. - NEW_FROM_REFERENCE.md Β§17.9 BOM Completion Triage (items A-I) β€” not cross-checked against B1-B5 tasks. May have duplicates or gaps. Verify on next full review.

11.7 S270e Coherence β€” Single-Level Recompose (Done)

3 engine bugs found and fixed by 36-test coherence suite (test_coherence.js): - BUG-1 SPAN sizing: _stepFill ignored stratResult.size for SPAN leaves β€” wall/slab stayed at childSize instead of stretching. Fixed: SPAN child dimension on fill axis uses strategy result. - BUG-2 Fill-origin: parentOrigin = maxReservedEnd pushed optionals past parent when mandatory walls at both edges. Fixed: uses leftmost reserved end. - BUG-3 Edge-follow: Right wall's tack.dx was static from DB. Fixed: _positionChild detects edge-anchored children and repositions relative to current host dimensions. _anchorFace stored from m_bom_line.anchor_face.

8 scenarios proven: storey grow/shrink (A), SPAN on X/Y/Z (B/C/D), UNIFORM recount (E), FIXED position (F), idempotency (G), mandatory-survives-shrink (H).

Element resize behaviour (metadata-driven, no hardcode)

Every element's response to parent resize is controlled by m_bom_line metadata:

Element layout_strategy mandatory max_count qty_type Resize behaviour
Wall SPAN 0 β€” β€” Stretches on length axis, thickness fixed
Door FIXED 1 1 FIXED Stays at tack, exactly 1, never resized
Window UNIFORM 0 NULL VARIABLE May ADD more to fill new space, existing stay put
Column FIXED 1 β€” FIXED Stays at tack, never moves
Slab SPAN 0 β€” β€” Stretches on both length and depth
Beam SPAN 0 β€” β€” Stretches on span axis
Roof SPAN 0 β€” β€” Follows building envelope

Key distinction: UNIFORM does NOT redistribute spacing. It fills remaining gap with additional instances if qty_type=VARIABLE. Wall material between openings is the infill β€” the wall itself is the SPAN parent, openings are its children.

Validation (post-recompose): A bare wall >6m without opening is not fixed by the engine β€” it's flagged by UBBL rules (disc_rules.json via BomRules.checkPlacement). The rule fires AFTER recompose and reports a violation. The user decides what to do. See BOM_ENGINE_SPEC.md Β§21.3 SETBACK for the pattern.

Controlling columns: layout_strategy, mandatory, max_count, min_count, qty_type, fill_axis, allocated_width/depth/height_mm β€” all in m_bom_line. See BOM_ENGINE_SPEC.md Β§3.3 for strategy vocabulary.

11.8 Cross-Level BOM Scenarios (Stage 2)

Principle: BOM JOIN query, not geometry invention. A cross-level response is a targeted SQL JOIN that pulls a recipe (one BOM line + its strategy/attachments) without materializing the full tree. The recipe is instantiated at a computed hostAABB, then recompose() fills it via the same single-level operation.

-- Example: "What slab recipe covers a storey boundary?"
SELECT bl.child_product_id, bl.layout_strategy, bl.allocated_*
FROM m_bom_line bl
JOIN m_bom parent ON bl.bom_id = parent.bom_id
WHERE bl.child_element_type = 'IfcSlab' AND bl.role = 'BOUNDARY'

The JOIN can pull across BOM sets β€” a slab recipe in a shared component library, a standard detail from a different BOM. Neither BOM knows about the other; the query crosses that boundary.

ID Scenario Trigger Status
S1 Boundary slab β€” GF extends past L1, gap needs slab GF.envelope > L1.envelope Next β€” first cross-level JOIN
S2 Column continuity β€” GF column supports L1 floor Column repositioned by drag Later β€” needs anchorFace=TOP propagation
S3 Foundation follows footprint Any storey grows β†’ building grows Already works β€” SPAN child of BUILDING
S4 Roof follows envelope Any storey width/depth change Already works β€” same as S3
S5 Cantilever β€” L1 wider than GF L1.envelope > GF.envelope Later β€” needs I1 exemption
S6 Stair/lift shaft β€” vertical continuity Any storey shifts on X/Y Later β€” needs SPAN_VERTICAL type
S7 External wall segment β€” corner at storey junction GF extends, L1 stays Next β€” paired with S1
S8 MEP rerouting — duct/pipe recount after resize Room size change B3 task — ROUTE→RouteWalker
S9 Setback β€” zoning limits upper storeys Building envelope exceeds limit Later β€” rule feedback to hostAABB
S10 Party wall β€” shared wall at boundary, never moves Grid drag near boundary Already works β€” mandatory FIXED

Extension points (all exist in code, need new roles/strategies): - _stepCascade (bom_node.js) β€” sibling envelope comparison for S1/S7 - _positionChild (bom_node.js) β€” anchorFace=TOP propagation for S2 - Constraints.fitCheck (bom_constraints.js) β€” I1 exemption for S5 - BomRules.checkPlacement (bom_rules.js) β€” SETBACK rule for S9 - _stepReserve at BUILDING level β€” shaft reservation for S6

11.9 Deferred (Stage 3+)

  • IFC export from NewBuilding.db
  • Diagonal grids / rotation / mirroring
  • Git-like branching (parallel design timelines)
  • GridInteraction extraction (doc_canvas.js β†’ grid_interaction.js, ~190 lines, low priority)

12. Technology Stack

Layer Technology Notes
Rendering Three.js r160 ESM BatchedMesh, InstancedMesh, DLOD
Database sql.js (WASM SQLite) Browser-side, no server
Storage IndexedDB Per-building DBs, offline-capable
Caching Service Worker Offline-first, versioned cache
Hosting OCI Object Storage + GitHub Pages Static files, CDN-cached
Language Plain JavaScript (ES5/6) No frameworks, no build tools

Zero server dependency. The entire designer runs in a browser tab.


13. For New Developers

Quick Start

  1. Open the viewer at the deployed URL (or deploy/dev/index.html locally)
  2. Load any building (e.g. SampleHouse, SampleCastle)
  3. Tap the Red Pill icon (capsule, top-right)
  4. Press Next repeatedly β†’ watch elements appear phase by phase
  5. Click a grid line β†’ drag it β†’ watch walls/slabs recompose

Running Tests

# All grid kinematics tests
node deploy/dev/tests/test_grid_kinematics.js

# Doc canvas tests (includes BUG-1/BUG-4 regression)
node deploy/dev/tests/test_doc_canvas.js

# Full recompose integration
node deploy/dev/tests/test_s268_recompose.js

# Grid modules (detection, overlay, drag)
node deploy/dev/tests/test_grid_modules.js

All tests are whitebox: Β§-tagged console.log() lines prove values, counts, and state. No Playwright needed for value verification β€” the maths tells you.

Key Concepts

  • BOM = Bill of Materials. Recipe: parent β†’ children with quantities. Recursive.
  • Grammar = the proportional skeleton extracted from a reference building.
  • Event Log = deterministic command sequence. Same log β†’ same result. Always.
  • Rosetta Stone = user-verified ground truth. The calibration layer.
  • Kernel Op = one reversible command in the event log.
  • Attach Map = engine's index: which elements attach to which grid lines, and how.
Doc What It Provides
BOMBasedCompilation.md The recipe model β€” BOM hierarchy, tack offsets, verb formulas
TestArchitecture.md 9 verification gates, traceability matrix
SQLite3D_Schema.md DB schema β€” elements_meta, element_transforms
CLASH_DETECTION.md Clash rules, R-tree spatial indexing
BIM_Designer_Browser.md Viewer layer β€” the foundation Red Pill builds on
SPATIAL_COMPILATION_PAPER.md Theoretical proof β€” BOM ↔ protein folding analogy
NEW_FROM_REFERENCE.md Full 1800-line technical spec (all the details)