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

SKILLBOOKS_TRACKER_DESIGN.md



RELEASE

planning/2.5.0/designs/SKILLBOOKS_TRACKER_DESIGN.md

sha256 8daffdd5a4a7642b · 11380 bytes · original held in the private archive

# SKILL BOOKS TRACKER — Build-Ready Spec > Design-only. No code changes. Targets `origin/main` @ `1fad1ea` (v2.5.0-r32, 1020 tests). > Mirrors the existing Collectibles / Lincoln Memorabilia / Traits trackers exactly. ## Pattern verified (current code) - **Collectibles** = binary checklist: `state.collectibles = []` (string[] of found names), `FALLOUT_REGISTRY.collectibles` per game, `renderCollectibles()` reads registry + a `Set` of found names, shows `[found/total]` in the sub-panel `<summary><h3>`, found-first then missing, click-to-toggle. **This is the closest mirror for skill books** (binary, per-game catalog). - **Lincoln** uses a `{name: disposition}` map only because it tracks a _value_ (disposition). Skill books are pure found/missing → **the array model (collectibles/traits) is the correct, simpler fit.** - **Traits autoImportState** (`api.js:770`) = array + registry-name filter + dedup → the exact validation to copy. - Panels: `<details class="panel" data-tab="…">` → `<details class="sub-panel" data-sub-id="…">` (collapse persisted in `robco_panel_state`); `loadUI()` calls each `render*()` (`ui-core.js:1394–1396`); `_updatePanelBadges()` h2→count (`ui-core.js:797`); `expandPanelForCategory` tabMap (`ui-core.js:931`). --- ## A. State model **`state.skillBooks = []`** — a `string[]` of read/found skill-book names (binary). Recommended over a `{name:true}` map: it's the binary-correct shape and identical to `state.collectibles`/`state.traits` (Lincoln's map exists only for its disposition value). Per-campaign automatically — it lives in `state`, which is serialized whole into `robco_v8.campaigns[ctx]`. Round-trips through save/migrate (see §D). ## B. Registry Add **`FALLOUT_REGISTRY.skillBooks = [{ name, skill }]`** to **both** `reg_nv.js` (13) and `reg_fo3.js` (13), read-only (P23), placed alongside the other tracker arrays (near `collectibles`/`traits` in `reg_nv`, near `collectibles`/`lincolnMemorabilia` in `reg_fo3`). `skill` = the **exact app skill KEY** from `getSkillKeys()` for that game, **not the display label**. **Builder MUST map each title to the exact key and STOP if any doesn't resolve** against `SKILL_KEYS`/`SKILL_KEYS_FO3`: FNV (`reg_nv`, 13) — keys: `barter, energy_weapons, explosives, guns, lockpick, medicine, melee_weapons, repair, science, sneak, speech, survival, unarmed`: ```js { name: 'Tales of a Junktown Jerky Vendor', skill: 'barter' }, { name: 'Nikola Tesla and You', skill: 'energy_weapons' }, { name: 'Duck and Cover!', skill: 'explosives' }, { name: 'Guns and Bullets', skill: 'guns' }, { name: 'Tumblers Today', skill: 'lockpick' }, { name: 'D.C. Journal of Internal Medicine', skill: 'medicine' }, { name: 'Grognak the Barbarian', skill: 'melee_weapons' }, { name: "Dean's Electronics", skill: 'repair' }, { name: 'Big Book of Science', skill: 'science' }, { name: 'Chinese Army: Special Ops Training Manual', skill: 'sneak' }, { name: 'Lying, Congressional Style', skill: 'speech' }, { name: 'Wasteland Survival Guide', skill: 'survival' }, { name: 'Pugilism Illustrated', skill: 'unarmed' }, ``` FO3 (`reg_fo3`, 13) — keys: `barter, big_guns, energy_weapons, explosives, lockpick, medicine, melee_weapons, repair, science, small_guns, sneak, speech, unarmed`: ```js { name: 'Tales of a Junktown Jerky Vendor', skill: 'barter' }, { name: 'U.S. Army: 30 Handy Flamethrower Recipes', skill: 'big_guns' }, { name: 'Nikola Tesla and You', skill: 'energy_weapons' }, { name: 'Duck and Cover!', skill: 'explosives' }, { name: 'Tumblers Today', skill: 'lockpick' }, { name: 'D.C. Journal of Internal Medicine', skill: 'medicine' }, { name: 'Grognak the Barbarian', skill: 'melee_weapons' }, { name: "Dean's Electronics", skill: 'repair' }, { name: 'Big Book of Science', skill: 'science' }, { name: 'Guns and Bullets', skill: 'small_guns' }, { name: 'Chinese Army: Special Ops Training Manual', skill: 'sneak' }, { name: 'Lying, Congressional Style', skill: 'speech' }, { name: 'Pugilism Illustrated', skill: 'unarmed' }, ``` All 26 map cleanly against the current key lists (verified). Differences: FNV `guns`+`survival` ↔ FO3 `big_guns`+`small_guns` (no `survival`/`guns`). The `skill` is for the per-row "(boosts X)" display + the test guard. ## C. Protocol 4 wiring (every file/function) 1. **`state.js`** — default: `skillBooks: [],` (near `traits`/`lincolnItems`, ~line 355). 2. **`state.js` `migrateState()`** — `if (!Array.isArray(s.skillBooks)) s.skillBooks = [];` 3. **`api.js` `autoImportState()`** — mirror the traits block: array + registry-name filter (`FALLOUT_REGISTRY.skillBooks` names) + dedup: ```js { const raw = _g(parsed, 'skillBooks'); if (Array.isArray(raw)) { const names = typeof FALLOUT_REGISTRY !== 'undefined' && Array.isArray(FALLOUT_REGISTRY.skillBooks) ? new Set(FALLOUT_REGISTRY.skillBooks.map(b => b.name)) : new Set(); const seen = new Set(); state.skillBooks = raw.filter(b => { if (typeof b !== 'string' || !names.has(b) || seen.has(b)) return false; seen.add(b); return true; }); } } ``` Also add `'skillBooks'` to the delta-tracking category list (the array near `api.js:823`). 4. **`api.js` `getSystemDirective()`** — add an **unconditional** schema block (both games track skill books; the registry is per-game): ``` ### **Skill Books Tracker** state.skillBooks is a string[] of skill-book titles the Courier has read. Include only names exactly as defined in the active game's skill-book registry. Update when the Courier reads a skill book. ``` (No `ctx` ternary — unlike Lincoln (FO3-only) / Traits (FNV-only), skill books exist in both games.) 5. **`ui-render.js`** — `renderSkillBooks()` mirroring `renderCollectibles()`: read `FALLOUT_REGISTRY.skillBooks` + `Set(state.skillBooks)`, set the sub-panel `<summary><h3>` to `> SKILL BOOKS [found/total]`, list found-first then missing, each row = `title — (boosts <Label>)` with `[FOUND]/[ ]` click-toggle → `toggleSkillBook(name)`. Add `toggleSkillBook(name)` (add/remove from `state.skillBooks` + `saveState()` + re-render), exactly like `toggleCollectible`. 6. **`ui-core.js` `loadUI()`** — add `renderSkillBooks();` next to `renderCollectibles()` (~`1396`). 7. **`index.html`** — new top-level panel (see §E). 8. **`ui-core.js` `_updatePanelBadges()`** — `{ h2text: '> SKILL BOOKS', count: (state.skillBooks || []).length }`. 9. **`ui-core.js` `expandPanelForCategory()` tabMap** — `skillBooks: 'stat'`. **Suite 1 (autoImportState field-coverage auto-discovery)** will auto-detect `state.skillBooks` and assert it's covered by autoImportState + the save envelope — step 3 satisfies it (gate fails otherwise). ## D. Cloud-sync determination (state explicitly in commit message) > **Cloud-sync determination:** `state.skillBooks` is part of the campaign `state` object, which is serialized **whole** into every save and cloud payload (`saveState`/`exportSaveFile`/`saveCurrentToCloud`/`pullFromCloud`/`loadCloudSave` all do `campaigns[ctx] = JSON.parse(JSON.stringify(state))`). Therefore the field **automatically rides the cloud save** — no separate cloud wiring. Older saves lacking it are normalized to `[]` by `migrateState()` on load. No auto-push is introduced (cloud remains manual-button only, Protocol 34). ## E. UI - **Placement: STAT tab, near the Skill Matrix** (skill books boost skills) — a dedicated top-level `<details class="panel" data-tab="stat">` titled `> SKILL BOOKS`, structured identically to the COLLECTIBLES panel: ```html <details class="panel" data-tab="stat"> <summary><h2>&gt; SKILL BOOKS</h2></summary> <details class="sub-panel" id="skillBooksSubPanel" data-sub-id="skill_books"> <summary><h3>&gt; SKILL BOOKS</h3></summary> <!-- render sets [found/total] --> <div id="skillBooksDisplay"><span class="empty-state">No skill books loaded</span></div> </details> </details> ``` - Compact + collapsible + **persisted collapse** via `data-sub-id="skill_books"` (default-collapsed on first load), `[found/total]` in the summary h3, each row = book title + `(boosts <Skill>)` + click-to-toggle found/missing, found-first — **identical to collectibles/Lincoln**. - **Per-game**: the active registry drives it — FNV renders its 13, FO3 its 13. **No show/hide needed** (both games have a `skillBooks` registry; unlike Lincoln which is FO3-only and hides itself). A clean FNV load shows only FNV books; FO3 only FO3 books. ## F. Test plan (new Suite, both runners, parity) **Registry/data:** `FALLOUT_REGISTRY.skillBooks` length **13** in each game; every `skill` ∈ `getSkillKeys()` for that game; no duplicate names; sentinels — FNV has `Guns and Bullets`(guns) + `Wasteland Survival Guide`(survival); FO3 has `U.S. Army: 30 Handy Flamethrower Recipes`(big_guns) + `Guns and Bullets`(small_guns) and **no** `Wasteland Survival Guide`. **Protocol 4:** `state.skillBooks` default `[]`; `migrateState` coerces non-array → `[]`. **autoImportState (behavioral):** a valid registry name is kept; a non-registry name is rejected; a non-string is rejected; duplicates de-duped. **Render/wiring:** `renderSkillBooks` + `toggleSkillBook` defined; `renderSkillBooks` called from `loadUI`; `#skillBooksDisplay` exists; `_updatePanelBadges` has the `> SKILL BOOKS` entry; `expandPanelForCategory` tabMap has `skillBooks`. **Coverage/persistence:** Suite 1 auto-coverage passes for `skillBooks`; a round-trip guard that `skillBooks` survives a save-envelope → load (state→serialize→migrate→state). ~14–16 guards → 1020 → ~1035. Sync count both runners + docs. ## G. Sequencing / cache / version **ONE commit** — small and additive, identical in shape to the just-shipped Lincoln/Traits trackers (each was a single commit): registry data + Protocol-4 wiring + panel + Suite. **Cache bump r32 → r33** (served: `state.js`, `api.js`, `reg_nv.js`, `reg_fo3.js`, `ui-render.js`, `ui-core.js`, `index.html`). Docs (P2/2a): ARCHITECTURE, README, CHANGELOG `[Unreleased]` Added, test-count sync in both runners + all doc locations. **APP_VERSION stays 2.5.0** (Phase 6 → `[Unreleased]`; bumps to 2.6.0 only at Phase-6 completion). ## H. Key decisions for the owner (recommend defaults; minimal) 1. **State shape** — **(a) `string[]` of found names** _[recommended — binary, matches collectibles/traits]_ vs (b) `{name:true}` map. 2. **Toggle model** — **(a) binary found/missing** _[recommended — mirrors Lincoln/collectibles exactly]_ vs (b) count/percent (N/A — a skill book is a one-time read). 3. **Default state** — **(a) empty (all missing)** _[recommended]_ vs (b) pre-seeded. 4. **Placement** — **(a) STAT tab near the Skill Matrix** _[recommended — owner-suggested; thematically skill-related]_ vs (b) a third sub-panel inside the existing INV-tab COLLECTIBLES panel (maximal structural mirror). All four recommendations keep it a simple found/missing checklist mirroring the Lincoln/collectibles trackers — no reason to deviate.
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).