Red Pill RosettaStone — What "Verified Re-Model" Means¶
Status: B0 PROPOSAL — awaiting user approval of the comparison definition. No witness code, no editor changes until the open questions in §8 are answered. Lane:
prompts/TRUTH_MODEL_REDPILL_ROSETTA.mdLane B (B0 spec → B1 bridge → B2 witness).
RED_PILL.md §2 advertises the round-trip as "Verified (Rosetta Stone gates)" (RED_PILL.md:50).
This document defines the comparison that makes that sentence true — the same arithmetic-proof
rigor the Java gates apply to the static compiler reconstruction, applied to the dynamic
output of the 2D Grid Editor. Two modes, both arithmetic, both NON-INVENT:
- Identity round-trip — grammar → grid → materialize, no edits → output must equal the reference within the existing gate tolerances. Proves the forward path is lossless.
- Governed-delta — after a grid drag, every moved element must have moved because the
BOM/grid said so: its new position is derivable from its
m_bom_linerelationship applied to the dragged grid line — never from geometric proximity.
Placement recommendation: a new standalone doc (this file), cross-linked one line each from
RED_PILL.md §13 Related Docs and TheRosettaStoneStrategy.md (after the Six Gates table) —
NOT a §section inside NEW_FROM_REFERENCE.md. Reasons: (a) NEW_FROM_REFERENCE.md is already
~1900 lines and is a UX/feature spec, not a verification contract; (b) this doc is the Red-Pill
peer of TheRosettaStoneStrategy.md and will be cited by witnesses (@Traces style), so it
needs a stable, short anchor namespace of its own. (Cross-link edits to the two existing docs
are held for approval with the rest of this proposal.)
1. Grounding — how the editor actually behaves today (read first, all cited)¶
The gate design below is driven by these verified facts, not by the docs' aspirations:
| # | Fact | Evidence |
|---|---|---|
| F1 | The recompose attach map is built from geometric proximity only: ATTACH_TOL = 0.5 m centreline distance, EDGE_TOL = 0.1 m edge distance; everything else is INTERIOR |
grid_kinematics.js:27-28, _classifyElement :104-210, _classifyInterior :377-400 |
| F2 | INTERIOR elements are bulk-translated bay-proportionally on every drag — no BOM consulted |
grid_kinematics.js:535-540 (dragGrid adds bay commands), _computeBayProportional :568-605 |
| F3 | The BOM recompose path (§BOM_RECOMPOSE) exists but fires only after the heuristic pass, debounced 16 ms, and only if _bomNodes was populated by a prior Next press |
grid_recompose.js:359-364 (gate condition), :390-476 (_fireBomRecompose) |
| F4 | Even the BOM path selects affected parents through the heuristic attach map (attachMap[gridId] → guid set → _elementRef match) — BOM data never builds the map |
bom_tree.js:206-233 (getAffectedBranch) |
| F5 | The BOM relationship vocabulary needed for governance already exists per line: layout_strategy, anchor_face, edge_offset_mm, mandatory, fill_axis, tack dx/dy/dz, element_ref |
schema migration/W022_bom_engine_columns.sql:6-15; loaded into BOMNode at bom_tree.js:100-185 (_anchorFace :169, _elementRef :177) |
| F6 | Live failure this gate must catch (SampleCastle, 2026-05-24): heuristic classified ATTACH=1 SPAN=0 EDGE_R=7 → 8/119 governed, 111 bulk-translated as "interior", structure coherence broke; §RECOMPOSE_ENGINE fired, §BOM_RECOMPOSE did not |
memory project_rs_before_drag.md; log tags at grid_recompose.js:199 / :464 |
| F7 | A second, separate drag cascade exists for clearance classes (furniture/outlets/lights) using proximity strategies (proportional/pin_to_wall/center_bay) from grid_rules.json |
grid_drag.js:126-209 (cascadeElements), grid_rules.json:42-79 |
| F8 | Persistence is split: the clearance cascade writes element_transforms (grid_drag.js:678-687); the kinematics/BOM recompose mutates mesh matrices only — no DB write anywhere (db.exec reads only at grid_recompose.js:117,126). A NewIFC.db materialized today would not contain kinematic drag deltas |
grid_recompose.js:211-306 (_applyCommand family), :503-570 (_applyBomDiffCommand) |
| F9 | ~~Materialization grammar + event_log → NewIFC.db is roadmap P5, not built~~ CLOSED (sw v653): viewer/materialize.js toBuffer(db)=db.export() of the edited in-memory db → NewIFC.db; identity mode 1(b) now runs the gate on the REAL file (W-REDPILL-MATERIALIZE M1 PASS 3504/3504 digest match) |
viewer/materialize.js, viewer/tests/poc_redpill_materialize.js; was RED_PILL.md §11.5 P5, NEW_FROM_REFERENCE.md §8.3 |
| F10 | Grid lines carry provenance: detected (grid_dims.js clustering, rules in grid_rules.json grid_detection) or user-calibrated (GRID_CALIBRATE kernel_op, gold-line RS mode) |
grid_dims.js:24-37, NEW_FROM_REFERENCE.md §6.5, RED_PILL.md §10.1 |
| F11 | The Java side already proves BOM→coordinates == reference with mm-rounded AABB digests — the browser gate must reuse this maths, not fork it | SpatialDigest.computeFromBOMTree DAGCompiler/.../SpatialDigest.java:368-402 |
Consequence: the editor today fails Mode 2 by construction (F1–F4, F6), and Mode 1 has a persistence hole (F8) that any honest round-trip gate will expose. That is what Lane B1/B2 fix.
2. Reused gate maths (extracted from the Java gates — no new numbers)¶
All tolerances below are recovered from code; none are invented here:
| Quantity | Value | Source |
|---|---|---|
| COUNT | exact: expected = base + GenerativeCount, delta == 0 |
RosettaStoneGateTest.java:154-170 (G1) |
| VOLUME | Σ AABB volume, |Δ%| ≤ 0.1 |
RosettaStoneGateTest.java:190-203 (G2); SQL :719-727 runs unchanged in sql.js |
| DIGEST | per-element line minX\|maxX\|minY\|maxY\|minZ\|maxZ\|material_rgba in mm, Math.round, per-class CLASS=X COUNT=N, SHA-256; geometry_hash excluded cross-mode |
SpatialDigest.java:64-125, cross-mode rule :140-153; invoked with false at RosettaStoneGateTest.java:232-233 |
| Centroid bands | EXACT ≤ 1.0 mm, DRIFT ≤ 50 mm, else SHIFT (+ MISSING/EXTRA) |
EyesConstants.java:39-40 (SPATIAL_EXACT_MM, SPATIAL_DRIFT_MM); SpatialDiff.classify BIMEyes/.../SpatialDiff.java:424-450 |
| Placement target | "100% within 1 mm. This is the primary metric." | TheRosettaStoneStrategy.md:46-48 (Tier 2) |
Reusing the digest line format matters beyond convenience: a JS digest computed in the browser over the same rows is byte-comparable with the Java oracle digest (F11) — the ledger oracle-equivalence pattern, applied to geometry.
3. Mode 1 — Identity round-trip (no edits)¶
Issue proved: the forward path (grammar → grid → full BOM cascade) is lossless before any edit. If identity fails, governed-delta results are meaningless.
Procedure (per building, e.g. SH):
1. Load reference DB; enter Red Pill; press Next (full BOM cascade, materializeBomLevel,
grid_recompose.js:585-626, §BOM_NEXT). Zero user edits, empty event log.
2. Capture the materialized element set. Two targets, phased:
- (a) in-session (available now): element AABBs as the editor holds them
(element_transforms + mesh matrices — see Q5 on the F8 split);
- (b) NewIFC.db (P5 landed, F9 closed — sw v653): the materialized file itself — the real
round-trip per NEW_FROM_REFERENCE.md §16.5/§16.7. viewer/materialize.js db.export()s the
edited in-memory db; poc_redpill_materialize.js M1 runs §2's gate maths on the reopened file
(PASS 3504/3504, digest match, centroid 0.0mm). Mode 1(a) in-session remains the no-IDB path.
3. Compare against the reference DB with the reused maths of §2:
| Check | Gate semantics | Verdict rule |
|---|---|---|
| RP-COUNT | G1 reuse: per-class element count | exact equality |
| RP-VOLUME | G2 reuse: Σ AABB volume | |Δ%| ≤ 0.1 |
| RP-DIGEST | G3 reuse: cross-mode SpatialDigest (geometry_hash excluded) | digests equal |
| RP-CENTROID | SpatialDiff bands | all elements EXACT (≤ 1 mm); report centroidMax |
IFC.db byte-identity (reference untouched, NEW_FROM_REFERENCE.md §16.7) is asserted as a
precondition, not a gate — it is already the editor's standing contract (RED_PILL.md §9).
4. Mode 2 — Governed-delta (after a grid drag)¶
Issue proved: every element that moved, moved because the BOM/grid said so — the exact inverse of the F6 regression (111 ungoverned bulk-translations).
4.1 The predictor P(BOM, grid′)¶
A pure function — this is the heart of the gate:
predicted_AABB(e) = P( m_bom_line(e), grid_state_after_drag )
- Inputs: the element's BOM relationship row (
layout_strategy,anchor_face,edge_offset_mm,mandatory,fill_axis, tackdx/dy/dz,allocated_*_mm— F5) and the post-drag grid positions (old position + Δ of the dragged line, all other lines unchanged). Pis the BOM recompose algebra that already exists and is already tested (BOMNode.recompose(hostAABB′)— bom_node.js / bom_strategies.js; the §S270e behaviour table inRED_PILL.md§11.7: SPAN stretches, FIXED stays, UNIFORM adds, anchor-face edge-follows). The gate does not define new placement maths — it demands the drag path route through the proven maths.- No peeking:
Pmay read only(BOM, grid′). It must never read the actual post-drag positions it is judging — otherwise the gate proves nothing. (Same discipline as the ledger oracle: predict independently, then diff.)
4.2 Element classification after a drag¶
For every element in the building (not just the moved ones):
| Class | Definition | Gate rule |
|---|---|---|
| GOVERNED | has a BOM line (via element_ref → _elementRef, bom_tree.js:177) whose recompose under grid′ predicts a new AABB |
|predicted − actual| ≤ tol per axis (tol: see Q2; proposed 1.0 mm = SPATIAL_EXACT_MM) |
| STATIC | BOM says unaffected (e.g. mandatory FIXED outside the affected branch; element on un-dragged grids) |
actual must equal pre-drag position within tol — this clause catches the 111 bulk-translations |
| UNGOVERNED | actual moved > tol but no BOM line exists / no prediction derivable | counts toward ungoverned= — FAIL in strict mode (Q4 governs whether a declared heuristic-fallback budget is ever acceptable) |
Verdict: PASS iff every GOVERNED element is within tol, every STATIC element is unmoved, and
ungoverned == 0 (strict mode). The witness must name each offending element (guid, class,
predicted, actual, |Δ|) — the gate must be able to FAIL and say why.
4.3 What "actual" means (forced by F8)¶
Today "actual position" lives in two places: element_transforms (clearance cascade,
grid_drag.js:678-687) and mesh instance matrices (kinematics/BOM path — never persisted,
grid_recompose.js). The gate defines actual = the persisted DB row — because that is what
materializes into NewIFC.db and what every downstream consumer (viewer, clash, ERP) reads.
Implication for B1: the governed recompose path must persist its deltas (or the gate will fail
on the mesh/DB divergence — correctly).
4.4 Volume under delta¶
After a legitimate drag, total volume changes by design (SPAN stretches). The delta-mode
volume check is therefore predicted total volume vs actual total volume within the same
±0.1 % band (G2 maths, different comparands) — not actual-vs-reference. (Confirm: Q6.)
5. Gate vocabulary — mapping to the existing G-gates¶
| Red-Pill gate | Reuses | Status |
|---|---|---|
| RP-COUNT | G1-COUNT (RosettaStoneGateTest.java:154-170) |
reuse, new comparands |
| RP-VOLUME | G2-VOLUME ±0.1 % (:190-203) |
reuse |
| RP-DIGEST | G3-DIGEST cross-mode (SpatialDigest.java:140-153) |
reuse |
| RP-CENTROID | Tier-2 / SpatialDiff bands (EyesConstants.java:39-40) |
reuse |
| G-GOVERNANCE (numbering: Q1) | — | genuinely new: "every delta is BOM-explained" (§4). No existing gate checks why a coordinate changed; G1–G6 check only that the end state matches a reference. Governance is the dynamic-truth peer of G5-PROVENANCE: G5 traces every element to the library; G-GOVERNANCE traces every movement to a BOM relationship + a grid op in kernel_ops. |
Not mapped (out of scope for this gate): G0-COMPILED (editor output is not a c_order
compilation), G4-TAMPER (source-scan, stays Java-side), G6-ISOLATION (single-building editor
session). They remain owned by the static-compiler regime — this spec extends the truth
model, it does not replace it (prompt: Out of scope).
Naming collision (must be arbitrated, Q1): the lane prompt names the new gate
G7-GOVERNANCE, but G7-COMPOSITION already exists in the canon — the Tier-4 compositional
gate for buildings without a reference DB (TheRosettaStoneStrategy.md:113-117).
Recommendation: number the new gate G8-GOVERNANCE (next free), keeping G7-COMPOSITION as
documented. The §-log shapes below are numbering-agnostic on purpose.
6. §-log claim shapes (what the B2 witnesses will emit)¶
One verdict line per mode per building — machine-greppable, every number sourced:
§REDPILL-RS mode=identity build=SH count=ok(119/119) vol=+0.02% digest=match centroidMax=0.4mm verdict=PASS
§REDPILL-RS mode=delta build=SH grid=B axis=X delta=+1.500 governed=119/119 static=ok ungoverned=0 fallback=0 centroidMax=0.8mm verdict=PASS
Failure lines name the offender (the gate must FAIL loudly, never aggregate-only):
§REDPILL-RS-FAIL mode=delta guid=2O2Fr$t4X7Zf8NOew3FLKI class=IfcFurnishingElement reason=UNGOVERNED moved=412mm bom_line=none
§REDPILL-RS-FAIL mode=delta guid=1hOSvn6df7F8_7GcBWlR72 class=IfcWall reason=PREDICT_MISS predicted=(5.000,0.000) actual=(5.347,0.000) dmax=347mm
§REDPILL-RS-FAIL mode=identity class=IfcDoor reason=COUNT ref=14 out=13
Supporting lines (B1 contract — no silent heuristic creep):
§BOM_ATTACH map built: bom=108 fallback=11 (fallback guids logged above)
§BOM_RECOMPOSE ... ← must fire on drag (today only §RECOMPOSE_ENGINE fires — F3/F6)
§REDPILL-RS verdicts are consumed by Lane A's system_is_real.sh runner (A2) and the CI
smoke gate (A3) once B2 lands.
7. Witness plan (B2 forward pointer — NOT implemented in B0)¶
scripts/poc_redpill_rosetta.js(or browser whitebox perTestArchitecture.md§Browser Testing): identity on SH → PASS; governed drag (replay the F6 SampleCastle drag) → PASS withungoverned=0; forced heuristic path → FAIL naming the elements. Run viabuild/erp/run_witness.sh-style log discipline; read the log, not the exit code.- Reuse, don't fork: digest = JS port of the §2 line format (byte-diffable against Java digests); volume = the G2 SQL verbatim in sql.js.
7b. B1 implementation spec — BOM-driven attach map + persist-first + timeline unify¶
Implemented on
feat/redpill-b1-bom-attach. The 7 design answers (§8) are LAW here: Q1=G8-GOVERNANCE, Q2=1.0mm, Q3=event-log-is-the-reference (= the History op-log), Q4=strict ungoverned=0-for-anything-that-MOVED, Q5=persist-first, Q6=±0.1%, Q7=ALL-classes.
7b.1 The replacement (kills F1–F4, F6)¶
Today grid_recompose.rebuild() builds the attach map via _kinEngine.attachGridToElements()
(proximity heuristic, F1), then _fireBomRecompose selects parents through that heuristic map
(getAffectedBranch(_bomNodes, attachMap, gridId), F4). B1 inverts the source:
bom_tree.buildBomAttachMap(bomNodes, bomGridMgr)(new) — buildsgridId → [{guid,bomNodeId, parentId,anchorFace,strategy}]from the BOM tree itself: each materialized child carrying an_elementRef(→ guid,bom_tree.js:177) is bound to the grid line(s) its parent BOM created (thebomGridMgrgrids whosebomNodeId== the parent's id, or — viagrid_shared_key— the shared group). BOM data builds the map; proximity never enters.§BOM_ATTACH map built: bom=N fallback=M grids=G.- Heuristic fallback only where BOM is genuinely absent — a shown guid with no matching
_elementReffalls back to the proximity classification, and its count is thefallback=Mtally (F2: no silent heuristic creep). A fallback element is allowed to exist but, per Q4, must be STATIC — the witness assertsfallbackelements did not move. - On drag, if the BOM attach map is non-empty,
applyDragfires the BOM path (§BOM_RECOMPOSE) driven by the BOM map — not§RECOMPOSE_ENGINE's heuristic bay-proportional bulk-translate. Every governed child recomposes via the provenBOMNode.recomposealgebra (anchor-face edge-follow, SPAN stretch, FIXED stay —bom_node.js).governed=N/N ungoverned=0.
7b.2 Phase ordering + uncalibrated degrade (prompt B1)¶
materializeBomLevel (Next) must have run to populate _bomNodes + _bomGridMgr before a
governed drag. If a drag arrives with _bomNodes.length === 0 (UNCALIBRATED — user dragged before
Next), B1 degrades visibly: logs §BOM_RECOMPOSE skipped reason=uncalibrated (bomNodes=0) and
the legacy heuristic §RECOMPOSE_ENGINE path still runs (no break, no governance claim). The gate
treats an uncalibrated session as "not a governed-delta candidate" — it never silently claims
governed=.
7b.3 Persist-first (Q5, fixes F8)¶
The governed recompose mutates mesh matrices (_applyBomDiffCommand). B1 additionally persists
each governed move's new center to element_transforms and commits the whole governed set as
ONE GRID_MOVE kernel-op group (one gid) via the existing KernelOps.commitOp →
universal_history wrapper. "Actual = the persisted DB row" now holds: a read-the-tip query returns
the moved position. The op params.cascade carries every governed {guid,oldX,oldY,newX,newY} so
GridDrag.applyReplayedMove (the existing GRID_MOVE fold path, universal_history.js:274-282)
reverses the WHOLE governed set on a timeline step-back — the SO-Complete-style group fold.
7b.4 One op-log, one playback (kill the double)¶
grid_drag.js keeps a private hist[] compound-undo + Ctrl+Z handler. B1 routes the editor's
Ctrl+Z/Y through UniversalHistory.undo()/redo() (the shared HB timeline) so the timeline and the
keyboard drive the same op-log; the private hist[] becomes a redundant shadow and the Ctrl+Z
keydown no longer calls KernelOps.undoOp directly behind the timeline's back.
7b.5 Rides the SAME HB session tree (PR #291 contract)¶
The governed GRID_MOVE commit flows through KernelOps.commitOp → the universal_history
commitOp wrapper → _recordOp → _ensureAuthoritativeTreeKey() (re-keys via HB.setTreeKey
(_treeKey()), keyed off _openDbUrl()/A.DB_URL) → HB.push. No new key, no parallel store: the
governed move lands in the same per-session Z tree re-home recalls (the empty-Z #291 fix is honored
by construction — B1 adds no keying path of its own). poc_session_recall.js stays green.
7b.6 Witness — poc_redpill_governed_drag.js (W-REDPILL-GOVERNED-DRAG)¶
Node whitebox over bom_tree.js/bom_node.js/bom_grid.js + a fixture DB (W022 schema; built in
the witness — the SampleCastle DB is not in this worktree, so the fixture reproduces the F6 class:
a parent BOM with many _elementRef children that the proximity heuristic would bulk-translate as
interior). Asserts: BOM attach map governs all children (governed=N/N ungoverned=0); the
proximity-only baseline shows the 111-class regression (most interior) → the BOM map fixes it; the
move persists to element_transforms (read-the-tip = new position); ONE gid GRID_MOVE op rides
the HB tree; a timeline fold-back reverses the whole governed set. §BOM_RECOMPOSE fires,
§RECOMPOSE_ENGINE does not.
8. Open design questions — these gate B1/B2 (answers needed, not extractable)¶
Q1 — Gate number. Prompt says G7-GOVERNANCE; G7-COMPOSITION is already taken
(TheRosettaStoneStrategy.md:117). Rename the new gate G8-GOVERNANCE (recommended), or
renumber/absorb COMPOSITION?
Q2 — Governed-delta tolerance. Proposal: reuse SPATIAL_EXACT_MM = 1.0 mm
(EyesConstants.java:39) for |predicted − actual|. Since both sides are float maths over the
same engine, even 1 mm is generous — accept 1.0 mm, or demand exact-after-mm-rounding (the
SpatialDigest convention, effectively 0.5 mm)?
Q3 — Reference for composed/edited buildings (Tier-4). A re-modelled building has no
original DB. TheRosettaStoneStrategy.md:103-140 already answers the static version with
G7-COMPOSITION (provenance + fragment fidelity + invariants + containment, W-COMP-). Is the
Red-Pill answer: identity gate against the reference grammar* + governed-delta for every
subsequent op + W-COMP-style invariants on the result — i.e. "the event log IS the reference"?
Or do you require an independent re-materialization (replay the log twice through independent
paths and diff digests) as the oracle?
Q4 — Elements with no BOM relationship. B0's gate says "moved without BOM justification =
FAIL", but B1 allows a heuristic fallback "where BOM data genuinely doesn't exist" with a
§-logged count. These conflict at the boundary: does a fallback-moved element fail the gate?
Proposal: strict mode (default, ungoverned=0 required, fallback elements must be STATIC) vs a
declared fallback=N budget mode for DBs with incomplete element_ref coverage. Which is the
shipping default — and is any non-zero fallback ever a PASS?
Q5 — Identity scope today (F8/F9). RESOLVED. F8 fixed by B1 (persist-first → element_transforms);
F9 closed by P5 (viewer/materialize.js, sw v653). Identity mode 1(b) now runs the gate on the real
exported NewIFC.db (W-REDPILL-MATERIALIZE M1); 1(a) in-session remains the no-IDB/node path. "Actual =
the persisted DB row" holds end-to-end: the persisted center materializes byte-for-byte into the file.
Q6 — Delta-mode volume band. Confirm: predicted-vs-actual volume within the same ±0.1 % (G2 reuse), with actual-vs-reference volume intentionally unchecked after an edit (volume change is the design intent).
Q7 — Class coverage denominator. Identity per-class counts need a declared inclusion list:
compare ALL classes in the reference, or only classes the BOM recipe covers (STD_MEP defaults
for small buildings, NEW_FROM_REFERENCE.md §5.3, may materialize differently)? Proposal:
ALL classes, with any exclusion named in the verdict line — silent exclusions are how 111
elements went unwatched.
Copyright (c) 2025-2026 Redhuan D. Oon. MIT Licensed.