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

STEP2_PHASE0_PLAN.md



RELEASE

planning/2.8.0/plans/STEP2_PHASE0_PLAN.md

sha256 05df7b58e804f52a · 24955 bytes · original held in the private archive

# STEP 2 (v2.8.0) — PHASE 0 FOUNDATIONS — Implementation Plan > **Stage 1 of the Protocol 8 workflow (Opus — Diagnose & Plan). PLAN ONLY. No edits, no commits, no pushes.** > **Baseline:** `dev` @ `2bd28aa` — v2.7.0, cache `robco-terminal-v2.7.0-r9`, 1557 tests / 130 suites (verified against live code, not just the audit). > **Plan of record:** `planning/FEATURE_PHILOSOPHY_AUDIT.md` §5 Phase 0 (items 1–7) + `planning/ROADMAP_REBASELINE.md` Tier 0/§S2. > **Authority:** the audit is adopted; where the task brief's Phase-0 enumeration differs from the audit §5 placement, §3 below flags it. > This file lives in gitignored `planning/`. --- ## 0. Verification pass — audit claims vs live code (line-drift + discrepancies) Every load-bearing seam re-checked against source this session: | Claim (audit / rebaseline) | Live code | Verdict | | --- | --- | --- | | `getSystemDirective()` is a ~190–195-line monolith, `api.js:23–218` | `api.js:23–214` (~191 lines) | ✅ (minor drift −4) | | GA-5 ternaries at `api.js:179` (+ 187/195) | `ctx === 'FO3' ?` at **179**; `ctx === 'FNV' ?` at **187** and **195** | ✅ exact | | `window.onload` boot block "~900 lines (grown past 568)" (FP-SYS-6) | `ui-core.js:330–898` = **568 lines** | ⚠ **DRIFT** — it is ~568, *not* ~900. The audit's "grown past 568" is wrong; rebaseline's 330–898/568 is right. | | Squad affinity render-only, `ui-render.js:183–195` | `ui-render.js:182–207` (render at 194; no native input) | ✅ (drift +12) | | `_threatCompute` at `ui-render.js:2313+`; ammoBurn unused vs reserves | `_threatCompute` at **2268**; `ammoBurn` at 2292/2305; rendered 2379; **never compared to `state.ammo`** | ✅ finding holds (line drift −45) | | `_CONSULT_CATS` = items/perks/quests/locations/companions, `ui-render.js:2405` | Exactly that, at **2405** | ✅ exact | | Projected Timeline stub, `index.html:1330` | `#timelineDisplay` at **1321**, stub text at **1333** | ✅ (drift −9) | | `campaign_notes` `[T#]` string log, 200-cap, oldest dropped | `api.js:544–546` `push([T${ticks}] …)` + `.slice(-200)` | ✅ exact | | Faction threshold crossing inlined in autoImportState (D-4) | `api.js:753–790` | ✅ — **and it is FNV-hardcoded**: `majorFactionKeys=['ncr','legion','house','bos','boomers','khans']` + `FACTION_REGISTRY.find`. A latent Protocol-38 leak (see §3). | | Gate parity compares TOTAL only, not per-suite (S2-F5) | `scripts/gate.js:226–234` matches `/ALL (\d+) TESTS/` and compares `nodeTotal !== psTotal` — **totals only** | ✅ exact | | ~24 `robco_*` device keys | **31 distinct** `robco_*` keys enumerated (see Unit 5 manifest) — more than the audit's "24+" | ⚠ higher than stated; audit's "24+" is a floor | | `GAME_DEFS[ctx].ai.{skillSystemText,factionSystemText,irreversibleTriggers}` already exist | `state.js:369–381` (FNV), `454–465` (FO3) | ✅ — the agnostic destination pattern is proven; `trackerDirectives` is a new sibling | | `alert()`/`confirm()` "~30 sites" | `grep` counts **50** `alert(`/`confirm(` substring hits across `js/*.js` (some are `confirmAction`-style / comments) | ⚠ raw count higher; true call-site count needs a definition-anchored sweep (Unit 12) | **Net:** all findings hold. The one factual correction is FP-SYS-6's "~900 line" size — it is ~568. Two latent issues surfaced not called out as Phase-0-blocking: the FNV-hardcoded faction-threshold detector (`api.js:753`) and the higher-than-stated key/alert counts. --- ## 1. Phase 0 build UNITS — dependency order + chapters Grouped into chapters of 1–3 units for session rotation. Every unit: full gate on every commit; on `dev`; game-agnostic (Protocol 38); **no `APP_VERSION` bump** (Phase 0 stays 2.7.0, cache-rev bumps only — see §4); Protocol 15 both-runners parity; Protocol 2a count sync. ### CHAPTER A — api.js keystone (verifiable, most-unblocking) | Unit | What | Why here | Risk/Cx | Model | | --- | --- | --- | --- | --- | | **U1 — Directive decomposition + GA-5 retirement** (FP-SYS-7 / S2-F2) | Split `getSystemDirective()` into per-section builders; move the 3 GA-5 ternaries → `GAME_DEFS[ctx].ai.trackerDirectives`; Protocol 14 golden + contract test same commit. | **FIRST — see §2.** Hard prerequisite for ALL AI-residue (INTERCEPT/OVERSEER/banter) *and* FO4; closes the last Protocol-38 residual; byte-verifiable → low real risk despite touching served code. | **Low–Med** / M | **Sonnet** implement (golden test de-risks); Opus audit | ### CHAPTER B — boot seams | **U2 — `window.onload` decomposition** (FP-SYS-6 / S2-F3) | 568-line boot block → named seams (`_hydrateStateFromStorage`/`_restoreDevicePrefs`/`_wirePanelPersistence`/`_wireStandby`/`_wireKeyboardShortcuts`/`_startAmbientTimers`/`_wireUnloadFlush`). Behavior byte-identical. | Every Phase-1 organizing feature (state machine, MOTD, clearance restore, Module Bay init) lands in boot; decompose before piling on. Comes after U1 because U1 is lower-risk and proves the "verifiable refactor" pattern. | **Med–High** / M–L (closure entanglement: `sessionStart`, standby vars, `_uptimeInterval`, `_memCycleInterval` share `window.onload` scope) | **Opus** plan-heavy, Sonnet careful impl, Opus audit | ### CHAPTER C — test tier + gate hardening | **U3 — `autoImportState` VM behavioral test** (FP-AI-4 / S2-F4) | Node VM-sandbox that stubs `state`/`GAME_DEFS`/`localStorage`, feeds malformed/hostile/partial/valid Tri-Node payloads, asserts clamps + no-throw + round-trip. Establishes the behavioral-test tier. | The AI→state path has no *headless* behavioral guard (test.html only, in-browser). Must exist before any AI-residue. The VM harness U1 seeds is reused here. | **Med** / M | **Sonnet** (harness design benefits from Opus review) | | **U4 — Per-suite parity + merge-gate discipline** (S2-F5) | `scripts/gate.js` parity upgraded from total-only to per-suite (suite name → count map compared across runners). | Phase 0 adds many tests; per-suite drift is currently invisible. Tighten before the volume lands. | **Low–Med** / S–M | **Sonnet** (mechanical) | ### CHAPTER D — meta-store + boundary | **U5 — MetaStore accessor + key manifest** (FP-SYS-5) | One `MetaStore.get/set/remove(key)` owning the 31-key `robco_*` namespace; a registered-key manifest `{name,type,default,owner}`; migrate raw `localStorage` device-key sites through it. | P4 boundary is convention-only; IndexedDB (Phase 2) needs one choke point; Hardware Life triples its importance. | **Med** / M (wide, mechanical, served-file) | **Sonnet** impl; Opus sets boundary rules | | **U6 — Boundary gate guard suite** (FP-PER-4) | New suite: no campaign-shaped field written to a bare `robco_*` key; device features don't write `robco_v8`; MetaStore is the only device-key writer. | Makes the two-store boundary structural, not conventional. Depends on U5's accessor existing. | **Low–Med** / S–M | **Sonnet** | ### CHAPTER E — event bus + auto-log | **U7 — OS Event Bus** (FP-SYS-2) | Tiny `emit(event,payload)` + `on(event,fn)`; typed events (`faction.threshold`,`hp.critical`,`level.up`,`quest.status`,`location.discovered`,`rad.tier`,`limb.crippled`,`collectible.acquired`,`boot.completed`…). Re-point the 3 inline crossing-detectors (incl. the FNV-hardcoded `api.js:753` one → game-agnostic via `getFactionRegistry()`). | Highest-leverage shared infra; every Phase-1/2 consumer (record, news, badges, incident log) subscribes instead of re-implementing D-4. | **Med** / M | **Opus** design (event contract), Sonnet impl | | **U8 — Auto-log expansion via the bus** (FP-AUTO-2) | Emit events for level-ups, collectible acquisitions, crafts, trades, sleeps (today only factions/quests/locations log). | Terminal Record (Phase 1) views are only as rich as what's recorded; born structured. | **Low–Med** / S–M | **Sonnet** | ### CHAPTER F — connection sweep + native-input authority | **U9 — Cheap connector sweep** | FEAT-1 retire `#timelineDisplay` stub; FEAT-2 "CURRENT SITTING" line from `stats.sessionStart`; FEAT-4 THREAT ammo-adequacy advisory (`ammoBurn` vs `state.ammo[caliber]`); FEAT-5 CONSULT covers trackers (collectibles/skillBooks/magazines/traits/lincoln) + shows reserved cols (`dlc`/`Special_Property`); GI-11 hide MODS filter for FO3 via `GAME_DEFS.hasWeaponMods`. | Days of work total; closes real completeness/identity gaps; queuing behind big phases buys nothing. UI-touching → Protocol 10 render-check. | **Low** / S each | **Sonnet** (some **fable**-able: FEAT-1/2) | | **U10 — Native-input-path audit + affinity fix** (FP-AI-5 + FP-FEAT-3) | Enumerate every AI-writable state field; confirm/native-edit path; **fix affinity (P-3): add a native affinity input to the squad row** (the one live authority violation). Document stats-adjust decision. | Closes a player-authority (P1) violation two versions before the protocol formalizes. | **Low–Med** / S | **Opus** audit (enumeration) + Sonnet affinity fix | ### CHAPTER G — hygiene ledgers (docs) | **U11 — Parity ledger + reserved-column register + skill-less audit** (DATA-1 / DATA-8 / EXP-2) | Commit §2.4 asymmetry table (each row `GENUINE`|`GAP`+target) + annotate unconsumed DB columns with intended consumer + audit `getSkillKeys()` consumers degrade to `[]`. Docs/comments only. | Prevents re-litigating asymmetries every audit; makes FO4 scope estimable. Lowest risk; can run any time — parked at the end so it captures Phase-0's own new seams. | **Low** / S | **Sonnet** / **fable** | ### CHAPTER H — modal driver (task-brief Phase-0 item; audit places in Phase 1 — see §3) | **U12 — One modal driver + alert/confirm purge** (FP-SYS-8 + FP-SYS-10) | `openModal({title,body,mode,blocking,onClose})` consolidating `#sysModal`/`#updateModal`/changelog; `confirmAction(...)` helper; sweep the ~30–50 `alert()`/`confirm()` sites in batches (destructive ops keep Protocol-34 confirm semantics). | Last big frame-breaker; MANUALS/Distribution open documents in modals. **Audit §5 = Phase 1; task brief lists it under Phase 0.** Recommend LAST in Phase 0 (or first in Phase 1) — either is consistent. | **Med** / M (wide) | **Opus** driver API design, Sonnet sweep | **Dependency spine:** U1 → (U2 ∥ U3/U4) → U5 → U6 → U7 → U8 → U9/U10 → U11 → U12. - **api.js serial cluster** (three-way-merge hazard — serialize, full gate between): U1 → U7's `api.js:753` re-point → later U5 (secrets keys) → future INTERCEPT/OVERSEER. - **ui-core.js boot cluster** (serialize): U2 → later MOTD/attract/standby (Phase 1). - **state.js GAME_DEFS additive sequence:** U1 (`ai.trackerDirectives`) → later E1 voice/ambient block. - U3's VM harness is seeded by U1's golden test → build once, reuse. --- ## 2. FIRST UNIT — fully specified: **U1 · Directive decomposition + GA-5 retirement** (FP-SYS-7 / S2-F2) ### 2.1 Why this is the pick (lowest-risk × most-unblocking foundational) - **Most-unblocking:** it is a *hard* prerequisite (per rebaseline §S2.C api.js serial cluster) for every AI-residue feature (U5 INTERCEPT, OVERSEER hook, TRADE/BIO banter) **and** an explicit Step-4/FO4 prerequisite (S4-P1). It also closes the **last Protocol-38 residual** (GA-5) — a standing project constraint — so it discharges debt while unblocking. - **Lowest-risk of the two hard-prerequisite decompositions:** unlike `window.onload` (U2, with shared closures/timers/lifecycle across 568 lines), `getSystemDirective()` is a **single pure function returning one template string, one caller** (`api.js:1441`), no side effects, no shared state. The agnostic destination (`GAME_DEFS[ctx].ai.*`) **already exists and is proven** — I am extending a known pattern, not inventing one. - **Verifiable to ~zero behavioral risk:** the entire correctness condition is "the assembled string is byte-identical to today's for FNV and FO3." A golden-master test captures the pre-refactor output and asserts equality → if it matches, the AI sees the identical prompt. No other Phase-0 unit is this controllable. - **It seeds the VM test harness** U3 needs (both call the function under stubs), so it front-loads reusable infra. Boot decomposition (U2) is *slightly* more unblocking but materially riskier (closure entanglement, many boot entry-paths) → it is the immediate next chapter, not first. ### 2.2 Files, functions, and exact seams **`js/api.js`** — `getSystemDirective()` (lines **23–214**). Cut into named per-section builders, each returning a string; `getSystemDirective()` composes them in the current order. Proposed seams (module-scope functions, hoisted, above `getSystemDirective`): - `_directiveConstraints()` — playstyle → `constraintStr` (27–30) + `campaignModeStr` (34–53). **Must keep the literal tokens `campaignModeStr` and `state.playthroughType`** — the AI-contract coverage suite asserts their presence (`tests/robco-diagnostics.js:427/431`). - `_directivePersonaAndContract(ctx)` — the FICTIONAL CONTEXT OVERRIDE + persona + Tri-Node schema + example schema block (57–107). - `_directiveCoreTracking()` — Core State/Formatting + Operational Matrix + ROBCO_DEV_MANUAL (109–129). - `_directiveSkills(ctx)` / `_directiveFactions(ctx)` — wrap `GAME_DEFS[ctx].ai.skillSystemText` (132) / `factionSystemText` (141) (unchanged interpolation). - `_directiveSystems()` — Head Trauma / Perk / Quest / Equipped / Session-Statistics / G2 Safety-Net (137–176), incl. `GAME_DEFS[ctx].ai.irreversibleTriggers` (171). - `_directiveTrackers(ctx)` — **the GA-5 retirement.** Replaces the three ternaries (179/187/195) with `GAME_DEFS[ctx].ai.trackerDirectives` + the unconditional skill-books block (202–203). - `_directiveInjectionBoundary()` — the closing injection-resistance section (205–213). **`js/state.js`** — add `ai.trackerDirectives` (a single pre-built string) to each `GAME_DEFS` entry, adjacent to the existing `ai` block: - **FNV** (`state.js:369–381`): `trackerDirectives` = the **Traits** directive (188–190 text) + the **Skill Magazines** directive (196–198 text), joined exactly as today's two `FNV ?` blocks render (blank line between). **No Lincoln.** - **FO3** (`state.js:454–465`): `trackerDirectives` = the **Lincoln Memorabilia** directive (180–182 text). **No Traits, no Magazines.** - A future game with no trackers → `trackerDirectives: ''` (or omit → builder coalesces to `''`) — **no code change, no crash** (Protocol 38 N-game seam). **Critical:** the moved strings contain non-ASCII in the surrounding directive (⚙️ 🛑 ⚠ ▲ em-dashes). **Edit tool or Node `fs.writeFileSync(…, 'utf8')` only — never PowerShell** (Protocol 39; Suite 90 guards U+FFFD / `â€` / `â–`). ### 2.3 The precise change (net effect) 1. Concatenation order and every interpolated token are **preserved byte-for-byte**; only the *carrier* changes (inline ternaries → data field + builder). 2. `getSystemDirective()` body becomes a composition: `` return [ _directivePersonaAndContract(ctx), _directiveConstraints(), … , _directiveTrackers(ctx), _directiveInjectionBoundary() ].join('') `` (or a single template literal calling the builders) that reproduces the current 57–213 string exactly. 3. `ctx` resolution unchanged: `const ctx = GAME_DEFS[state && state.gameContext] ? state.gameContext : 'FNV';` (55) — preserve the sanctioned FNV fail-safe. ### 2.4 Acceptance criteria (Protocol 26 — verify on the real artifact) 1. `getSystemDirective()` is composed from named per-section builders; **no `ctx === 'FO3' ?` or `ctx === 'FNV' ?` ternary remains anywhere in `js/api.js`.** 2. Tracker text is sourced from `GAME_DEFS[ctx].ai.trackerDirectives` (FNV = traits+magazines; FO3 = lincoln; skill-books block stays unconditional in the builder body). 3. **Golden-master equality:** the assembled directive string for `ctx='FNV'` and `ctx='FO3'` is byte-identical to the pre-refactor baseline (captured before editing). This is the primary safety gate. 4. **Protocol 14 same commit:** an AI-contract test validates the Tri-Node schema statement is intact and the `autoImportState()` round-trip still holds (extend the existing contract coverage; add the golden assertions). 5. Existing coverage stays green — specifically the tokens `campaignModeStr` (`:427`) and `state.playthroughType` (`:431`); Suite 89 GATE-AGNOSTIC (no two-game coercion); Suite 43 GAME_DEFS structural integrity (updated for the new `ai.trackerDirectives` field if it asserts shape); Suite 90 UTF-8. 6. **Escape-ratchet (Protocol 36b):** extend Suite 89 with (a) negative guard — no `ctx === 'FO3'\|FNV' ?` ternary in `getSystemDirective`; (b) positive guard — `trackerDirectives` present in both `GAME_DEFS` entries and referenced by the builder. Add in both runners at parity. 7. `CACHE_NAME` bumped `…-r9` → **`robco-terminal-v2.7.0-r10`** (served `js/api.js` + `js/state.js` changed; Protocol 1). **`APP_VERSION` stays 2.7.0** (no owner-approved bump; §4). 8. Full gate green: ESLint 0, Prettier clean, both runners at parity with counts synced everywhere (Protocol 2a), browser checks; docs updated (Protocol 2 — ARCHITECTURE/CHANGELOG[Unreleased]/README as applicable). 9. Game-agnostic proof: adding a third game later needs only `GAME_DEFS.FO4.ai.trackerDirectives` — zero edits to `getSystemDirective()` or its builders. ### 2.5 Verification steps 1. **Capture baseline first** (before any edit): in a Node VM sandbox stub `state={gameContext:'FNV',…}`, `GAME_DEFS`, `APP_VERSION`, and `localStorage.getItem` (for `_commGet('playstyle',…)`), `require`/eval `getSystemDirective`, record the FNV and FO3 output strings. This becomes the golden fixture (and seeds the U3 harness). 2. Refactor; re-run the sandbox; assert **exact string equality** for both games (and for a matrix of playstyle × playthroughType × campaignMode combinations, since those branches must be untouched). 3. `npm run lint && npm run format`. 4. `npm run gate` (full) — confirm "Both runners agree: N tests" and identical totals; confirm Suite 90 (no mojibake — the moved non-ASCII survived), Suite 89 (+ new guards), AI-contract suite all pass. 5. Confirm no runtime path change: `getSystemDirective()` still has exactly one caller (`transmitMessage`, `api.js:1441`); no signature change. ### 2.6 Entry paths / edge cases enumerated (Protocol 8 plan-audit) - **ctx = FNV** → trackerDirectives = traits + magazines; **not** lincoln. ✔ - **ctx = FO3** → trackerDirectives = lincoln; **not** traits/magazines. ✔ - **ctx unknown / `state` undefined** → `ctx='FNV'` fail-safe (55) → FNV trackers; no crash. - **Future game (FO4) with `trackerDirectives:''` or absent** → builder emits empty section; no ternary, no crash. - **playstyle:** `melee` vs any-other → the two `constraintStr` branches (27–30) preserved. - **playthroughType:** each of 5 known values + unknown → `_playthroughDirectives[type] || ''` (42) preserved. - **campaignMode:** `standard` / `rng` / `rng-locked` → `rngStr` + `lockedModifier` (44–50); and the **combined** case (playthrough + rng both active, 53) preserved. - **Skill-books block** — unconditional, both games, must remain (202–203). - **Interpolated dynamics** — `GAME_DEFS[ctx].label`, `APP_VERSION`, `skillSystemText`, `factionSystemText`, `irreversibleTriggers` — all must survive verbatim. - **UTF-8** — the ⚙️/🛑/⚠/▲/em-dash glyphs in the persona/matrix sections and in the moved tracker strings must not double-encode (Suite 90). ### 2.7 Protocol implications (checklist for the implement session) - **P1 cache bump:** YES → `sw.js` `CACHE_NAME` → `robco-terminal-v2.7.0-r10`. - **P2 docs:** ARCHITECTURE.md (note the directive is now section-composed + `ai.trackerDirectives` seam); CHANGELOG.md `[Unreleased]` (plain-English + `Under the Hood`); README if directive/agnosticism described. - **P2a counts:** new tests → update every count location (RULES.md ×5, CLAUDE.md ×5, README ×4, ARCHITECTURE, CHANGELOG `[Unreleased]` header, both runners' per-suite comments, `tests/test.html` `Suites:` if a suite is added). Run the `Select-String` drift scan. - **P13 regression test:** the golden-equality + GA-5-absence guards *are* the regression tests. - **P14 AI-contract:** required, same commit (criterion 4). - **P15 parity:** both runners updated together, same total. - **P36b escape-ratchet:** Suite 89 tightened (criterion 6). - **P38:** the whole point — data-driven, N-game safe. - **P39/Suite 90:** Edit/Node writes only. - **P19 batch:** land U1 as one complete, gated commit (decomp + state field + tests + docs + cache) — not incrementally. --- ## 3. Discrepancies (audit ↔ code ↔ task brief) + risks **Line/size drift** (all minor, corrected in §0): `getSystemDirective` 23–**214** not 218; GA-5 at 179/187/195 (exact); `_threatCompute` at **2268** not 2313; `_CONSULT_CATS` at **2405** (exact); timeline stub **1321/1333**; affinity **182–207**. **Factual corrections:** 1. **FP-SYS-6 "~900 lines" is wrong** — `window.onload` is **568 lines** (`ui-core.js:330–898`). Plan/estimate U2 against 568, not 900. 2. **`robco_*` key count is 31, not "24+"** — the audit's floor undercounts; U5's manifest must enumerate all 31 (listed in Unit 5 scope below). 3. **`alert()`/`confirm()` raw grep = 50 hits, not ~30** — includes non-call-site matches; U12 needs a definition-anchored count before scoping the sweep. **Task-brief ↔ audit §5 placement discrepancies (flag for owner/next session):** 1. **Modal driver / alert-purge (U12):** the task brief lists "purge `alert()`/`confirm()` → one diegetic modal driver" **inside Phase 0**; audit §5 places SYS-8/SYS-10 in **Phase 1** (item 12). Recommend running it **last in Phase 0** (or first in Phase 1) — behaviorally identical ordering, low coupling to U1–U11. 2. **"VATS↔THREAT shared target"** appears in the task's connection-sweep list, but the audit assigns the VATS/THREAT target merge to **v2.9.0 ENCOUNTER** (FP-GP-1 / P-6). Recommend: in Phase 0 do only the **cheap half** (let THREAT surface the bestiary DT and let VATS *optionally* read it, keeping the manual `#vatsTargetDT` override) **or** defer entirely to 2.9.0 per audit. **Owner ruling wanted.** (Not in U9 as specced above — flagged, not silently included.) 3. **"effective-skill resolver" (FEAT-6)** is in the task's sweep list but the audit places it "**v2.8.0 late or v2.9.0 opener**" (before the dialogue helper/ENCOUNTER). Recommend keeping it **out of Phase 0** (it's M-complexity integration, not a cheap connector) and scheduling it in the late-2.8 bridge — hence it is *not* in U9/U10 above. **Flag.** **Risks:** - **U2 (boot)** is the real Phase-0 risk: 568 lines of shared closures (`sessionStart`, `_standbyActive`, `_uptimeInterval`, `_memCycleInterval`, `_startUptimeClock`, `enterStandby/exitStandby`, `_inputHistoryIdx`). Naïve extraction breaks scope. Mitigation: keep shared vars at a documented boot-scope object or module scope; decompose behavior-preserving, verified by Suite 56 (load order) + boot-smoke + render-check. - **U7 (event bus)** must re-point the **FNV-hardcoded** faction detector (`api.js:753` `['ncr','legion','house','bos','boomers','khans']` + `FACTION_REGISTRY.find`) to `getFactionRegistry()` — otherwise the bus inherits a Protocol-38 leak. Treat as an escape-ratchet fix in the same unit. - **U5 (MetaStore)** is wide/mechanical across served files — highest count-churn; land as one batched, gated commit. - **Changelog/version tension:** memory note says "batch changelog into a single release version at program end"; audit frames Step 2 as **v2.8.0**. Phase-0 commits should accumulate under `[Unreleased]` at **APP_VERSION 2.7.0 with cache-rev bumps**, and the **`APP_VERSION`→2.8.0 flip needs explicit owner approval** (per constraint + Protocol 2 for the release). **Owner ruling wanted on when to flip.** --- ## 4. Standing constraints for the build (every unit) - All work on **`dev`**; full gate green on **every** commit (Protocol 43 — same bar as `main`). - **Protocol 1** cache-rev bump on any served-file (`index.html`/`sw.js`/`manifest.json`/icons/`css/`/`js/`) commit; test-/doc-/script-only commits skip it. Next rev after r9 = **r10**. - **No `APP_VERSION` bump** without owner approval (stays 2.7.0 through Phase 0; `[Unreleased]` accrues). - **Protocol 38** game-agnostic (data-driven via `GAME_DEFS`, N-game safe); **Protocol 39** UTF-8 (Edit/Node writes only). - **Player authority / native-input paths** honored (U10 audits it; U9 affinity fix closes the one live violation). - **Protocol 15** both runners at parity; **Protocol 2a** counts synced everywhere in the same commit; **Protocol 19** batch related work into one gated push. --- *END — Stage 1 plan. Nothing implemented, committed, or pushed. `dev` @ `2bd28aa` untouched; `planning/` is gitignored.*
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).