# Phase C+12 re-validation (cold-vs-cold) ## Cache-wipe protocol (CORRECTED from C+11.1) The Phase C+11.1 protocol wiped `~/.local/share/Xenia/cache`, but the Windows-debug canary running under wine actually stores its cache at `xenia-canary/build-cross/bin/Windows/Debug/cache/` (the binary directory; verified by canary's `Storage root: Z:\...\build- cross\bin\Windows\Debug` startup log). C+12's effective wipe targets the binary directory. Step-by-step (the protocol used here): 1. Backup canary's binary-dir cache: `tar -czf /tmp/canary-binary-cache-backup.tar.gz -C xenia-canary/build-cross/bin/Windows/Debug cache` (4.7 MB, 23 files). Copy to `audit-runs/phase-c12-NtQueryFullAttributesFile/ canary-binary-cache-pre-wipe.tar.gz`. 2. Wipe both engines' caches: * `find xenia-canary/build-cross/bin/Windows/Debug/cache -mindepth 1 -delete` * `find xenia-canary/build-cross/bin/Windows/Debug/cache0 -mindepth 1 -delete` * `find xenia-canary/build-cross/bin/Windows/Debug/cache1 -mindepth 1 -delete` * `find ~/.local/share/xenia-rs/cache -mindepth 1 -delete` 3. Cold ours run: `./xenia-rs/target/release/xrs-c12 exec --phase-a-event-log /tmp/ours-cold-c12-v2.jsonl -n 50000000 ""` (~3 s wallclock). 4. Cold canary run (background, killed after 5M+ canary events): `cd xenia-canary/build-cross/bin/Windows/Debug && /usr/bin/wine ./xc-c12.exe --mute=true --phase_a_event_log_path=canary-cold-c12-v2.jsonl ""` Killed via `wineserver -k` after ~120 s wallclock once the jsonl reached ~6 M lines (250 k tid=6 events buffered). 5. Truncate canary jsonl per-tid (cap tid=6 at 250 k, other tids at 20 k) to keep the diff tractable. Truncated file is 119 MB / 533 k lines (kept) — the differ runs on this. 6. Diff: `python3 xenia-rs/tools/diff-events/diff_events.py --canary --ours /tmp/ours-cold-c12-v2.jsonl --out /tmp/diff-c12-cold-v2.md`. 7. Restore canary's binary-dir cache from backup so future runs keep the oracle intact. ## Canonical post-C+12 cold-vs-cold matched-prefix table | canary_tid | ours_tid | matched | canary_total | ours_total | first_divergence_at | notes | |---|---|---|---|---|---|---| | 6 | 1 | **103862** | 250000 | 108471 | 103862 | **main chain — +1458 events vs C+11.1**. New divergence: NtCreateFile `game:\dat\files.tbl` returns canary=STATUS_OBJECT_NAME_NOT_FOUND (0xC0000034) / ours=STATUS_SUCCESS (synth-empty stub for missing disc files; AUDIT-006 fallback). | | 4 | 11 | 9 | 20000 | 9 | — | sister — no divergence within the 9 ours events | | 7 | 2 | 29 | 29 | 30 | — | sister — no divergence within the 29 canary events | | 12 | 7 | 2 | 10532 | 3 | 2 | sister — pre-existing KeWaitForSingleObject return (TIMEOUT/SUCCESS); NOT regressed | | 14 | 9 | 39 | 20000 | 75 | 39 | sister — pre-existing XAudio init divergence; NOT regressed | | 15 | 10 | 15 | 20000 | 15 | — | sister — no divergence within the 15 ours events | ## Gate matrix | gate | result | notes | |---|---|---| | Build (cargo build --release) | PASS | 1 unrelated `dead_code` warning, no errors | | Kernel tests (172 → 177) | PASS | 5 new C+12 tests, all pass | | Full workspace tests | PASS | 702 tests pass (sum of all crates) | | Determinism — 3× cold stable-digest | PASS | `ad4f74ee324fdedb0bfdd4cc4c6468e9` (all 3) — IDENTICAL to C+11.1 baseline | | `--stable-digest` digest unchanged | PASS | C+12 fix is observation-only on the stable counters | | Phase B `image_loaded_sha256` | PASS | `ea8d160e9369328a5b922258a92113efb8d7ce3e1a5c12cc521e375985c91c18` unchanged | | Cold-vs-cold matched prefix | PASS | **102404 → 103862 (+1458)** on main chain | | Sister-chain regression check | PASS | All 5 sister chains unchanged | ## Source data | file | size | notes | |---|---|---| | `canary-cold.jsonl` | 1.4 GB | full canary cold run, 6.0 M lines (post-kill) | | `canary-cold-tid6-250k.jsonl` | 119 MB | per-tid truncated for diff (tid 6: 250 k, others: 20 k cap) | | `ours-cold.jsonl` | 28 MB | ours cold run, 121 k tid=1 events | | `diff-cold-vs-cold.md` | 8 KB | the `diff_events.py` output | | `canary-binary-cache-pre-wipe.tar.gz` | 4.7 MB | canary's 23-file oracle preserved before wipe (binary-dir!) | | `digest-cold-stable-{1,2,3}.json` | <1 KB each | 3 determinism runs, identical hash | | `phase-b-snap/ours/` | ~5 MB | Phase B 5-file snapshot with `image_loaded_sha256` | ## Notes * The +1458-event advance lands at a new tid=6 divergence: `NtCreateFile game:\dat\files.tbl` returns SUCCESS in ours because the disc dump doesn't ship this file but ours's `open_vfs_file` fallback synthesizes a 0-byte file (see `exports.rs:1399-1422`), while canary returns `STATUS_OBJECT_NAME_NOT_FOUND` since it has no equivalent stub. This is a long-standing intentional fallback (rationale: "synthesize empty file lets the game's existence probe succeed for files the rip lacks") that now becomes the next divergence target. * The synth-empty fallback was originally introduced to prevent `XamShowDirtyDiscErrorUI` on missing disc files — removing it outright might regress past audits. Phase C+13 should investigate whether Sylpheed actually needs the file or whether the dirty-disc path is now correctly gated by other logic. * Stable digest unchanged confirms the C+12 fix is a **pure observation change**: the in-memory entry tree and the `nt_query_full_attributes_ file` rewrite affect only the kernel's response on `cache:` lookup probes, not the per-instruction execution path observed by the digest. The 1458-event advance is the kernel-export-side effect.