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

MAINREVERT_COMPAT_PLAN.md



RELEASE

planning/2.8.0/plans/MAINREVERT_COMPAT_PLAN.md

sha256 f828f6772e1a573b · 10110 bytes · original held in the private archive

# MAIN-REVERT ↔ CLOUD-SAVE COMPATIBILITY — Investigation + Safe Plan > READ-ONLY investigation. Nothing was changed, committed, or pushed. This doc is the > only file written (planning/ is gitignored). Implement nothing without owner approval. > > Scenario: owner will later revert **`main`** back to the clean **v2.6.0** baseline > (tag `v2.6.0` = commit `bc8c2eb`), dropping the unreleased 2.7.0 work from production > (that work stays on `dev`). Concern: CLOUD saves (Firestore project `nv-overlord`) > created while running the in-progress 2.7.0 build will then be loaded by the reverted > v2.6.0 production code — will the older code drop/break newer save fields, or treat > them as a "future version"? --- ## TL;DR VERDICT — **SAFE. No data loss. No "future version" prompt. No save surgery needed.** The owner's cloud saves are **fully compatible** with reverted v2.6.0 and survive a load → re-save round-trip with **zero field loss**. The three feared failure modes **cannot fire**: 1. **"Future version" rejection** — does NOT trip. Every cloud save carries `version: "2.6.0"` (APP_VERSION never changed during the whole 2.7.0 build). v2.6.0's future-check is `schemaVersion||version > APP_VERSION` → `2.6.0 > 2.6.0` is **false**. 2. **Field dropping on load** — does NOT happen. v2.6.0's import path is **passthrough** at every stage (sanitize copies all fields, migrate only *adds* missing ones, boot hydrates with a spread merge). Nothing whitelists or deletes unknown fields. 3. **Round-trip strip on re-save** — does NOT happen. Re-save serializes the full in-memory `state`, which already holds every field loaded. **The decisive fact:** **v2.6.0 IS the Phase-6 release.** The "newer 2.7.0 fields" the owner worried about (`collectibles`, `traits`, `skillBooks`, `magazines`, `lincolnItems`, faction `fame`/`infamy`, `campaignMode`, `playthroughType`, `mapView`) all already exist in v2.6.0's `state` defaults + `migrateState`. The unreleased 2.7.0 work added **zero new state fields** — it was hardening (XSS escaping, perf caching, game-agnostic refactor, more-defensive import coercion). --- ## EVIDENCE ### Git topology | Ref | Commit | Note | |-----|--------|------| | `v2.6.0` tag | `bc8c2eb` | "Release: v2.6.0 — Phase 6 (content, crafting, trackers, fixes)" — the revert target | | `main` (now) | `505276f` | ~16 unreleased commits **ahead** of v2.6.0 (WU-A series + a11y + fixes) | | `dev` (now) | `44542d9` | 7 further commits ahead of main (WU-B series) — the furthest "2.7.0 build" tip | "Revert main to v2.6.0" = move `main` back to `bc8c2eb`. The owner's saves were made on the 2.7.0 build; comparing **v2.6.0 → dev** (the furthest tip) covers everything the saves could possibly carry. ### 1. Save envelope + version stamp - `APP_VERSION === '2.6.0'` on **v2.6.0, main, AND dev** (confirmed in `js/state.js:2`). It never moved during the 2.7.0 build. - Cloud payload (`js/cloud.js`, both `saveCurrentToCloud` and `syncLocalSavesToCloud`) stamps **`version: "2.6.0"`** and `schema: 2`. It writes **no `schemaVersion`** and **no `checksum`** field (it writes `contentHash`, a dedup hash, not the envelope `checksum` that the integrity check looks for). - `verifySaveEnvelope` (`js/state.js`) on a cloud doc: - `sv = schemaVersion || version` → `"2.6.0"`. `_semverGt("2.6.0","2.6.0")` = false → **not** `future_version`. - No `checksum` field present → returns **`{status:'legacy'}`** → loads normally, **no warning, no confirm**. (The `checksum_mismatch` path also cannot fire.) ### 2. What the cloud saves contain Payload = `{ schema:2, version:"2.6.0", savedAt, updatedAt, label, gameContext, contentHash, robco_v8, chat, playstyle }`. `robco_v8` is the full multi-campaign container; each campaign is `JSON.parse(JSON.stringify(state))` — the **entire** state object, no field whitelist. Every field below already exists in v2.6.0: - Core: `lvl, xp, hpCur, hpMax, s/p/e/c/i/a/l, caps, rads, karma, ticks, loc, limbs` - Arrays/objects: `inventory, ammo, quests, perks, squad, status, equipped, campaign_notes, locationHistory, stats, factions` - Phase-6 trackers: `collectibles[], traits[], skillBooks[], magazines[], lincolnItems{}` - Mode flags: `campaignMode, playthroughType, mapView, gameContext` **`js/state.js` diff v2.6.0 → dev = only 2 hunks:** a `seedInventory` constant added to `GAME_DEFS` (not a state field) and a `_loadingSave` guard in `saveState()`. The `let state = {…}` defaults block and `migrateState` are **byte-identical**. ⇒ no new state fields exist in the 2.7.0 build. ### 3. v2.6.0 baseline load path (does it drop / reject?) `loadCloudSave(docId)` in v2.6.0 `js/cloud.js`: 1. `verifySaveEnvelope` → `legacy`/`ok` (never future for these saves; see §1). 2. `snapRollingBackup()` — backs up current local state before loading. 3. `sanitizeImportedContainer(data.robco_v8)` — **passthrough**: `Object.assign({}, s)` copies *all* fields, then coerces only the known ones (numbers, limbs, quests, inventory, squad, perks, status, equipped). It **never deletes** unknown fields and `return`s the full object. (`equipped` is the only sub-object rebuilt, to exactly `{weapon,armor,headgear}` — the only sub-keys that exist; no loss.) 4. `migrateState(version, campaign)` — **additive**: only adds missing fields and migrates legacy keys (old flat faction keys, `macros`); never strips unknown fields. 5. writes `robco_v8` to localStorage, sets `playstyle`, reloads. Boot hydration (`js/ui-core.js`, **unchanged** v2.6.0 ↔ dev): `state = { ...state, ...activeCampaign }` — **spread merge**, so every saved field lands in `state`. ⇒ Load **preserves every field**. Reject-as-future and field-drop both impossible. ### 4. THE REAL RISK — load → re-save round-trip After load, `state` holds **all** fields (spread merge). On any subsequent save/push, `saveCurrentToCloud` does `robco_v8.campaigns[ctx] = JSON.parse(JSON.stringify(state))` — serializing the **full** state. No field is stripped going back out. **Round-trip is lossless.** This is the permanent-data-loss path the owner feared, and it is closed. ### One real (harmless) difference dev's `sanitizeImportedContainer` gained extra *defensive coercion* for the Phase-6 fields (drop nulls in `collectibles/traits/skillBooks/magazines`, whitelist `lincolnItems` dispositions, clamp faction `fame`/`infamy` to non-negative ints). v2.6.0 lacks that coercion but still **carries the fields through untouched**. Because the saves were written by the app itself (well-formed values), the absence of extra coercion changes nothing on load. (Only relevance: a *hand-corrupted* save could pass a malformed value into v2.6.0 that dev would have cleaned — not the owner's case.) --- ## SAFE ONE-TIME PROCEDURE ### (a) Back up the cloud saves first — YES, do this (belt-and-suspenders) Reverting `main` is a **git/deploy** operation; it does **not** touch Firestore. The cloud documents (`users/{uid}/saves/{docId}` in project `nv-overlord`) persist unchanged regardless of which code is deployed. Still, take a cold backup before the revert: - **Easiest (in-app, while still on the 2.7.0 build):** for each cloud save, load it, then use **EXPORT SAVE FILE** to write a local `.json` to disk. Keep those files. - **Or (no code dependency):** Firebase console → Firestore → project `nv-overlord` → `users/{uid}/saves` → export/copy each save doc's JSON. (A `gcloud firestore export` to a bucket also works if configured.) This backup is insurance only — analysis shows the saves load cleanly as-is. ### (b) Normalize the saves to a "v2.6.0-clean" shape? — **NO. Not needed.** The saves are **already** v2.6.0-shaped (identical state schema). v2.6.0's import is already preserve-on-passthrough. No save surgery, no reverted-code patch, no migration script. Touching the saves would add risk for zero benefit. All real game data (caps/level/inventory/skills/factions/perks/quests/trackers) is preserved automatically. ### (c) Version stamp so v2.6.0 reads them as current, not future — **NO change needed.** Saves already carry `version: "2.6.0"`, which equals v2.6.0's `APP_VERSION`, so the future-version branch never executes. Do **not** rewrite the stamp. > Contingency only (does not apply here): if any save somehow carried `version` > 2.6.0, > v2.6.0 would show a **warning + "Force-load anyway?" confirm** — and even force-loading > still runs the same passthrough sanitize + additive migrate, so **even then there is no > data loss**, just a scary prompt. Since every stamp is exactly 2.6.0, the prompt won't > appear. ### Irreversible steps needing owner approval - **None on the save/data side.** No destructive Firestore writes or deletes are required; do not delete or overwrite any cloud doc. - The **`main` revert itself** (revert/reset `main` → `v2.6.0`, bump `CACHE_NAME`, push) is the owner's separate deploy action — out of scope here and unrelated to save safety. Per Protocol 1/16 it needs a cache bump so cached clients pick up the reverted build; that does not affect the saves. --- ## POST-REVERT SANITY CHECK (optional, after the owner deploys v2.6.0) 1. Hard-reload prod + "REBOOT TERMINAL" so the reverted build is actually live. 2. Open ACCOUNT → cloud saves list; load one 2.7.0-era save. 3. Confirm **no** "future version" prompt appears and it loads silently. 4. Spot-check a Phase-6 field survived: trackers (snow globes/bobbleheads, traits, skill books, magazines), faction fame/infamy, caps/level/inventory. 5. Re-save to cloud, reload, load again — confirm the round-trip kept everything. --- ## ONE-LINE ANSWER FOR THE OWNER Your cloud saves are safe — v2.6.0 *is* the Phase-6 release, the version stamp never left 2.6.0, and every load/save stage is passthrough, so reverting `main` to v2.6.0 will read your saves as current and lose nothing. Take a quick EXPORT-SAVE backup as insurance, then revert with confidence. No save editing or version-stamp surgery required.
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).