GENERATED VIEW · PRIVATE ARCHIVE · DO NOT SERVE
RobCo Industries · Archive Museum

AUDIT_FO3_SPINE.md



RELEASE

planning/2.8.5/audits/FO3/AUDIT_FO3_SPINE.md

sha256 f20c4464d7aa4af0 · 14348 bytes · original held in the private archive

# AUDIT — FO3 Pip-Boy Spine (U0 / U1 / U2) > Independent Opus audit (Protocol 8 stage 3). Read from the diff first; commit > messages consulted only afterward. Base `1bd38ed` → head `95c5b1f` on `dev`. > **READ-ONLY — nothing fixed, nothing committed.** Scratch render harness lives > outside the repo. ## VERDICT: ✅ ACCEPT WITH FOLLOW-UPS The three commits do exactly what the build plan scoped, no more and no less, and the implementer's claims hold up under independent verification. NV is genuinely untouched (proven by me, not accepted from the claim). The gate was **strengthened, not weakened** — the one narrowed guard and the one rewritten matcher are both legitimate. Editing survives on all boards. 3103 tests pass (I ran them). The only real follow-up is **owner expectation management**: the spine is a faithful functional *skeleton*, not yet a finished Pip-Boy — as the plan intended and the implementer honestly disclosed. --- ## 1. Is NV genuinely untouched? — YES, mechanically proven ✅ (BLOCKER-class, cleared) I booted the real app at 780×360 under both games via a headless file:// render (styles load correctly that way — see §8) and read computed styles back: | Probe | NV result | Verdict | |---|---|---| | `[data-subtab]` stamped on boards | **0** | axis dormant ✅ | | `.subtab-active` present | **0** | ✅ | | `#navkey-operator` border-radius | **5px** (the shared keycap, NOT the FO3 dome's `50%`) | FO3 dome skin never applied ✅ | | `#fo3TopStrip` / `#fo3SubtabRail` display | **none / none** | invisible to NV ✅ | | DIR keycap | **display:flex** (present) | ✅ | | **`--bezel-wire`** | **`#ffb642`** (NV amber) — NOT FO3's `#ffa030` | **no custom-property leak** ✅ | | horizontal overflow | **0** | ✅ | **The custom-property leak the audit flagged does not exist.** `--bezel-wire` / `--bezel-wire-rgb` are the only custom properties declared in the new file, and both sit inside the `[data-game='FO3']` block (css/60-fo3-pipboy.css:38-41) — declared on the FO3-matched `<html>` element, never `:root`. The live NV root still reports `#ffb642`. Confirmed. **Every rule in the new file is `[data-game='FO3']`-scoped.** I parsed all 30 rule blocks by hand: every selector — including inside the single `@media (orientation:landscape)` block, and every comma-separated part — begins with `[data-game='FO3']`. No `:root`, `:where()`, `:has()`, or bare selector escapes the scope. **Zero bytes changed in the 12 existing CSS files** — `git diff --stat` shows only `css/60-fo3-pipboy.css` added (+313). Confirmed independently, matching the implementer's claim. **New shared markup renders identically for NV.** `#fo3TopStrip` and `#fo3SubtabRail` were added to shared markup, but both ship the HTML `hidden` attribute and are only revealed by FO3-landscape CSS — for NV they are `display:none`, take zero space, produce zero overflow (render-confirmed). This is the "new markup hidden by CSS" trap done correctly. The NV screenshot is the unchanged terminal: flat numbered keycaps `[1]`–`[6]`,`[0]`, DIR present, accordion boards, NV telemetry LCD. --- ## 2. No game branch / no forked pipeline — CONFIRMED ✅ - The three new functions (`_applyRailGrouping`, `selectSubtab`, `_applyRails`) carry **no `FO3`/`FNV`/`FO4` literal** (grep-confirmed; also locked by new test 223.1b). The axis switches entirely on `getIdentity().rails` **truthiness** — a presence-of-data check, exactly the sanctioned pattern (Protocol 38). - **The render pipeline did not fork.** All 22 boards keep their single shared markup and their existing `render*()` path. The second axis is a `data-subtab` attribute stamp + a `.subtab-active` class toggle + **one** FO3-scoped CSS `display:none` rule. No parallel FO3 renderer exists. - One deviation from the plan's literal wording, and it is a **correct** one: the plan said call `_applyRails()` from `selectSubsystem()`; the implementer placed it in `switchTab()` instead, because `switchTab()` is the true choke point every entry path (including the boot-time `initTabs()`) funnels through — `selectSubsystem()` is bypassed on boot. Without this, "reload restores the last sub-tab" would silently fail on every fresh load. This is the plan-review correction stage 2 is supposed to produce, and it's covered by tests 223.9/223.10. --- ## 3. Editing survives on ALL boards — CONFIRMED ✅ (BLOCKER-class, cleared) - Render-verified: on a visible FO3 board, an editing control reports `pointer-events: auto`. - The austere board re-skin (css:291-300) sets **only** cosmetic properties (`border-radius`, `background`, `border-color`) on `.bay-board`/button/input — no `display:none`, no `visibility:hidden`, no `pointer-events:none`. Guarded by test 224.6. - Every rail'd board is reachable: the 5 sub-tab names per subsystem are all unique across all three rails (STATUS/SPECIAL/SKILLS/PERKS/GENERAL · MANIFEST/CRAFT/BARTER/ SQUAD/CURIO · MAP/QUESTS/NOTES/LOG/QUERY), so the global `.subtab-active` toggle selects exactly the intended group; each group is one tap on its rail button. - No-rail boards (weigh bridge, 3× CHASSIS, 4× SETTINGS) carry no `data-subtab`, so the hide rule can't match them — they stay stacked exactly as today. The no-rail allowlist is exhaustively derived from index.html (test 222.7), so no future board can fall through both lists silently. - **Nothing goes read-only.** The only `display:none` in the file are on decorative bits (`.nk-led`, `.nk-num`, `#navkey-settings::after`) and the DIR keycap (still fired by hotkey `[0]`) — never an editing control. --- ## 4. Was the gate weakened? — NO, it was strengthened ✅ (BLOCKER-class, cleared) **Suite 181.20 narrowing — legitimate.** The old guard string-matched the *whole* `state.js` for `opVital|opSpecial|opHarness|…`. U0 legitimately added those board ids as **data** inside `GAME_DEFS.FO3.identity.rails`, which would false-positive the whole-file match. The narrowing routes through `extractStateKeys()` (a real brace-walk of `let state = {…}`), so it still enforces the true invariant — "no new campaign-state field" — without colliding with identity data. I verified `extractStateKeys` returns a populated set of real state fields, so the assertion is **not vacuous**; it passes because the invariant genuinely holds, not because it was defanged. **Rewritten CSS matcher (224.3d) — genuinely stronger.** The comment documents (and I confirm the reasoning) that the *old* count-based check passed even after the FO3 hide rule's selector was deliberately unscoped — a count can't distinguish "one scoped rule swapped for an unscoped one" from "totals still add up." The replacement is a real brace-depth parse that asserts **every** top-level rule inside the media block is FO3-scoped on **every** comma-separated part. This catches a class the old one missed. Not loosened — tightened. **+101 tests are real guards, not padding.** Suites 222/223/224 run the **real extracted function bodies** in a `vm` sandbox against a fake DOM/MetaStore (not reimplementations), and cover: rails shape, NV/FO4 carry no rails key, every id resolves to one real board on the right tab, the exhaustive no-rail allowlist, the axis-inert-for- NV no-op, per-subsystem persistence + reload restore, stale-pref fallback, the choke-point call ordering, CSS scoping, zero image assets, and the board re-skin never disabling a control. **224.10 records a genuine live-browser regression** (the sub-tab buttons stacked full-width because the global `button{width:100%}` won — fixed with `width:auto`, locked) — Protocol 42 applied honestly. **I ran the runner: `ALL 3103 TESTS PASSED`.** Lint clean on all three changed JS files. --- ## 5. Cascade slot & registration — CORRECT ✅ `css/60-fo3-pipboy.css` sits after `55-feedback-animations.css` and before `99-mobile.css` in: index.html `<link>` order, `sw.js` ASSETS, `check-boot-chain.js` CANONICAL_CSS_ORDER (count 12→13), and the derived `CSS_SPLIT_FILES`. `CACHE_NAME` bumped `r12`→`r15` (monotonic across U0/U1/U2 served-file commits). eslint globals added for the 3 new functions. **No new `<script>` tag**, so LOAD-ORDER-GUARD is correctly untouched; test.html/repomix correctly need no change (plan §7). CODE_MAP.md lives under gitignored `library/` — not verifiable from the committed diff (plan says update it in the same commit; taken on trust). --- ## 6. Accessibility — sound, one caveat ✅/NOTE - Sub-tab buttons: `role="tab"` + `aria-selected` (dynamic). Rail: `role="tablist"` + `aria-label="Sub-tabs"`. Re-skinned keycaps keep their existing `role="tab"` — only the skin changes, so the tablist ARIA is intact. - Hotkeys `[1]`–`[5]`/`[0]` and `#go=` deep-links route through the **unchanged** `selectSubsystem()`/`SHORTCUT_ROUTES` — the only diff hits are in comments; no routing logic changed. - `.nk-num` is visually hidden but the hotkey number stays in the button's aria-label; `#bezelTelemetry` is visually clipped but kept in the a11y tree (`aria-live`) so a screen-reader user still hears updates. - **NOTE (not a blocker):** I did not run the push-boundary axe baseline-diff (it's a gate step, not part of this static audit). The markup is structurally correct, but a second-level tablist nested near the first is worth a screen-reader sanity pass on real hardware before the casing units (U3/U4) build on it. --- ## 7. Does it actually look like a Pip-Boy? — Honest skeleton, NOT yet the finished machine ⚠ NOTE (owner call) Blunt read of the actual render (screenshot captured): **What's there and works:** the bottom casing bar shows a **lit amber OPERATOR lamp**, two dark unlit domes (OPERATIONS/DATABANK), a ribbed **radio knob** (UPLINK), a white-face **gauge** (CHASSIS), and a **flick toggle** (SETTINGS) — the Pip-Boy hardware vocabulary reads correctly. Inside the glass: the austere green boxed **top strip** (`LVL 1 · HP 100/100 · Wg 1.0/200 · RAD 0 · Caps 0`, live data) and the **sub-tab rail** (STATUS·SPECIAL·SKILLS·PERKS·GENERAL, active boxed, in one inline row). This is recognizably the *beginning* of a Pip-Boy, and it is a real, faithful step. **What's not there yet:** the **top half of the screen is still the generic RobCo terminal** — the "UNIFIED OPERATING SYSTEM TERMINAL UNIT" header, the governor dial, the PWR/UPLINK/FAULT lamps — not a Pip-Boy hood. The controls are a **flat bottom strip**, not the real device's left-rail-gauge/knob + bottom-lamps + right-toggle layout. There is no chunky device body wrapping the green screen. So as a *complete picture* it does **not yet** read as "a genuine Pip-Boy recreation." **My judgment: this is an honest scoping call, accurately disclosed** — the CSS header, the CHANGELOG ("early, functional pass; a fully dressed casing … comes later"), and the README ("_early, in progress_ … a fully dressed casing and the Vault Boy status view are still to come") all say exactly this, and the build plan §5/§6 explicitly defers the Vault Boy (U3) and the multi-column casing dressing (U4). Against the spine's own contract (plan §6 U2 AC), **every criterion is met**. It is not a failed Pip-Boy; it is an intentionally partial one. **Follow-up for the owner (the reason this is ACCEPT-WITH-FOLLOW-UPS, not clean ACCEPT):** the owner rejected three prior designs for not reading as a Pip-Boy, so he should judge this as the **functional skeleton**, not the finished machine — and specifically **view it on his real phone in landscape + installed PWA** (the plan's explicit Protocol 29-class gate) before forming a verdict. If he expects the U2 spine alone to look like the mockup, he will be underwhelmed; that gap is U3+U4, by design. Recommend confirming he's aligned on "skeleton first" before U3/U4 proceed. --- ## 8. The dev-server "CSS served as JS" bug — harness, not the app ✅ Confirmed a tooling/MIME problem, not an app defect. The app loads CSS via ordinary `<link rel="stylesheet">` tags; my file:// render applied all styles correctly (the FO3 casing rendered, NV stayed unstyled-by-FO3). Nothing in the app's own CSS loading is implicated. --- ## 9. `hidden` attribute vs `display:none` — holds ✅ Both `#fo3TopStrip` and `#fo3SubtabRail` ship the HTML `hidden` boolean attribute (tests 223.11b / 224.9b), so before any FO3 CSS applies they are `display:none` — no unstyled fascia can flash, and nothing decorative is visible pre-CSS. Render-confirmed `display:none` for NV with zero overflow. Correct. --- ## 10. Other notes to a colleague (all minor — NOTE severity) - **Latent test-gap (NOTE):** the scoping guard (224.3b/224.3d) checks rules *inside* the media block and counts `[data-game='FO3']` occurrences *before* it as `==1`, but does **not** structurally reject a future `:root{ --x }` custom-property declaration added before the media block. The current file is clean (no such leak), so this is a latent gap, not a live defect — worth tightening when U3/U4 next touch the file (assert the pre-media region contains exactly the one `[data-game='FO3']` token block and nothing else). - **Cosmetic no-op (NOTE):** `_applyRails()` now runs `rail.innerHTML = ''` on **every** NV tab switch (a hidden, already-empty element). Harmless and render-confirmed inert; noting only for completeness. - **Pre-existing prose (NOTE, not this build):** CHANGELOG line ~25 (the PS-mirror deletion entry) still says "all 3002 tests" in its narrative. It predates this build and describes that change's own moment; not introduced here, but a pedantic sync some may want. --- ### Bottom line Clean, disciplined, honestly-reported work. NV is provably untouched (no CSS custom- property leak). No game branch, no forked render pipeline. Editing survives everywhere. The gate was strengthened, not weakened — the narrowed 181.20 and the rewritten CSS matcher are both legitimate, and the +101 tests are real behavioral guards. 3103 pass. The single thing to actively manage is the owner's expectation: **this is the faithful functional skeleton the plan scoped, not the finished Pip-Boy** — best judged on real hardware, with U3 (Vault Boy) and U4 (full casing) understood as what closes the visual gap.
STAMP · generated for RELEASE v2.8.5 commit 06e5180 (06e51801b38a) · archive input-tree hash c07fbfbdd2e1ddeb · 754 files · no wall-clock timestamp (regenerates identically when nothing changed).