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

GAME_AGNOSTICISM_AUDIT.md



RELEASE

planning/2.8.0/audits/GAME_AGNOSTICISM_AUDIT.md

sha256 b5d0fb0c0b5a46f2 · 20420 bytes · original held in the private archive

# GAME_AGNOSTICISM_AUDIT.md — RobCo U.O.S. Stage A9 > **FILED UNDER 2.8.0 — PRODUCED-BY, NOT MEASURED-VERSION.** This document *measures* an earlier codebase > (see its baseline stamp below), but it was *produced* as part of the audit/planning phase that became the > 2.8.0 overhaul. Planning artifacts are filed by the release whose work produced them, never by the version > they discuss. The version in the title or baseline is the code it examined, not the folder it belongs in. > (A 2.5.0 and 2.6.0 folder do exist, holding feature designs for features that shipped in those releases. There > is no 2.7.0 folder because no artifact has been identified as produced during 2.7.0.) > **PLAN-ONLY.** Untracked (matches `.gitignore` `*_AUDIT.md`). Implements nothing, commits nothing. > Baseline: `origin/main` @ `fe380e3` — v2.6.0, cache `robco-terminal-v2.6.0-r1`, 1078 tests / 89 suites. > **Goal:** every feature data-driven per-game via `GAME_DEFS` + the active registry, so adding a 3rd game (FO4, Round 3) = **data + one `GAME_DEFS` entry, ZERO feature-code changes.** > **The test for every finding:** *"would this survive adding a 3rd game with NO change to this code?"* If no → finding. > **Distinction held throughout:** game-agnostic = the MECHANISM generalizes to N games. Game-SPECIFIC features (Traits FNV-only, Lincoln FO3-only, Magazines FNV-only) are FINE **as long as** they ride a generic per-game pattern (`GAME_DEFS` flag + per-game registry) a future game could opt into. The violation is hardcoding a *game literal*, not having a per-game feature. --- ## 0. WHAT IS ALREADY AGNOSTIC (the good baseline — do not regress) The architecture is mostly right; the violations are leaks around a sound core. | Mechanism | Where | Why it's agnostic | |---|---|---| | **`GAME_DEFS` config layer** | `state.js:225` | One entry per game: `factions`, `skillKeys`, `usesKarmaCenter`, `collectibleLabel`, `hasTraits`/`hasMagazines`/`tracksLincoln`, `calendar`, `ai.*`. Adding a game = add an entry. | | **`_activeDef()` + getters** | `state.js:277,289,293` | `getSkillKeys()`/`getFactionRegistry()` resolve from the active def — N-game safe. | | **Per-game feature flags** | `_activeDef().hasTraits` / `.tracksLincoln` / `.hasMagazines` (`ui-render.js:731,825,983`) | Game-specific trackers gate on a **generic flag**, not a literal — the correct pattern. A 3rd game declares `hasTraits:true` to opt in. | | **Karma-vs-Faction panel toggle** | `_updateContextPanels()` (`ui-render.js:1931`) via `usesKarmaCenter` | Flag-driven, not `ctx==='FO3'`. | | **World map** | `renderWorldMap()` reads `FALLOUT_REGISTRY.zones` (`ui-render.js:1245`) | Per-game registry (boot-loaded) — agnostic. | | **Skills render** | `renderSkills()` iterates `getSkillKeys()` (`ui-render.js:483`) | N-skill-set safe. | | **Calendar** | `_activeDef().calendar` (`ui-render.js:276,367`) | Per-game epoch. | | **AI directive skill/faction/triggers** | `GAME_DEFS[ctx].ai.skillSystemText` etc. (`api.js:111,120,150`) | Sourced from the def. | | **Migrate fallback** | `if (!GAME_DEFS[s.gameContext]) s.gameContext='FNV'` (`state.js:541`) | Validates against `GAME_DEFS` keys — correct fail-safe. | **Implication:** the fix list below is mostly **behavior-neutral refactors** that route existing literals through the already-present `GAME_DEFS`/registry — low risk, the dual-runner suite is the safety net. --- ## DELIVERABLE 1 — PROTOCOL TEXT (for RULES.md + CLAUDE.md) > ## Protocol 38 — Game-Agnostic Architecture (data-driven per-game) > > Every feature must be **data-driven per game** through `GAME_DEFS` + the active per-game registry (`FALLOUT_REGISTRY`, `databaseCSVs`) resolved via `_activeDef()` / `getSkillKeys()` / `getFactionRegistry()`. **Adding a game = data + one `GAME_DEFS` entry + its two data files (`db_XX.js`, `reg_XX.js`), with ZERO feature-code changes.** > > **Rules:** > 1. **Never hardcode a game literal in feature code.** No `'FNV'`/`'FO3'` string in branching, list membership, or config selection outside the sanctioned declaration sites (below). > 2. **Never assume exactly two games.** No two-element `{FNV, FO3}` map, no `['FNV','FO3']` array, no `ctx === 'FO3' ? … : …` two-way branch where the `else` silently means "the other game." Iterate `GAME_DEFS` / read the active def instead. > 3. **Validate context against `GAME_DEFS`, not a literal allowlist.** Use `GAME_DEFS[ctx] ? ctx : 'FNV'` — never `ctx === 'FO3' ? 'FO3' : 'FNV'` (which collapses a 3rd game to FNV) and never `if (ctx !== 'FNV' && ctx !== 'FO3') return` (which rejects it). > 4. **Game-specific features are allowed — via the generic pattern.** A feature that exists in only one game (Traits, Lincoln, Magazines) MUST gate on a `GAME_DEFS` flag + a per-game registry array, never on a game literal. A future game opts in by declaring the flag. > 5. **Per-game tuning lives in `GAME_DEFS`, not in code.** Skill sets, faction lists, starting inventory, VATS region tables, barter coefficients, calendar epochs, collectible labels — all are `GAME_DEFS`/registry fields. > 6. **Sanctioned game-literal sites (the ONLY allowed exceptions):** (a) the `GAME_DEFS` declaration block and `SKILL_KEYS*`/`FACTION_REGISTRY*` constant declarations in `state.js`; (b) the **boot manifest map** (the single static game→data-files map the boot loader iterates); (c) the `|| 'FNV'` fail-safe default and the migrate/legacy-v7→v8 bootstrap; (d) comments/doc strings. Everything else is a violation. > > **Why:** FO4 (and any future game) must be a near drop-in. Every hardcoded game literal is a place the next game silently breaks or is ignored. Guarded by **Suite 89** (Protocol 20 static guard) and Protocol 15 runner parity. --- ## DELIVERABLE 2 — GATE GUARD SPEC (Suite 89 — Game-Agnosticism; both runners) A **static source guard** added to **both** `tests/check-persistence.js` and `tests/check-persistence.ps1` (Protocol 15 parity, Protocol 2a count sync), modeled on the existing Protocol-20 structural guards. All checks are static (no runtime). It scans the **served feature JS** (`js/api.js`, `js/ui-core.js`, `js/ui-render.js`, `js/ui-audio.js`, `js/ui-saves.js`, `js/ui-account.js`, `js/cloud.js`) and `index.html`'s inline boot script, **after stripping comments and string-literal doc blocks**. **Allowlist (sanctioned sites — excluded from the scan):** - `js/state.js` entirely for the `GAME_DEFS`/`SKILL_KEYS`/`SKILL_KEYS_FO3`/`FACTION_REGISTRY*` declaration region (the per-game config source of truth). - A single named **boot manifest** map in `index.html` (e.g. `GAME_FILES`) — the one legit game→files map. - The `|| 'FNV'` fail-safe default token and the legacy `robco_v7`→`v8` migration bootstrap. **Assertions (each FAILS the build):** 1. **No game-coercion anti-pattern:** regex `===\s*['"]FO3['"]\s*\?\s*['"]FO3['"]\s*:\s*['"]FNV['"]` (and the FNV-mirror) must not appear outside the boot manifest. *(Catches GA-2.)* Required form: `GAME_DEFS[x] ? x : 'FNV'`. 2. **No literal allowlist branch:** `!==\s*['"]FNV['"]\s*&&\s*[^)]*!==\s*['"]FO3['"]` must not appear. *(Catches GA-3.)* Required form: `if (!GAME_DEFS[ctx]) return;`. 3. **No two-game ternary in feature code:** any `ctx === 'FO3' ?` / `gameContext === 'FNV' ?` (and `: 'FO3'` / `: 'FNV'` tails) in the scanned files → fail. *(Catches GA-4, GA-5, GA-6.)* 4. **No two-game array/map literal:** a single array or object literal that contains **both** `'FNV'` and `'FO3'` as elements/keys (outside the allowlist) → fail. *(Catches GA-1 if the boot map isn't the sanctioned one; catches inline `['FNV','FO3']`.)* 5. **No hardcoded faction-key array in feature code:** an array literal of ≥3 known faction keys (e.g. contains `'ncr'`+`'legion'` or `'enclave'`+`'outcast'`) → fail; must use `getFactionRegistry()`. *(Catches GA-4.)* 6. **No hardcoded skill-key array outside `state.js`:** an array/object literal enumerating ≥3 skill keys (e.g. `guns`/`small_guns`/`energy_weapons`) in feature code → fail; must derive from `getSkillKeys()`. *(Catches GA-10.)* 7. **Positive presence guards:** `GAME_DEFS`, `_activeDef`, `getSkillKeys`, `getFactionRegistry` all defined in `state.js`; the boot manifest map present in `index.html`. *(Lost-safeguard guard per Protocol 20.)* **Count impact:** ~10–12 new assertions → 1078 → ~1089. Sync both runners + all doc count locations (Protocol 2a). Suite numbering: **Suite 89** (Suite 88 = UI Consistency from A7). --- ## DELIVERABLE 3 — FIX LIST (existing non-agnostic spots) > Severity: **HIGH** = hard-blocks or silently breaks a 3rd game · **MEDIUM** = game-3 feature degraded/wrong (some are latent FO3 bugs today) · **LOW** = cosmetic/peripheral · **INFO** = note only. > Tier: **BUILD-NOW** = behavior-neutral refactor · **SPEC-FIRST** = needs design/contract care · **SKIP** = correct as-is. ### Counts: HIGH 4 · MEDIUM 4 · LOW 2 · INFO 1 — **11 findings** (CRITICAL 0) --- **GA-1 · [HIGH] · Boot loader hardcodes the db/reg file map** — `index.html:1914–1917` - `ctx === 'FO3' ? ['js/db_fo3.js','js/state.js','js/reg_fo3.js'] : ['js/db_nv.js','js/state.js','js/reg_nv.js']` — a 3rd game cannot declare its data files; the `else` silently loads FNV. - **Fix:** introduce a single static **boot manifest** `GAME_FILES = { FNV:[…], FO3:[…] }` and select `GAME_FILES[ctx] || GAME_FILES.FNV`. This is the **one sanctioned** game→files map (it must precede `state.js`/`GAME_DEFS`, so it can't live in `GAME_DEFS` — chicken-and-egg). Adding a game = one manifest line + two files. - **Files:** `index.html` (boot script), `tests/check-persistence.{js,ps1}` (Suite 56 load-order guard update + Suite 89 sanctioned-map check), `sw.js` (cache). · **Tier:** SPEC-FIRST (boot-order sensitive; Suite 56). · **Severity:** HIGH. **GA-2 · [HIGH] · Game-coercion anti-pattern collapses game 3 → FNV** — `index.html:1909`, `api.js:35`, `api.js:918`, and the `|| 'FNV'` coercions at `ui-core.js:101,379,606,607`, `ui-saves.js:113,158,159`, `ui-account.js:9`, `cloud.js:260,262,363,381` - `(state.gameContext) === 'FO3' ? 'FO3' : 'FNV'` and `x === 'FO3' ? 'FO3' : 'FNV'` silently map **any** non-FO3 value (including a 3rd game) to FNV. - **Fix:** `GAME_DEFS[ctx] ? ctx : 'FNV'` everywhere the value is *validated*; the plain `|| 'FNV'` fail-safe defaults (where the value is just a missing-read fallback) are **fine and stay** (sanctioned). Distinguish: coercion-that-discards a valid game = fix; default-for-absent = keep. - **Files:** `js/api.js`, `js/ui-core.js`, `js/ui-saves.js`, `js/ui-account.js`, `js/cloud.js`, `index.html`, `tests/check-persistence.{js,ps1}`, `sw.js`. · **Tier:** BUILD-NOW (behavior-neutral for the two current games). · **Severity:** HIGH. **GA-3 · [HIGH] · `onGameContextChange` hardcoded allowlist rejects game 3** — `ui-core.js:785` - `if (ctx !== 'FNV' && ctx !== 'FO3') return;` — the context switcher refuses any 3rd game. - **Fix:** `if (!GAME_DEFS[ctx]) return;`. - **Files:** `js/ui-core.js`, `tests/check-persistence.{js,ps1}`, `sw.js`. · **Tier:** BUILD-NOW. · **Severity:** HIGH. **GA-4 · [HIGH] · `_nativeCrossroads` hardcoded faction-key arrays** — `api.js:929–957` - Two-element `ctx === 'FO3' ? [12 enclave/bos/…] : [11 ncr/legion/…]` faction-key lists. Game 3 shows no factions in the [CROSSROADS] analysis. - **Fix:** iterate the active registry — `getFactionRegistry().map(f => f.key)` (the arrays are literally the registry keys, so this is behavior-neutral for FNV/FO3). - **Files:** `js/api.js`, `tests/check-persistence.{js,ps1}`, `sw.js`. · **Tier:** BUILD-NOW. · **Severity:** HIGH. **GA-5 · [MEDIUM] · AI directive tracker blocks gated by game literal, not flag** — `api.js:158,166,174` - `ctx === 'FO3' ? Lincoln… : ''`, `ctx === 'FNV' ? Traits… : ''`, `ctx === 'FNV' ? Magazines… : ''`. These ARE game-specific features (correct to vary), but they're keyed off the literal instead of the generic flag — a 3rd game can't declare its own trackers without editing this code. - **Fix:** gate each on its `GAME_DEFS` flag (`_activeDef().tracksLincoln`/`.hasTraits`/`.hasMagazines`) and source the directive text from a new per-game `ai.trackerDirectives` field in `GAME_DEFS`. This is the exemplar of "game-specific feature on a generic pattern." - **Files:** `js/state.js` (`GAME_DEFS.*.ai.trackerDirectives`), `js/api.js` (`getSystemDirective` assembles from flags), `tests/check-persistence.{js,ps1}` (Protocol 14 AI-contract test). · **Tier:** SPEC-FIRST (AI contract text — Protocol 14). · **Severity:** MEDIUM. **GA-6 · [MEDIUM] · `seedNewCampaignInventory` hardcoded FNV-only** — `ui-core.js:1300` - `if (ctx !== 'FNV') return;` then pushes the Vault 13 Canteen. Starting inventory is per-game data hardcoded as a literal branch. - **Fix:** a per-game `GAME_DEFS[ctx].seedInventory` array (FNV declares the canteen; FO3/game-3 declare their own or `[]`). The function iterates that array. - **Files:** `js/state.js` (`GAME_DEFS.*.seedInventory`), `js/ui-core.js`, `tests/check-persistence.{js,ps1}`, `sw.js`. · **Tier:** BUILD-NOW. · **Severity:** MEDIUM. **GA-7 · [MEDIUM] · VATS body-region modifier/AP table hardcoded "FNV standard"** — `ui-core.js:1104` (in `showVATSOverlay`) - The 8-region `{mod, ap}` table is hardcoded FNV values; a 3rd game (or even FO3 specifics) can't vary it. - **Fix:** move to `GAME_DEFS[ctx].vats.regions`. **Folds into the planned WU-N1 (VATS) spec** — build it data-driven from the start. - **Files:** `js/state.js` (`GAME_DEFS.*.vats`), `js/ui-core.js`, `tests/check-persistence.{js,ps1}`, `sw.js`. · **Tier:** SPEC-FIRST (folds into WU-N1). · **Severity:** MEDIUM. **GA-8 · [LOW] · `[CONTEXT: FNV] / [CONTEXT: FO3]` help hints hardcoded** — `ui-core.js:1918–1919` - Two literal hint lines; a 3rd game isn't advertised. - **Fix:** iterate `Object.values(GAME_DEFS).map(d => '> Type [CONTEXT: '+d.id+'] for '+d.label)`. - **Files:** `js/ui-core.js`, `tests/check-persistence.{js,ps1}`, `sw.js`. · **Tier:** BUILD-NOW. · **Severity:** LOW. **GA-9 · [LOW] · Legacy v7→v8 migration bootstraps `campaigns: { FNV: state }`** — `ui-core.js:118–125` - The single-game→multi-game migration hardcodes FNV as the legacy bucket. - **Verdict:** **SKIP** — legacy single-game saves predate FO3 and were genuinely FNV-era; this is the sanctioned historical-bootstrap exception (Protocol 38 rule 6c). Correct as-is. · **Severity:** LOW/INFO. **GA-10 · [MEDIUM] · VATS combat-skill set hardcodes FNV keys (latent FO3 bug)** — `ui-core.js:1070–1076` - `combatSkills = { guns: skills.guns || skills.small_guns, energy_weapons, melee_weapons, sneak, explosives }`. Uses FNV's `guns`; patches `small_guns` but **omits `big_guns` entirely** → FO3 Big Guns is invisible to VATS today, and a 3rd game's combat skills can't register. - **Fix:** derive combat skills from `getSkillKeys()` filtered by a `GAME_DEFS[ctx].combatSkills` whitelist (per-game). Fixes the live FO3 gap **and** makes it agnostic. - **Files:** `js/state.js` (`GAME_DEFS.*.combatSkills`), `js/ui-core.js`, `tests/check-persistence.{js,ps1}` (regression for the FO3 big_guns gap — Protocol 13), `sw.js`. · **Tier:** SPEC-FIRST (folds into WU-N1; carries a regression test). · **Severity:** MEDIUM. **GA-11 · [INFO] · `usesKarmaCenter` is a binary karma-XOR-faction assumption** — `state.js` flag / `_updateContextPanels` - The mechanism is correctly flag-driven (agnostic), but it encodes "a game has karma OR factions, never both/neither." A future game wanting both, or a third reputation model, would need a richer flag. - **Verdict:** **SKIP / note** — the flag mechanism generalizes; refining the model is a Round-3 design concern (don't design FO4 here). · **Severity:** INFO. --- ## PLANNED-FEATURE GUARDRAIL (bake into each N/F spec) Assessment of the 2.7.0 planned features for agnosticism-as-spec'd: | Unit | Agnostic as-spec'd? | Guardrail to bake in | |---|---|---| | **WU-N1 VATS** | ⚠ **RISK** | Region table (GA-7) + combat-skill set (GA-10) are FNV-hardcoded today. Spec must read `GAME_DEFS[ctx].vats.regions` + `.combatSkills` and `getSkillKeys()`. **GA-7 + GA-10 fold directly into WU-N1.** | | **WU-N2 TRADE** | ✅ mostly | Reads `VENDORS.CSV`/items DB per-game (agnostic). **Guardrail:** the barter coefficient must be `GAME_DEFS[ctx]` data (WU-D4b), not a literal. | | **WU-N3 THREAT** | ✅ | `BESTIARY.CSV` per-game lookup. No literals — fine. | | **WU-N4 CONSULT** | ✅ | `registrySearch`/`lookupItemInDb` per-game. Fine. | | **WU-N5 BIO-SCAN** | ✅ mostly | `CHEMS.CSV` + limb state per-game. **Guardrail:** advisory rules must not hardcode FNV chem names — drive thresholds/names from the chems DB. | | **WU-N6 LOOT** | ✅ | DB `Value` per-game. Fine. | | **WU-F1–F9 capabilities** | ✅ by nature | Device/UI features (Wake Lock, Vibration, Web Share, Badge, Radio synth, boot, log, optics, console) carry no game data. **Guardrail:** **WU-F9 TERMLINK Console** extends `NATIVE_COMMAND_ROUTER` — any data-bearing command must route through the active registry/`GAME_DEFS`, never a game literal (it will be scanned by Suite 89). **WU-F6 Cold-Start Boot** boot text is RobCo-generic (fine). | **Net:** N3/N4/N6 and F1–F8 are agnostic as-spec'd; **WU-N1 is the one real risk** (absorbs GA-7 + GA-10); N2/N5 need a one-line per-game-data guardrail; F9 needs the registry-routing guardrail. --- ## FEEDS THE MASTER_PLAN UPDATE `GAME_AGNOSTICISM_AUDIT.md` → fold the agnosticism work into the **2.7.0 foundation (Phase A)** so every later unit is born on an agnostic base: - **New WU-A4 · Game-Agnostic refactor + Protocol 38 + Suite 89** (Phase A, BUILD-NOW core): GA-2, GA-3, GA-4, GA-6, GA-8 (behavior-neutral literal→`GAME_DEFS`/registry refactors) + the Protocol 38 text + the Suite 89 gate guard. Lands early so Suite 89 guards all subsequent units. - **WU-A5 · Boot manifest** (Phase A, SPEC-FIRST): GA-1 boot file-map → `GAME_FILES` manifest (Suite 56 coordination). - **GA-5** (AI tracker directives) → folds into the api.js directive work (Protocol 14 test), sequence with the Phase-N api.js cluster. - **GA-7 + GA-10** → fold into **WU-N1 (VATS)** as agnosticism acceptance criteria (GA-10 carries a Protocol-13 regression for the live FO3 `big_guns` gap). - **Born-compliant rule extended:** add "passes Suite 89 (no hardcoded game)" to the §1.4 baked-in acceptance criteria for every N/F feature. - Counts: ~2 new Phase-A units (A4, A5) + 2 absorbed into N1 + 1 into the AI directive work → THIS BUILD ~40 → ~42 units. Suite 89 adds ~10–12 tests (1078 → ~1089), synced both runners + docs. --- ## SUMMARY FOR THE OWNER - **Worst offenders (HIGH, all hard-block or silently break a 3rd game):** the **boot loader's hardcoded db/reg file map** (GA-1), the **`=== 'FO3' ? 'FO3' : 'FNV'` coercion** that collapses any new game to FNV across ~12 sites (GA-2), the **context-switcher's literal allowlist** `if (ctx !== 'FNV' && ctx !== 'FO3') return` (GA-3), and the **crossroads hardcoded faction-key arrays** (GA-4). - **The architecture is fundamentally sound** — `GAME_DEFS` + `_activeDef()` + per-game flags is the right pattern; most violations are behavior-neutral literal→registry refactors the dual-runner suite protects. - **Planned features:** N3/N4/N6 + F1–F8 are agnostic as-spec'd; **WU-N1 VATS is the one real risk** (hardcoded region table + combat-skill set — GA-7/GA-10, the latter a live FO3 `big_guns` bug); N2/N5 need a per-game-data guardrail; F9 console needs registry-routing. - **Counts:** HIGH 4 · MEDIUM 4 · LOW 2 · INFO 1 = **11 findings** (CRITICAL 0). - **Deliverables produced:** Protocol 38 text (data-driven per-game; never hardcode a game; never assume two), the Suite 89 gate-guard spec (7 static assertions + sanctioned-site allowlist, both runners), and the triaged fix list with file-touch lists. - **Next:** feeds a MASTER_PLAN update — adds WU-A4 (agnostic refactor + Protocol 38 + Suite 89) and WU-A5 (boot manifest) to the Phase-A foundation, folds GA-7/GA-10 into WU-N1, and extends the born-compliant rule with "passes Suite 89." --- *GAME_AGNOSTICISM_AUDIT.md — Stage A9, plan-only. No code changed, nothing committed. FO4 (Round 3) is the forward target; nothing here designs it — these fixes only ensure nothing blocks it.*
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).