RELEASE
planning/2.8.5/audits/AUDIT_karma.md
sha256 0ea9fafe883a292a · 11552 bytes ·
original held in the private archive
# AUDIT — FO3 Karma Engine rebuild (Protocol 8 Stage 3, independent)
> **Auditor:** Opus, Stage-3 independent audit. **Branch:** `dev` @ `5ed8ba2` (clean).
> **Method:** started from the diff + running app + live fallout.wiki cross-check;
> reproduced the citation guard red-then-green by hand; rendered the Karma Center
> with Playwright directly at 360/412/desktop. The implementer's report was **not**
> read. Snapshot as of 2026-07-15.
> **Commits audited:** `5946e41` (karma engine) + `5ed8ba2` (2.9.0→2.8.0 version revert).
## VERDICT: ✅ PASS
The unit does exactly what it set out to do. The fabricated "ENCLAVE HIT SQUAD" is
gone; the replacement data is real, wiki-cited, and independently verified; the
citation guard genuinely prevents recurrence (I broke it and watched it catch the
break); the engine is deterministic/offline; NV is untouched; the version revert is
complete and the golden-master hashes are correct. Two **minor, non-blocking**
findings (doc-count drift; a pre-existing app-wide portrait font-size gap the unit
inherited) are listed at the end. Neither warrants a return to Stage 2.
---
## 1. IS THE DATA CORRECT AND ACTUALLY CITED? — ✅ YES
### Enclave hit squad is GONE (grepped the whole codebase, not just the karma file)
Every surviving `enclave` reference is legitimate, not the fabrication:
- `state.js:805` — the **faction-reputation registry** entry `{ key:'enclave', name:'Enclave', tier:'major' }`. The Enclave is a real FO3 reputation faction; unrelated to karma hit squads.
- `state.js:1661` — the karma **action** `destroy_enclave_crawler` ("Destroyed the Enclave Mobile Command crawler", +500, cited to Broken Steel's *Who Dares Wins*). A **real** karma event, correctly cited.
- `state.js:1812` — the AI faction-directive text (lists Enclave as a rep faction). Legit.
- Comments in `state.js` / `ui-render-factions.js` that document the removal.
The hit-squad logic (`getKarmaHitSquad`, `GAME_DEFS.FO3.karma.hitSquads`) contains **no Enclave**. Rendered proof (412px, good karma): the only "Enclave" on screen is the cited action label; the hit-squad line reads `[!] TALON COMPANY RISK — PRIVATE CONTRACT`. `fabricatedPattern: false`.
### Corrected mechanic — both directions, neither at Neutral ✅
`hitSquads`: **Regulators @ −250 (evil)**, **Talon Company @ +250 (good)**. `getKarmaHitSquad` returns Regulators at `karma ≤ −250`, Talon at `karma ≥ +250`, `null` between. Behaviorally boundary-exact (Suite 230.9, and my own render: −750 → REGULATORS, +750 → TALON, 0 → NO BOUNTY ACTIVE).
### 90-title table — spot-checked against LIVE fallout.wiki (WebFetch 403-blocked as the build documented; verified via WebSearch across fallout.wiki + fandom mirror + Neoseeker) ✅
| Level | Good | Neutral | Bad | Verified |
|---|---|---|---|---|
| 1 | Vault Guardian | Vault Dweller | Vault Delinquent | ✅ |
| 15 | Urban Legend | Urban Myth | Urban Superstition | ✅ |
| 30 | Messiah | True Mortal | Devil | ✅ |
Also rendered live: L20 good = "Last, Best Hope of Humanity" ✅, L25 bad = "Stuff of Nightmares" ✅, L12 neutral = "Keeper" ✅. The L11 "Capital" (not "Capitol") spelling is an intentional, documented correction consistent with "Capital Wasteland". No invented-looking titles found.
### Companions — exactly 8, correct gates, each cited ✅
Verified against fallout.wiki: **none** = Dogmeat, Charon; **good** = Fawkes, Star Paladin Cross; **neutral** = Butch DeLoria, Sergeant RL-3; **evil** = Clover, Jericho. Every row carries a `src`. Rendered proof: Dogmeat & Charon appear at every alignment; Butch (previously missing entirely) now appears at neutral; Clover/Jericho at evil; Fawkes/Cross at good.
### UNVERIFIED values ship as `null` — none guessed ✅
- `project_purity_self` → `delta: null`, `verificationStatus:'conflict'` (+600 vs +1000 source conflict, both pages cited).
- `good_quest_act` / `evil_quest_act` → `delta: null`, `unverified` ("at least ±50", no exact figure).
- Talon `squadSize: null`, `spawnFrequency: null`; Regulators `spawnFrequency: null` (squadSize `3` is genuinely cited on the Regulator page — verified: "typically operate in three-member squads").
### Every constant carries a `fallout.wiki` src ✅ (spot-checked hitSquads, companions, titlesSrc, all 27 events).
---
## 2. THE CITATION GUARD (Suite 230) — ✅ REAL, and I proved it red-then-green
Read the validator (`_validateKarmaCitations`): it flags (a) any hitSquad/companion/event/titlesSrc lacking a `fallout.wiki` src, (b) an `enclave` faction name, (c) an UNVERIFIED/conflict event with non-null `delta`, (d) an UNVERIFIED hitSquad with non-null spawnFrequency/squadSize, (e) Regulators not at −250 or Talon not at +250. It runs against a corrupted fixture (230.2, RED) **and** the real shipped data (230.3, GREEN).
**I reproduced red-then-green against the REAL data myself** (not the self-contained fixture):
- Replaced the real Regulators entry in `state.js` with a fabricated `{ faction:'Enclave Hit Squad', src:'', spawnFrequency:'daily', … }`.
- Ran the runner → **3 failures**, each naming the exact problem:
- `230.3 ✗ … ["…(Enclave Hit Squad) missing fallout.wiki src","…names an Enclave faction","Regulators missing or not at threshold -250"]`
- `230.4 ✗ [anti-regression]`
- `230.9 ✗ [behavioral] getKarmaHitSquad`
- Reverted → **ALL 3236 PASS**.
The guard is wired to the actual data, not just a green-only fixture. Recurrence is genuinely blocked. Behavioral coverage (230.8–230.11, 230.14) exercises the real engine functions against the real data end-to-end.
---
## 3. DETERMINISTIC / NATIVE / OFFLINE, and NV survived — ✅
- **Zero AI at runtime.** All six engine functions (`getKarmaTier`, `getTitleAlignment`, `getKarmaTitle`, `getKarmaHitSquad`, `getKarmaCompanions`, `applyKarmaEvent`) are pure over `state.karma`/`state.lvl` + `_activeDef().karma`. No `fetch`/`transmit` path.
- **Level clamp correct.** `getKarmaTitle` = `Math.max(1, Math.min(lvl, 30)) - 1`, falling back to the tier label if the array is absent. L31 and L50 both resolve to the L30 title (Messiah/Devil) — no crash, no `undefined`. Verified in Suite 230.8 and by direct render (L50 → Messiah).
- **NV untouched.** `GAME_DEFS.FNV` has **no** `karma` block (Suite 230.5 + my render check: `hasKarmaData:false`). `usesKarmaCenter:false` ⇒ the FO3 Karma Center block stays hidden and the NV swing-needle stays visible; the FO3 data never leaks (Protocol 38). NV slider still writes state (value 500 confirmed). No game literals in `getKarmaHitSquad`/`getKarmaCompanions` — data-driven via `_activeDef()`.
- **Redundancy fix.** The duplicate tier label is gone — "NEUTRAL" appears exactly **1×** on the board (was the F9 double-print). The `stat_karma` slider is never hidden and still overrides manually: setting it to −300 fired `input` and drove `state.karma = -300`, `disabled:false`, `readOnly:false` — edit survives, nothing became read-only. The action picker and the slider sync through one path (`applyKarmaEvent` writes state + slider + `updateKarmaUI` + `renderKarmaCenter`); applying "water_to_beggar" moved 0 → +50 and synced the slider; the null-delta UNVERIFIED event was a correct no-op.
---
## ALSO VERIFIED
- **Version fix complete.** `APP_VERSION = '2.8.0'`; `CACHE_NAME = 'robco-terminal-v2.8.0-r30'`. The only `2.9.0` strings are (a) the `v2.9.0 ENCOUNTER` roadmap refs in `db_*.js`/`ARCHITECTURE.md` (fine) and (b) the Suite 228 `_applySlotEnvelope` mock fixture `APP_VERSION:'2.9.0'` — introduced by unrelated commit `2210b57`, a pre-existing version-comparison fixture (fine). Changelog entries sit under `## [Unreleased]`.
- **Golden-master hashes recomputed correctly.** Diffing the pre-karma commit (`eecf825`) → HEAD, **exactly 2** sha256 rows changed, **both `ctx:'FO3'`** — the 2 rows carrying the corrected `irreversibleTriggers` content (Enclave/Outcast fabrication removed). The 9 FNV rows are byte-identical to pre-existing. Suite 131 is green in my full run, which is definitive proof the committed hashes match the live directive text at `APP_VERSION=2.8.0` (the directive embeds `${APP_VERSION}`).
- **Nothing weakened.** The removed Suite 187 assertions (187.9d/e/f, which tested the *old* flat `karmaCompanions` structure) were **replaced with equal-or-stronger** versions: the new 187.9d checks *both* `getKarmaCompanions` and `renderKarmaCenter` bodies for hardcoded companion literals and **adds Butch DeLoria** to the forbidden set the old test omitted. The eslint change is a single `_KARMA_TIERS: 'readonly'` globals declaration (Protocol 22 cross-file reuse), not a rule relaxation. No assertion was softened.
- **Protocol 22** (extended `renderKarmaCenter` in place — no `renderKarmaCenterNew`/fork), **1** (cache r30), **13** (Suites 230 + 187 are the guarding tests, same commit), **41** (working tree clean; Suite 98 green).
- **CHANGELOG accuracy.** All six user-facing entries are true against the running app (title live-updates, tappable cited actions with UNVERIFIED marked, Regulators/Talon fix, 3 companion-gate fixes + Butch restored, double-print fix, citation-guard note). Plain-English, correct category grouping (Protocol 21).
- **Full gate:** `ALL 3236 TESTS PASSED` on a clean tree.
---
## FINDINGS (minor — non-blocking; not grounds for Stage-2 return)
**F-1 — Doc test-count drift (LOW, Protocol 2a incompleteness, introduced by this unit).**
The count bump 3222→3236 updated the canonical Protocol 2a locations, but **4 prose mentions still read the old count**:
- `CLAUDE.md:205` ("3222-test gate"), `CLAUDE.md:233` ("3222-test pre-commit gate"), `CLAUDE.md:572` ("**229 suites, 3222 tests**"), `README.md:331` ("3222-test count").
These are hits on Protocol 2a's own mandated `Select-String` drift scan and should show `3236` (and `230 suites` for line 572). Cosmetic/internal-doc only; no functional impact. Recommend fixing in a follow-up doc-sync commit.
**F-2 — New karma filter input is sub-16px in PORTRAIT mobile (LOW, pre-existing app-wide, inherited via Protocol 22).**
The new `#karmaEventFilter` (`type="search"`) renders at **13.33px** at 360/412px **portrait** (the owner's primary Android/OnePlus orientation), tripping Protocol 17's ≥16px focus-zoom guard, and shows as a plain white UA box. Root cause: the FO3 16px restyle (`css/60-fo3-pipboy.css:1768`) lives inside `@media (orientation: landscape)`, so portrait falls back to the unstyled base rule.
**However, this is NOT a regression this unit introduced** — `invDrawerSearch`, `questSearch`, `perkSearch`, and `notesSearch` all render at 13.33px in portrait too. The unit correctly reused the established `.tray-head input[type='search']` primitive (Protocol 22) and inherited the pre-existing portrait gap. Page-level overflow is clean at 360/412 (verified). Recommend an **app-wide** portrait fix (add a base or `@media(orientation:portrait)` 16px rule covering all `.tray-head` search filters) as a separate task, rather than singling out the karma filter — and a static Protocol-20 guard so it can't regress.
---
## Evidence artifacts
- `planning/mockups/fo3/audit-karma-412-good.png` (Talon + good companions + "Last, Best Hope of Humanity")
- `planning/mockups/fo3/audit-karma-412-evil.png` (Regulators + evil companions + "Stuff of Nightmares")
- `planning/mockups/fo3/audit-karma-412-neutral.png` (NO BOUNTY + Butch/RL-3 + "Keeper")
- `planning/mockups/fo3/audit-karma-360-good.png` / `audit-karma-desktop-*.png`
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).