BUGS
bugs/cross-game-registry-leak/record.md
sha256 f5fd27daeae6ffe7 · 4830 bytes ·
original held in the private archive
---
title: The Cross-Game Registry Leak
status: IN-FLIGHT
repo: app
observed: 2026-07-14
caught_by: owner report, then independent audit that reproduced real data loss
one_line: Loading a cross-game save switched the state in place but left the boot-time registry stuck on the old game — corrupting every registry-backed surface, and silently deleting real campaign data.
guard_location: js/ui/ui-saves.js · _applySlotEnvelope() cross-game branch (persist robco_v8 + reload, Protocol 22)
verified: red→green in a real Playwright browser both directions; regression Suite 228 + render-check.mjs end-to-end
exhibited: true
lesson: Boot-time-only globals cannot be switched in place; an in-place cross-game apply is a data-integrity bug, not a cosmetic one. (Protocol 22 — no parallel apply path.)
awaiting_tag: v2.8.5
provenance:
- commit 2210b57 (app repo) — "fix(saves): cross-game local-slot load left the registry stuck on the old game"
- planning/2.8.5/audits/AUDIT_registry_leak.md (Stage 3 independent audit)
- planning/2.8.5/mockups/FO3/audit-crossgame-BUGGY.png and audit-crossgame-FIXED.png
---
## What happened
`_applySlotEnvelope()` (the shared core behind LOAD SLOT and VERSION RESTORE) detected a
cross-game context mismatch, confirmed it, and then applied the switch **in place**
(`loadUI()` only, no reload). But `FALLOUT_REGISTRY` and `databaseCSVs` are **boot-time
globals**, selected exactly once at boot by `index.html`'s `GAME_FILES` manifest (one of
`reg_nv.js`/`reg_fo3.js` + one of `db_nv.js`/`db_fo3.js` per boot). An in-place apply left
those globals **stuck on the old game's data** while `state.gameContext` silently flipped
to the new one. Every registry-backed surface was then serving the wrong game:
location/item/quest/perk autocomplete, the native LOOT/THREAT/CONSULT lookups, and the
AI's own `databaseCSVs` system context — not just the location box the owner first
noticed.
**Correction to the record this specimen was drawn from.** The museum mockup (specimen
08) framed this as a *cosmetic* defect — "a New Vegas-skinned terminal showing a Fallout 3
save, the wrong wasteland's chrome around the right save." That is wrong. This is a
**data-integrity** defect. The independent audit reproduced **real campaign-data loss** on
the pre-fix code: the AI-import path (`autoImportState`) validates state arrays against
`FALLOUT_REGISTRY` and **drops every entry the registry doesn't recognise**, on a write —
so with a stale registry the campaign's own FO3 collectibles (`Strength`, `Perception`)
matched nothing in the loaded NV registry and were silently emptied and persisted. The
implementer's "only lookups and suggestions, never what got written" claim was overturned
by the audit.
## How it was caught
Owner report of wrong-game autocomplete, then a Stage-3 independent audit (Protocol 8)
that reproduced the bug red→green in a real browser **and** independently found the
data-loss the implementer had ruled out.
## The guard
The cross-game branch now persists `robco_v8` synchronously and **reloads**, exactly like
every other cross-game apply path (`onGameContextChange`, `loadCloudSave`,
`restoreCloudSaveVersion`) already did — Protocol 22, no parallel apply path. Boot then
re-selects the correct `reg_*.js`/`db_*.js` via the manifest. The same-game (common) path
is unchanged: still an in-place `loadUI()`, no reload. Unsaved state survives the reboot
because the fix sets `window._loadingSave = true` and persists before the reload, and the
`beforeunload` flush honours that flag.
## What the guard costs
A full page reload on every cross-game load/restore — the heavier apply path — accepted
because it is the only way to reselect boot-time globals honestly. The same-game path
keeps the fast in-place apply, so the cost is paid only when the game actually changes.
## Containment
Reproduced then resolved in a real Playwright browser, both directions, plus fresh-boot
baselines and the unregressed same-game fast path. Regression coverage landed in the same
commit (Protocol 13): `tests/robco-diagnostics.js` Suite 228 (6 tests, including a
vm-sandbox proof of the real extracted `_applySlotEnvelope()`) and `tests/render-check.mjs`
(end-to-end). **Not yet SEALED:** this is 2.8.5-era work; the app is pinned to `v2.8.0`
and `v2.8.5` is not tagged. Promote to SEALED when `awaiting_tag` exists
(`git tag --contains 2210b57`).
## Provenance
Commit `2210b57` (app repo) and its full message; `planning/2.8.5/audits/AUDIT_registry_leak.md`
(the audit whose PASS carried "one mandatory record correction" — the data-loss finding
quoted above). The two capture PNGs referenced by the mockup exist under
`planning/2.8.5/mockups/FO3/`. This record deliberately **replaces the mockup's cosmetic
framing** with the audited data-integrity account.
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).