Files
xenia-rs/audit-runs/cache-subsystem-plan/persistent-experiment.md
MechaCat02 ef93a4fa14 handoff: VSync/event-wedge fixes + iterate 2.A–2.BC research notes
Source changes (dormant parity infra, retained from iterate 2.AI/2.AO):
- xenia-kernel/exports.rs: nt_create_event manual_reset polarity +
  related event wiring
- xenia-gpu/mmio_region.rs: D1MODE_VBLANK_VLINE_STATUS hardcode parity

Also lands the audit-runs/ analysis notes (.md/.txt/.json digests) for the
iterate 2.x VSync/0x10e8/0x1004 wedge investigation. Raw trace dumps
(.jsonl/.gz/.csv/.stdout) and agent worktrees (.claude/) are gitignored as
regenerable local artifacts — see memory + HANDOFF for the running findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 07:19:08 +02:00

168 lines
6.3 KiB
Markdown

# Persistence experiment — `XENIA_CACHE_PERSIST=1` impact on matched-prefix
## Setup
* Binary: `xenia-rs/target/release/xrs-c10` (active C+10 build, both engines).
* Window: 50M instructions per boot.
* Flag: `XENIA_CACHE_PERSIST=1`; mount falls back to `$HOME/.local/share/xenia-rs/cache`
because `XDG_DATA_HOME` is empty on the host.
* Comparison baseline: C+10 default-tmpdir digest
`instructions=50000002 imports=40465 swaps=1 draws=0` (with stable-digest md5
`b8fa0e0460359a4f660adb7605e053de`); event log
`xenia-rs/audit-runs/phase-c10-NtQueryFullAttributesFile/canary.jsonl`.
* Diff tool: `xenia-rs/tools/diff-events/diff_events.py --tid-map 6=1`.
## Boot-by-boot digests
| boot | mode | instructions | imports | swaps | draws |
|---|---|---|---|---|---|
| C+10 baseline (default tmpdir) | wipe-on-boot | 50000002 | 40465 | 1 | 0 |
| Persist boot 1 (cold persist) | persistent | **50000003** | **40485** | 1 | 0 |
| Persist boot 2 (warm persist) | persistent | 50000003 | 40485 | 1 | 0 |
The persistence path is +1 instruction / +20 imports over the default-tmpdir
baseline. Warm boot is identical to cold boot at the digest level (no
regression observed at 50M; AUDIT-053's regression was at 500M+).
## On-disk cache state per boot
### After persist boot 1 (cold)
Total: 7 files / 1.4 MB.
```
d 4096 /access ← BUG: should be 240 B file (canary)
d 4096 /ignore ← BUG: not in canary's cache
d 4096 /recent ← BUG: should be 160 B file (canary)
d 4096 /d4ea4615
d 4096 /d4ea4615/e
d 4096 /69d8e45c
d 4096 /69d8e45c/9
d 4096 /69d8e45c/e
d 4096 /aab216c3
d 4096 /aab216c3/5
d 4096 /aab216c3/a
f 2400 /d4ea4615e46ee8ca.tmp
f 5784 /69d8e45ce534ffea.tmp
f 12288 /69d8e45c9355f2f8.tmp
f 12288 /69d8e45c973a5c0a.tmp
f 12288 /aab216c35ee70e0a.tmp
f 614400 /aab216c3a2c8c185.tmp
f 685464 /69d8e45c939a9dcc.tmp
```
Notable:
* `access`, `ignore`, `recent` exist as DIRECTORIES (bug #2).
* `.tmp` flat journals exist (cache-build path is firing).
* Hash subdirectories exist (AUDIT-054's `FILE_DIRECTORY_FILE` handling works).
* **Hierarchical leaf files** (e.g. `d4ea4615/e/46ee8ca`) **do not exist**
the `.tmp` → leaf rename is being silently dropped (bug #1).
### After persist boot 2 (warm)
Total: 7 files / 1.6 MB (`.tmp` files grew).
| file | boot 1 | boot 2 | growth | canary's equivalent leaf size |
|---|---|---|---|---|
| `d4ea4615e46ee8ca.tmp` | 2400 | 2800 | +400 | 400 B (`d4ea4615/e/46ee8ca`) |
| `69d8e45ce534ffea.tmp` | 5784 | 6748 | +964 | 964 B (`69d8e45c/e/534ffea`) |
| `69d8e45c9355f2f8.tmp` | 12288 | 14336 | +2048 | 2048 B (`69d8e45c/9/355f2f8`) |
| `69d8e45c973a5c0a.tmp` | 12288 | 14336 | +2048 | 2048 B (`69d8e45c/9/73a5c0a`) |
| `aab216c35ee70e0a.tmp` | 12288 | 14336 | +2048 | 2048 B (`aab216c3/5/ee70e0a`) |
| `aab216c3a2c8c185.tmp` | 614400 | 716800 | +102400 | 102400 B (`aab216c3/a/2c8c185`) |
| `69d8e45c939a9dcc.tmp` | 685464 | 799708 | +114244 | 114244 B (`69d8e45c/9/39a9dcc`) |
**The per-boot growth of each `.tmp` file exactly matches the byte size of the
corresponding canary hierarchical leaf.** Strong indirect evidence that the
`.tmp` contains the same data as canary's leaf but is being appended on each
boot instead of being renamed-to-leaf and consumed.
This is exactly AUDIT-053's "journal-style appends per boot" pattern. AUDIT-053
predicted a `runtime_error` regression at warm-start because the version header
would go stale; in the 50M window of this experiment, that regression has not
yet manifested (AUDIT-054's report had it at 500M+).
## Phase A diff vs C+10 canary baseline
Command:
```
python3 xenia-rs/tools/diff-events/diff_events.py \
--canary xenia-rs/audit-runs/phase-c10-NtQueryFullAttributesFile/canary.jsonl \
--ours xenia-rs/audit-runs/cache-subsystem-plan/persist-warm-events.jsonl \
--tid-map 6=1
```
Result:
```
| canary_tid | ours_tid | matched | canary_total | ours_total | first_divergence_at |
|---|---|---|---|---|---|
| 6 | 1 | 102404 | 315020 | 108471 | 102404 |
```
**Matched-prefix unchanged from C+10 baseline (102404).**
Divergence event:
```
canary [6][102404] kernel.return NtQueryFullAttributesFile return_value=0
ours [1][102404] kernel.return NtQueryFullAttributesFile return_value=0xC0000034
```
Pre-context shows both engines reach the same path query
`cache:\d4ea4615\e\46ee8ca` byte-for-byte, but ours returns NOT_FOUND because
that leaf doesn't exist on disk (only the flat `.tmp` does).
## Conclusion
Persistence is **necessary but not sufficient**. The plan's Stage 1 (rename
fix) is required to convert `.tmp` flat journals into hierarchical leaves. Stage
2 (top-level file misclassification) is required to fix the `access`/`ignore`/
`recent` directory-vs-file bug. Stage 3 (flip default) follows after both
bugs are addressed.
Expected matched-prefix advance after all three stages: hundreds-to-thousands
of events, until a non-cache divergence appears.
## Reproduction commands
```bash
cd "/home/fabi/RE - Project Sylpheed"
# Clean cache for cold start
rm -rf ~/.local/share/xenia-rs/cache
# Boot 1 (cold)
XENIA_CACHE_PERSIST=1 ./xenia-rs/target/release/xrs-c10 check \
-n 50000000 --stable-digest --out /tmp/digest-boot1.json \
"Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso"
# Inspect resulting cache layout
find ~/.local/share/xenia-rs/cache -mindepth 1 -printf '%y %s\t%p\n' | sort
# Boot 2 (warm) — same command, no rm
XENIA_CACHE_PERSIST=1 ./xenia-rs/target/release/xrs-c10 check \
-n 50000000 --stable-digest --out /tmp/digest-boot2.json \
"Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso"
# Boot 2 with Phase A event log
XENIA_CACHE_PERSIST=1 ./xenia-rs/target/release/xrs-c10 exec \
-n 50000000 --quiet \
--phase-a-event-log xenia-rs/audit-runs/cache-subsystem-plan/persist-warm-events.jsonl \
"Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso"
# Diff
python3 xenia-rs/tools/diff-events/diff_events.py \
--canary xenia-rs/audit-runs/phase-c10-NtQueryFullAttributesFile/canary.jsonl \
--ours xenia-rs/audit-runs/cache-subsystem-plan/persist-warm-events.jsonl \
--tid-map 6=1
```
## State at session end
`~/.local/share/xenia-rs/cache/` is left in its post-boot-2 state (7 `.tmp`
files, ~1.6 MB, 3 directories that should be files, 7 empty hash subdirs).
Next session should `rm -rf ~/.local/share/xenia-rs/cache` before Stage 1
work to start from a clean slate.