MEMORY
memory/project-C--Dev--RobCo--RobCo-UOS/project_state.md
sha256 8de9fca695fd7dbe · 16429 bytes ·
original held in the private archive
---
name: project-state
description: "Capped ship-log. Points at the authoritative sources for project state, and preserves the non-derivable decisions, rulings and incidents that were buried in it."
metadata:
node_type: memory
type: project
originSessionId: 33225ceb-85e5-4109-8eef-34c8aac15314
---
## What this file was, and why it stops here
This was a running ship-log — a per-commit narrative of what shipped, with version numbers, test counts, cache revisions, suite lists, branch state and load orders written down as facts.
**It was capped on 2026-07-20 because every state claim in it had rotted.** All of it was derivable from the repo, and all of the derived copies had drifted from the source. The full historical log is preserved in git history; nothing was lost by removing it here.
**Do not restart a ship-log in this file.** If a fact can be looked up, look it up.
## Where each class of fact actually lives
| Want to know | Read |
|---|---|
| Current app version | `APP_VERSION` in source (`js/core/state.js`), and the `CACHE_NAME` string in `sw.js` |
| What shipped, when | `CHANGELOG.md`, and `git log` |
| What's queued / in progress | `QUEUE.md` |
| The protocols and the rulebook | `CLAUDE.md` + `rules/*.md` (`RULES.md` was deleted 2026-07-20 and a gate guard keeps it deleted) |
| Architecture, file map, load order | `ARCHITECTURE.md` and `library/CODE_MAP.md` |
| Test and suite counts | run the gate; the counts live in the runner output, not in prose |
| Long-form design context | `library/BRAIN_DUMP.md` (local-only), `planning/<version>/` (local-only, filed by producing release) |
---
## Correction carried forward
The old file asserted **contradictory** things about Protocol 23 in two different places — "right but UNENFORCED, the fix is a gate guard not a softening of the rule" in one entry, and "superseded" in another.
**The truth: Protocol 23 is ENFORCED.** Suite 236 (static architectural conformance) landed 2026-07-18 and enforces it against a measured debt baseline. The separate memory file arguing the guard is still owed is superseded by that suite.
---
## Open question — UNVERIFIED, check in GitHub settings
`main` branch protection may still list a **required status check under its old job name**. The gate job was renamed when CI became an OS matrix, so a required check pinned to the old name would never report again and would **block the next `dev` → `main` release merge**.
This cannot be read from disk and nothing in git history settles it. **Verify in the repo's Settings → Branches before the next release**, and update the required-check list if the stale name is still there. `dev` is unprotected, so this only bites at release time.
---
# Non-derivable content preserved from the old log
Everything below is here because it is *not* recoverable by reading the repo: owner rulings and their reasoning, things deliberately rejected, incidents that explain why a rule or a guard exists, and environment behaviour that has burned a session more than once.
## Owner rulings and their reasoning
- **The developer/debug console is canonical, not throwaway tooling.** The roadmap's hacking minigame will later unlock it in production builds. Its visibility is centralized in exactly ONE gate function, which today delegates to the staging check and stays false on production. A seam comment marks the unlock point and a test locks it. **Any future unlock work must extend that same function in place — never add a second gate.**
- **The diagnostic shell is stripped from the PROD build only, at deploy time**, leaving the repo source tree intact. Staging keeps it fully working — it is owner tooling and the owner uses it there.
- **"Mark location visited" is ADD-ONLY.** Owner ruled there is no un-mark / forget control; visit records are permanent by design.
- **The karma slider stays editable for every game**, regardless of which karma readout is shown for that game. Owner-mandated.
- **The degraded-boot roll happens BEFORE the first-boot check**, so it can fire on any boot, not only the first. Owner-critical detail — a later refactor that reorders these silently kills the effect. Owner also set the odds deliberately low (~1 in 100).
- **The radio toggle uses ON-semantics, not a mute flag,** and is deliberately kept out of the audio mute key map — folding it in would invert its meaning.
- **The full backup bundle carries campaign/save data ONLY.** Device preferences are deliberately excluded — that is the state/device boundary, not an oversight.
- **The rolling-backup ring is exported for completeness but never re-injected on import.** The live ring must hold the fresh pre-restore undo snapshot, and it is a device-local safety net, not primary portable data.
- **Restore is restore-over, never delete.** Save slots absent from an imported bundle are left as they are — no silent data loss.
- **Cloud push stays manual-button-only.** It must never auto-push on a state or stat change. Guards exist specifically to keep this true.
- **The service-worker update dialog is deliberately NOT routed through the shared modal driver.** It stays its own bespoke non-dismissable trap, and a test locks its exact shape.
- **A workflow-lint guard suite was declined as scope creep** — the single Node-version file plus the Windows CI job is the live guard.
- **Naming: device uptime and campaign time are distinct concepts** and the owner corrected the labels to keep them so. Do not merge them back into one readout.
- **Hand-maintained numbers in docs are a known rot source.** The architecture doc's file-size column was deleted for exactly this reason (the numbers were wildly wrong), and a guard forbids size strings from coming back. Same reasoning applies to any count kept by hand in prose.
- **Release-time production verification is mandatory** and was deliberately preserved when the branch-model refs around it were rewritten.
- **Releases are owner-gated.** Do not start a version-bump / release unit without an explicit go. Several units were explicitly designated stop-and-audit; when a task says stop, stop.
- **`planning/` is ARCHIVE-class: the backup mirrors it ADDITIVELY and never deletes.** This is what makes it safe to clear `planning/` locally once a document has served its purpose. A reorg of that tree is reconciled once, by pruning stale old paths from the archive.
## The full-replace class — CLOSED
The AI-response import path used to do a **full replace** of durable state from whatever array the model returned. The recommendation on record was to generalize the confirm-gated reconciliation discipline to **any destructive write to durable state, regardless of source**, rather than patching one path at a time.
**That is what happened.** The whole class was fixed across every durable collection — reconcile-and-confirm on each, a red-then-green regression test per path, and a static guard blocking reintroduction. The rule to carry forward is the general one: *a destructive write to durable state is confirm-gated regardless of where the data came from; removal-by-omission is never allowed.*
⚠ **Trap for anyone re-checking this.** The import service still contains comments of the form `// The old `state.squad = parsed.squad.map(...)` full-replace…`, left in place deliberately to explain why the reconcile logic beneath them exists. **They are documentation of a removed pattern, not the pattern.** Grepping for the shape hits the prose. *A memory session flagged this class as still-open on 2026-07-20 by trusting this file's then-stale wording without reading the code; corrected the same day. Verify against executable code before asserting a data-safety risk.*
## Scope rulings that were re-litigated and should not be again
- **A "shell" unit is shell only.** The bezel/nav overhaul unit was confirmed by the owner — after initial ambiguity, then reconfirmed again after it shipped — as chrome and navigation with **zero subsystem content dressing**. The mockup's "renders unchanged until the later unit" placards were the intended end state, not placeholders to fill in. Later units' scope was pulled forward into a shell unit once already; do not do it again.
- **A single global preference was accepted in place of a per-game one** when the per-game split turned out to be the only unbuilt nuance of a unit — the owner accepted the simpler behaviour rather than build the split. Per-game storage was later added anyway when it was genuinely wanted; the point that survives is that the owner will accept the simpler shape when asked.
## Incidents — why certain rules and guards exist
- **Live data loss from an AI-driven full replace.** A do-nothing model turn returned a short/empty inventory array; because an empty array is still an array, it passed the guard and **silently wiped held items — the owner lost a looted weapon in a live session.** This is why an AI-supplied array is treated as a *reconciled proposal*, never an assignment: additions apply immediately, reductions and drops are confirm-gated, and anything the model omits is kept. Removal-by-omission is never allowed.
- **A concurrent session hard-reset the worktree mid-build**, wiping all uncommitted work; git status simply went clean and the loss was found only by grepping for a symbol that should have been there. **When another session is active in this folder, commit and push frequently.**
- **A hung session's editor flushed partial work onto disk after a reset** — same file content, no new commits. Verify what is actually on disk before building on top of it.
- **Staging PWA froze on an old build.** Cloudflare 308-canonicalizes `/index.html` to `/`, and the Cache API rejects a redirected response inside `cache.addAll()` — which is all-or-nothing, so one redirected precache entry failed the entire service-worker install and no new worker ever activated. Staging-only; GitHub Pages serves `/index.html` at 200. Fixed by stripping that entry from the staging build's precache list. **Any all-or-nothing precache list is a landmine: one bad entry disables updates entirely.**
- **A phone occasionally booted the desktop layout on first load after an update.** A standalone PWA can momentarily settle with a wide layout viewport, so a width-only media query matched on a phone. **Gate desktop layout on input modality (`hover:hover` and `pointer:fine`) in addition to width**, and use the same media query in JS rather than a raw width read.
- **The update prompt never fired in the installed PWA.** It was gated solely on the live service-worker controller, which is null on a standalone launch, and detection only ran at registration plus an interval — but an installed PWA *resumes* rather than reloads, so neither ever re-fired on the load the owner actually sees. Fixed with a persistent "has been controlled before" flag plus focus/visibility re-checks.
- **A boot-order hazard that only appears on some boots:** the UI and API files are static `<script>` tags and can execute before the dynamically-loaded core state file. **Any top-level (module-scope) code in those files that references a global defined in the core state file is unsafe.** Only code inside a function body called later from `window.onload` is safe. This shipped and was caught by the boot smoke check, not by unit tests.
- **A guard can be inert for weeks while looking green.** The cache-bump guard compared the staged worker against `origin/main`, but the working branch is always ahead of a release-only `main`, so the comparison passed unconditionally no matter what was staged — only manual discipline was moving the revision. **Assert against the branch's own HEAD, not another branch.**
- **A comparison pinned to the literal `HEAD` silently mutates into a different rule.** A test meant to verify one past change touched nothing outside its scope became "block any future change to these files forever" as HEAD moved. **Pin historical comparisons to the actual commit.**
- **A test can be tautological.** One guard grepped the runner's own source for probe strings that were the guard's own array literal — it proved nothing. It was replaced by an end-of-run count reconciliation, which makes the doc-count sync mechanical instead of honour-system.
- **Static/grep tests can be satisfied by a comment.** Behavioural tests that execute the real function in a sandbox are what actually prove behaviour. **Twin-check discipline:** a similarly-named function exercised by another suite is very easy to mistake for coverage of the one you changed.
- **A green gate is not proof it works in production.** An invisible panel shipped green, and a staging service-worker failure reached users under a green gate. Post-deploy verification is honour-system; treat it as a real step.
- **A deploy broke because the deploy workflow copied a directory that had been removed** in the working branch. Deploy config drifts from the tree it deploys — check it at release time.
- **CSS specificity beat a new component's base rules:** a single-class rule lost to an existing element+class rule, so every item rendered in the wrong shape. Match the existing specificity (element+class) rather than relying on source order.
## Release and deploy mechanics that are not obvious
- **Merge `dev` → `main` with `--no-ff`.** A fast-forward makes the tip commit shared between both branches, and the Pages environment attributes it to `dev`, so the branch policy **rejects the production deploy** even though the tag and release succeed. A real merge commit makes the tip main-only and avoids the whole problem.
- **A same-version hotfix on `main` never auto-deploys.** The release workflow is version-gated, so if the tag already exists it skips the deploy. Dispatch the deploy workflow manually against `main`.
- *(Historical, verify before relying on it: at the time this was written the environment had no `gh` binary and no token env var, but the git credential-manager token carried `workflow` scope, so workflow dispatch was done via the API with a token pulled from `git credential fill` — never printed.)*
## Environment behaviour that has cost sessions
- **Localhost serves stale service-worker-cached JS.** Unregister the service worker and clear caches before trusting a preview of a changed served file. This has bitten repeatedly, and has produced "function is undefined" errors that look like real bugs.
- **Preview CSSOM and `getComputedStyle` go stale after many reloads and cache clears in one session** — they have reported values contradicting a verifiably-correct inline style. Hard-reload before trusting layout measurements, and verify via the inline style attribute and the stylesheet rule text rather than a computed-style snapshot.
- **Use `scrollWidth === innerWidth` as the overflow signal, not an absolute pixel value** — the preview browser misreports `innerWidth` regardless of the resize tool's width.
- **PowerShell's `-match` is case-insensitive by default; JavaScript's `.test()` is not.** Any literal-text guard shared between tools can diverge on case. Related, and still live on this machine: never write non-ASCII content through PowerShell.
- **Prettier reflows long object literals and long headings across lines.** Any test regex matching source text must tolerate wrapping, or it will pass today and break on the next format run.
- **When inserting into a file before the last occurrence of a closing pattern, preserve everything after that pattern** — a naive slice-and-append silently drops the file's tail.
- The PowerShell test runner and the two-runner parity regime are **retired**. Ignore any older note that assumes two runners must agree.
- `.gitignore` lists `CLAUDE.md` as ignored, but it is already tracked and on GitHub. Pre-existing and harmless — don't "fix" it.
## Invariants carried over from the retired OneDrive-path memory store
- **Never add `clients.claim()` to the service worker** — it causes a black-screen boot.
- **The world map grid size is driven by the stored map-view setting, never by a viewport measurement.** Do not reintroduce `offsetWidth`/`innerWidth` into that size decision.
- **The left column needs `min-width: 0` in the BASE (mobile) styles, not only inside the desktop media query.** Without it, the single flexible column expands to min-content on phones when a wide panel is open.
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).