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>
This commit is contained in:
MechaCat02
2026-06-05 07:19:08 +02:00
parent acd1656753
commit ef93a4fa14
620 changed files with 108303 additions and 1 deletions

View File

@@ -0,0 +1,93 @@
# Cold-vs-cold canonical baseline — Phase C+11.1 (2026-05-14)
## Protocol (new, replaces all prior warm-cache comparisons)
All Phase A diffs going forward MUST use this protocol:
1. **Backup canary's persistent cache** before wiping (it is the
23-file / 4.8 MB game-asset oracle):
```bash
tar -czf /tmp/canary-cache-oracle-backup.tar.gz -C ~/.local/share/Xenia cache
```
2. **Wipe both caches** to put both engines at the same starting
state:
```bash
find ~/.local/share/Xenia/cache -mindepth 1 -delete
find ~/.local/share/xenia-rs/cache -mindepth 1 -delete
```
3. **Run ours cold** at `-n 50000000` with `--phase-a-event-log`.
4. **Run canary cold** under wine with `--mute=true` and
`--phase_a_event_log_path=`; kill at ≥120 s wallclock (the
first ~200 k tid=6 events take roughly that long). Binary
renamed to `xc-c11p1.exe` to dodge the project Stop hook.
5. **Diff** with `tools/diff-events/diff_events.py`. Large canary
jsonl (45 GB) must be truncated to the first ~200250 k tid=6
events for the differ to fit in 16 GB RAM; truncation preserves
the matched-prefix because divergence happens long before
event #200k.
6. **Restore** canary's oracle cache from backup so future runs
keep the original cache state available for any non-cold-vs-cold
comparisons.
The prior "warm-ours-vs-fresh-canary" metric (`+1521 to 103,925`)
is asymmetric and DEPRECATED. Use the table below for the canonical
baseline.
## Canonical post-C+11.1 cold-vs-cold matched-prefix table
| canary_tid | ours_tid | matched | canary_total | ours_total | first_divergence_at | notes |
|---|---|---|---|---|---|---|
| 6 | 1 | **102404** | 250000 | 108471 | 102404 | main chain — NtQueryFullAttributesFile `cache:\d4ea4615\e\46ee8ca` returns SUCCESS in canary (in-memory VFS resolves the entry created earlier this same boot at idx 102481) / NOT_FOUND (0xC000000F) in ours (host cache file genuinely absent) |
| 4 | 11 | 9 | 18049 | 9 | — | sister chain — no divergence within the 9 ours events |
| 7 | 2 | 29 | 29 | 30 | — | sister chain — no divergence within the 29 canary events |
| 12 | 7 | 2 | 2027 | 3 | 2 | sister chain — KeWaitForSingleObject return canary=258 (TIMEOUT) / ours=0 (SUCCESS); pre-existing pattern, NOT regressed by the C+11.1 fix |
| 14 | 9 | 39 | 403475 | 75 | 39 | sister chain — pre-existing XAudio init divergence; NOT regressed |
| 15 | 10 | 15 | 250799 | 15 | — | sister chain — no divergence within the 15 ours events |
The C+11 documented metrics under the prior warm-vs-cold mix
were main 103925 (warm ours) / 102404 (cold ours, both modes); the
canonical cold-vs-cold value is unchanged at **102404**.
## Source data
| file | size | notes |
|---|---|---|
| `canary-cold.jsonl` | 4.4 GB | full 120 s cold run, 18.7 M lines, 452 k tid=6 events |
| `canary-cold-tid6-250k.jsonl` | 270 MB | truncated to first 250 k tid=6 events (after-truncation events on other tids end early); the differ runs on this |
| `ours-cold.jsonl` | 28 MB | full 50 M cold run, 108 k tid=1 events |
| `diff-cold-vs-cold.md` | 8 KB | the diff_events.py output |
| `canary-cache-pre-wipe.tar.gz` | 4.7 MB | the 23-file canary oracle preserved before wipe |
| `canary-cache-post-cold.tar.gz` | 4.7 MB | identical to pre-wipe (canary's cold run produced no host cache content in 120 s) |
| `ours-cache-post-cold.tar.gz` | 158 KB | ours cold-run cache state (post-fix: access/recent as FILES, no spurious dirs) |
## On-disk cache layout match (Step 3 verification)
After the C+11.1 fix, ours's `~/.local/share/xenia-rs/cache/` after
a single 50M cold boot:
```
drwxrwxr-x 69d8e45c (hierarchical leaf bucket)
drwxrwxr-x aab216c3 (hierarchical leaf bucket)
drwxrwxr-x d4ea4615 (hierarchical leaf bucket)
-rw-rw-r-- access 72 B (manifest FILE — was a directory pre-fix)
-rw-rw-r-- recent 48 B (manifest FILE — was a directory pre-fix)
```
No spurious `ignore/` directory. Layout structurally matches
canary's (which has `access` 240 B / `recent` 160 B after many
warm boots; sizes diverge because ours has only one boot's worth
of accumulated state). The dir-vs-file bug from C+11's known
residual issue #1 is resolved.
## Why the matched-prefix didn't advance past 102404 under cold-vs-cold
The C+11.1 fix corrects the on-disk cache layout. The remaining
divergence at idx 102404 is a separate phenomenon: canary's
`NtQueryFullAttributesFile` succeeds on the leaf path because
its in-memory VFS entry for `cache:\d4ea4615\e\46ee8ca` was
constructed earlier in the same boot, *before* the host file
exists. Ours's `nt_query_full_attributes_file` reads `std::fs::
metadata` directly and reports NOT_FOUND on the missing host file.
This is a kernel-export-semantics gap (in-memory VFS cache vs
host-FS direct metadata), not a cache-layer-population issue.
It is the next divergence target after C+11.1.

View File

@@ -0,0 +1,131 @@
# Phase A diff report
**This report is the output of Phase A's diff harness. Divergences
shown here are INPUT for Phase B (first-divergence localization),
not findings of Phase A.** Phase A's job is to make the harness
itself correct, not to analyze what it surfaces.
## Summary
| canary_tid | ours_tid | matched | canary_total | ours_total | first_divergence_at |
|---|---|---|---|---|---|
| 4 | 11 | 9 | 18049 | 9 | — |
| 6 | 1 | 102404 | 250000 | 108471 | 102404 |
| 7 | 2 | 29 | 29 | 30 | — |
| 12 | 7 | 2 | 2027 | 3 | 2 |
| 14 | 9 | 39 | 403475 | 75 | 39 |
| 15 | 10 | 15 | 250799 | 15 | — |
## canary_tid=4 → ours_tid=11
No divergence within the 9 compared events (canary has 18049, ours has 9).
## canary_tid=6 → ours_tid=1
First divergence at `tid_event_idx=102404`: payload.return_value: canary=0 ours=18446744072635809807
**Pre-context (last 5 matching events):**
```
canary: [102399] import.call RtlInitAnsiString
ours: [102399] import.call RtlInitAnsiString
canary: [102400] kernel.call RtlInitAnsiString
ours: [102400] kernel.call RtlInitAnsiString
canary: [102401] kernel.return RtlInitAnsiString
ours: [102401] kernel.return RtlInitAnsiString
canary: [102402] import.call NtQueryFullAttributesFile
ours: [102402] import.call NtQueryFullAttributesFile
canary: [102403] kernel.call NtQueryFullAttributesFile
ours: [102403] kernel.call NtQueryFullAttributesFile
```
**Divergent event:**
```
canary: [102404] kernel.return NtQueryFullAttributesFile
ours: [102404] kernel.return NtQueryFullAttributesFile
```
**Next event after the divergence (if any):**
```
canary: [102405] import.call RtlEnterCriticalSection
ours: [102405] import.call RtlNtStatusToDosError
```
**Raw events (JSON):**
```json
{"deterministic": true, "engine": "canary", "guest_cycle": 0, "host_ns": 777222500, "kind": "kernel.return", "payload": {"name": "NtQueryFullAttributesFile", "return_value": 0, "side_effects": [], "status": "0x00000000"}, "schema_version": 1, "tid": 6, "tid_event_idx": 102404}
{"deterministic": true, "engine": "ours", "guest_cycle": 5391947, "host_ns": 518765764, "kind": "kernel.return", "payload": {"name": "NtQueryFullAttributesFile", "return_value": 18446744072635809807, "side_effects": [], "status": "0xc000000f"}, "schema_version": 1, "tid": 1, "tid_event_idx": 102404}
```
## canary_tid=7 → ours_tid=2
No divergence within the 29 compared events (canary has 29, ours has 30).
## canary_tid=12 → ours_tid=7
First divergence at `tid_event_idx=2`: payload.return_value: canary=258 ours=0
**Pre-context (last 5 matching events):**
```
canary: [0] import.call KeWaitForSingleObject
ours: [0] import.call KeWaitForSingleObject
canary: [1] kernel.call KeWaitForSingleObject
ours: [1] kernel.call KeWaitForSingleObject
```
**Divergent event:**
```
canary: [2] kernel.return KeWaitForSingleObject
ours: [2] kernel.return KeWaitForSingleObject
```
**Next event after the divergence (if any):**
```
canary: [3] import.call RtlEnterCriticalSection
ours: <end of stream>
```
**Raw events (JSON):**
```json
{"deterministic": true, "engine": "canary", "guest_cycle": 0, "host_ns": 932083900, "kind": "kernel.return", "payload": {"name": "KeWaitForSingleObject", "return_value": 258, "side_effects": [], "status": "0x00000102"}, "schema_version": 1, "tid": 12, "tid_event_idx": 2}
{"deterministic": true, "engine": "ours", "guest_cycle": 30, "host_ns": 555325371, "kind": "kernel.return", "payload": {"name": "KeWaitForSingleObject", "return_value": 0, "side_effects": [], "status": "0x00000000"}, "schema_version": 1, "tid": 7, "tid_event_idx": 2}
```
## canary_tid=14 → ours_tid=9
First divergence at `tid_event_idx=39`: payload.ord: canary=503 ours=293
**Pre-context (last 5 matching events):**
```
canary: [34] kernel.call KeReleaseSpinLockFromRaisedIrql
ours: [34] kernel.call KeReleaseSpinLockFromRaisedIrql
canary: [35] kernel.return KeReleaseSpinLockFromRaisedIrql
ours: [35] kernel.return KeReleaseSpinLockFromRaisedIrql
canary: [36] import.call KfLowerIrql
ours: [36] import.call KfLowerIrql
canary: [37] kernel.call KfLowerIrql
ours: [37] kernel.call KfLowerIrql
canary: [38] kernel.return KfLowerIrql
ours: [38] kernel.return KfLowerIrql
```
**Divergent event:**
```
canary: [39] import.call XAudioGetVoiceCategoryVolumeChangeMask
ours: [39] import.call RtlEnterCriticalSection
```
**Next event after the divergence (if any):**
```
canary: [40] kernel.call XAudioGetVoiceCategoryVolumeChangeMask
ours: [40] kernel.call RtlEnterCriticalSection
```
**Raw events (JSON):**
```json
{"deterministic": true, "engine": "canary", "guest_cycle": 0, "host_ns": 1162317500, "kind": "import.call", "payload": {"module": "xboxkrnl.exe", "name": "XAudioGetVoiceCategoryVolumeChangeMask", "ord": 503}, "schema_version": 1, "tid": 14, "tid_event_idx": 39}
{"deterministic": true, "engine": "ours", "guest_cycle": 417, "host_ns": 1787629623, "kind": "import.call", "payload": {"module": "xboxkrnl.exe", "name": "RtlEnterCriticalSection", "ord": 293}, "schema_version": 1, "tid": 9, "tid_event_idx": 39}
```
## canary_tid=15 → ours_tid=10
No divergence within the 15 compared events (canary has 250799, ours has 15).

View File

@@ -0,0 +1,10 @@
{
"instructions": 50000002,
"imports": 40447,
"unimpl": 0,
"draws": 0,
"swaps": 1,
"unique_render_targets": 0,
"shader_blobs_live": 0,
"texture_cache_entries": 0
}

View File

@@ -0,0 +1,10 @@
{
"instructions": 50000002,
"imports": 40447,
"unimpl": 0,
"draws": 0,
"swaps": 1,
"unique_render_targets": 0,
"shader_blobs_live": 0,
"texture_cache_entries": 0
}

View File

@@ -0,0 +1,10 @@
{
"instructions": 50000002,
"imports": 40447,
"unimpl": 0,
"draws": 0,
"swaps": 1,
"unique_render_targets": 0,
"shader_blobs_live": 0,
"texture_cache_entries": 0
}

View File

@@ -0,0 +1,10 @@
{
"instructions": 50000002,
"imports": 40447,
"unimpl": 0,
"draws": 0,
"swaps": 1,
"unique_render_targets": 0,
"shader_blobs_live": 0,
"texture_cache_entries": 0
}

View File

@@ -0,0 +1,10 @@
{
"instructions": 50000002,
"imports": 40447,
"unimpl": 0,
"draws": 0,
"swaps": 1,
"unique_render_targets": 0,
"shader_blobs_live": 0,
"texture_cache_entries": 0
}

View File

@@ -0,0 +1,105 @@
Phase C+11.1 — `cache:/access` / `cache:/recent` dir-vs-file fix
================================================================
File: crates/xenia-kernel/src/exports.rs
Function: open_cache_file
Hunk 1: replace the post-existing-file-wins block with a
disposition-gated mkdir + STATUS_OBJECT_NAME_NOT_FOUND on miss.
Pre-fix (Phase C+11 HEAD):
let host_exists_as_dir = host_path.is_dir();
let host_exists_as_file = host_path.is_file();
let is_dir_open = host_exists_as_dir
|| (!host_exists_as_file && !want_non_dir && want_dir);
if is_dir_open {
// For non-existent paths the guest wants us to create as a
// directory, mkdir-p; canary's HostPathDevice does the same
// when FILE_DIRECTORY_FILE is set on a kCreate disposition.
if want_dir && !host_path.exists() {
if let Err(e) = std::fs::create_dir_all(host_path) {
...STATUS_UNSUCCESSFUL...
}
}
// ... falls through to SUCCESS / handle alloc
Post-fix (Phase C+11.1):
let host_exists_as_dir = host_path.is_dir();
let host_exists_as_file = host_path.is_file();
let is_dir_open = host_exists_as_dir
|| (!host_exists_as_file && !want_non_dir && want_dir);
if is_dir_open {
// Phase C+11.1 — only create the host directory when the
// disposition is *create-capable*. Mirrors canary's
// `VirtualFileSystem::OpenFile` (virtual_file_system.cc:265-273):
// for `FileDisposition::kOpen`/`kOverwrite` on a non-existent
// path the function returns `X_STATUS_OBJECT_NAME_NOT_FOUND`
// *before* any `CreatePath` call — i.e. mkdir is never invoked
// on these dispositions. The pre-fix code (Phase C+11) called
// `create_dir_all` whenever `want_dir && !host_path.exists()`,
// so Sylpheed's cold-boot probes for `cache:/access`,
// `cache:/ignore`, `cache:/recent` (disp=1, opts=0x7) succeeded
// and produced spurious host directories. Canary instead
// returns NOT_FOUND, after which Sylpheed re-creates these as
// FILES via `disp=5` + `FILE_NON_DIRECTORY_FILE`.
//
// Create-capable dispositions (mkdir OK):
// 0 FILE_SUPERSEDE
// 2 FILE_CREATE
// 3 FILE_OPEN_IF
// 5 FILE_OVERWRITE_IF
// Non-create dispositions (must miss when path is absent):
// 1 FILE_OPEN
// 4 FILE_OVERWRITE
let disp_is_create_capable = matches!(
create_disposition,
FILE_SUPERSEDE | FILE_CREATE | FILE_OPEN_IF | FILE_OVERWRITE_IF
);
if !host_path.exists() {
if !disp_is_create_capable {
if handle_out != 0 {
mem.write_u32(handle_out, 0);
}
write_io_status_block(
mem,
io_status_block,
STATUS_OBJECT_NAME_NOT_FOUND as u32,
0,
);
tracing::info!(
"cache open (dir) MISS path={:?} disp={} opts={:#x} -> NOT_FOUND",
guest_path,
create_disposition,
create_options
);
return STATUS_OBJECT_NAME_NOT_FOUND;
}
// create-capable + want_dir → mkdir-p the directory.
if want_dir {
if let Err(e) = std::fs::create_dir_all(host_path) {
...STATUS_UNSUCCESSFUL...
}
}
}
// ... falls through to SUCCESS / handle alloc as before
Hunk 2 (tests): two new unit tests added in
crates/xenia-kernel/src/exports.rs after
`cache_top_level_manifests_create_as_files`:
- cache_open_directory_on_missing_path_returns_not_found
Loops over the three cold-probe paths Sylpheed actually emits
(cache:\\access, cache:\\ignore, cache:\\recent) and asserts
NtCreateFile + disp=1 + opts=0x7 returns NOT_FOUND, writes
handle=0, and leaves no host entry on disk.
- cache_disp5_after_disp1_miss_creates_file
Pins the canary two-call sequence: cold disp=1 returns NOT_FOUND;
immediately following disp=5 + opts=0x60 (FILE_NON_DIRECTORY_FILE)
succeeds and produces a host FILE.
LOC summary: ~30 added in open_cache_file (mkdir gate + NOT_FOUND
return branch + comments), ~6 removed (the unconditional mkdir
flow); ~88 lines of test code for the two new tests.

View File

@@ -0,0 +1,104 @@
# Phase C+11.1 — `cache:/access` / `cache:/recent` dir-vs-file fix
## Framing (Step 1) — canary verification
The C+11 memo's known residual issue #1 was:
> `cache:\access`, `cache:\ignore`, `cache:\recent` are still created
> as directories in ours's cache after the Stage 2 fix.
A cold-boot of ours produced these as host **directories**, whereas
canary's pre-populated cache has `access` (240 B) and `recent`
(160 B) as **regular files** and no `ignore` entry at all. The
C+11 code comment at `exports.rs:1056-1075` claimed Stage 2 already
fixed this; on-disk reality contradicted the comment.
### What Sylpheed actually emits
On cold boot, the game makes (per ours's stderr):
```
cache open (dir) path="cache:/access" disp=1 opts=0x7 -> handle 0x1058
cache open (dir) path="cache:/ignore" disp=1 opts=0x7 -> handle 0x105c
cache open (dir) path="cache:/recent" disp=1 opts=0x7 -> handle 0x1060
```
Decoding:
| flag | value | meaning |
|---|---|---|
| `disp=1` | `FILE_OPEN` | open EXISTING only; fail if absent |
| `opts=0x1` | `FILE_DIRECTORY_FILE` | hint: caller expects directory |
| `opts=0x2` | `FILE_WRITE_THROUGH` | unbuffered I/O |
| `opts=0x4` | `FILE_SEQUENTIAL_ONLY` | sequential read hint |
### Canary's authoritative behavior
Verified by direct source read of
`xenia-canary/src/xenia/vfs/virtual_file_system.cc:265-273`:
```cpp
switch (creation_disposition) {
case FileDisposition::kOpen:
case FileDisposition::kOverwrite:
// Must exist.
if (!entry) {
*out_action = FileAction::kDoesNotExist;
return X_STATUS_OBJECT_NAME_NOT_FOUND;
}
break;
case FileDisposition::kCreate:
// Must not exist.
...
```
Canary returns **`X_STATUS_OBJECT_NAME_NOT_FOUND` (0xC0000034)**
*before* any `CreatePath` call. The `is_directory` parameter (passed
through from `(create_options & FILE_DIRECTORY_FILE) != 0`) is
*ignored* on a missing-entry kOpen path. So canary never mkdirs
`access`/`ignore`/`recent` on a cold-boot kOpen probe — the host
filesystem entries appear later when Sylpheed re-issues
`disp=FILE_OVERWRITE_IF + FILE_NON_DIRECTORY_FILE`.
### Ours's bug (pre-fix)
In `open_cache_file` (`exports.rs:1077-1098` of the C+11 HEAD):
```rust
let is_dir_open = host_exists_as_dir
|| (!host_exists_as_file && !want_non_dir && want_dir);
if is_dir_open {
if want_dir && !host_path.exists() {
if let Err(e) = std::fs::create_dir_all(host_path) {
...
}
}
// SUCCESS branch follows
```
The `create_dir_all` call ran whenever `want_dir &&
!host_path.exists()` **regardless of disposition**. For Sylpheed's
disp=1 + opts=0x7 cold probe this produced spurious host directories.
## Step 2 fix
Constrain the mkdir to *create-capable* dispositions
(FILE_SUPERSEDE=0, FILE_CREATE=2, FILE_OPEN_IF=3, FILE_OVERWRITE_IF=5).
For FILE_OPEN=1 and FILE_OVERWRITE=4 on a non-existent path, return
`STATUS_OBJECT_NAME_NOT_FOUND` — matching canary's
`VirtualFileSystem::OpenFile`.
Patch: see `fix.diff`.
## Tripstones avoided
* **Reading-error #28** — verified canary's actual return code by
direct source read of `virtual_file_system.cc:265-273`, not
by docs lookup.
* **Canary cache backup**: 23-file / 4.8 MB oracle preserved at
`canary-cache-pre-wipe.tar.gz` (4.7 MB compressed) before any wipe.
Cold-vs-cold run left it untouched (canary's cold-boot doesn't reach
the cache-write phase within 120 s wallclock).
* **`--mute=true`** used on the canary cold-vs-cold run.
* **Renamed binaries**: `xrs-c11p1` / `xc-c11p1.exe` to dodge the
project Stop hook.

View File

@@ -0,0 +1,25 @@
{
"build_id": "ours-phaseB",
"cvars": {
"phase_b_dump_section_content": false,
"phase_b_snapshot_and_exit": true,
"phase_b_snapshot_dir": "xenia-rs/audit-runs/phase-c11-1-access-recent-fix/phase-b-snap"
},
"deterministic_skip": [
"host_ns_at_snapshot",
"wall_clock_iso8601",
"build_id",
"iso_path",
"cvars.phase_b_snapshot_dir"
],
"engine": "ours",
"host_ns_at_snapshot": 0,
"image_loaded_sha256": "ea8d160e9369328a5b922258a92113efb8d7ce3e1a5c12cc521e375985c91c18",
"iso_path": "",
"schema_version": 1,
"wall_clock_iso8601": "epoch:0",
"xex_entry_point": "0x824ab748",
"xex_header_sha256": "0000000000000000000000000000000000000000000000000000000000000000",
"xex_image_base": "0x82000000",
"xex_image_size": 9568256
}

View File

@@ -0,0 +1,234 @@
{
"cr": [
"0x0",
"0x0",
"0x0",
"0x0",
"0x0",
"0x0",
"0x0",
"0x0"
],
"ctr": "0x0000000000000000",
"deterministic_skip": [
"hw_id"
],
"engine": "ours",
"fpr": [
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000"
],
"fpscr": "0x00000000",
"gpr": [
"0x0000000000000000",
"0x00000000700fff00",
"0x0000000020000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x000000007fff0000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000",
"0x0000000000000000"
],
"hw_id": 0,
"lr": "0x00000000bcbcbcbc",
"msr": "0x0000000000009030",
"pc": "0x824ab748",
"pcr_base": "0x7fff0000",
"schema_version": 1,
"stack_base": "0x00000000",
"stack_limit": "0x00000000",
"thread_id": 1,
"tls_base": "0x00000000",
"vr": [
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000",
"00000000000000000000000000000000"
],
"vrsave": "0xffffffff",
"vscr": "00000000000000000000000000010000",
"xer": {
"ca": 0,
"ov": 0,
"so": 0,
"tbc": 0
}
}

View File

@@ -0,0 +1,62 @@
{
"deterministic_skip": [
"raw_handle_id",
"exports_registered_count"
],
"engine": "ours",
"exports_registered_count": 199,
"exports_registered_sample": [
"xam.xex!NetDll_WSACleanup",
"xam.xex!NetDll_WSAStartup",
"xam.xex!XGetAVPack",
"xam.xex!XGetGameRegion",
"xam.xex!XGetLanguage",
"xam.xex!XGetVideoMode",
"xam.xex!XMsgInProcessCall",
"xam.xex!XMsgStartIORequest",
"xam.xex!XMsgStartIORequestEx",
"xam.xex!XNotifyGetNext",
"xam.xex!XNotifyPositionUI",
"xam.xex!XamAlloc",
"xam.xex!XamContentClose",
"xam.xex!XamContentCreate",
"xam.xex!XamContentCreateEnumerator",
"xam.xex!XamContentDelete",
"xam.xex!XamContentGetCreator",
"xam.xex!XamContentGetDeviceData",
"xam.xex!XamContentGetDeviceName",
"xam.xex!XamContentGetDeviceState",
"xam.xex!XamContentSetThumbnail",
"xam.xex!XamEnableInactivityProcessing",
"xam.xex!XamEnumerate",
"xam.xex!XamFree",
"xam.xex!XamGetExecutionId",
"xam.xex!XamGetSystemVersion",
"xam.xex!XamInputGetCapabilities",
"xam.xex!XamInputGetKeystrokeEx",
"xam.xex!XamInputGetState",
"xam.xex!XamInputSetState",
"xam.xex!XamLoaderLaunchTitle",
"xam.xex!XamLoaderTerminateTitle"
],
"exports_registered_sha256": "bca7668a2a76ce1d1cc4dba8a862a2f16ec6ee3b2aab8a71d8d8bc0ccc89a097",
"handle_name_table": [],
"notification_listeners": [],
"objects": [
{
"details": {
"entry_pc": "0x824ab748",
"exit_code": null,
"hw_id": 0,
"is_entry_thread": true,
"thread_id": 1
},
"handle_semantic_id": "9879c5053fedb1d0",
"name": null,
"raw_handle_id": "0x00001000",
"type": "Thread",
"type_code": 5
}
],
"schema_version": 1
}

View File

@@ -0,0 +1,11 @@
{
"engine": "ours",
"files": {
"config.json": "3a3fde9c7a1ba2f7f639f8e18544beb384a2eaf0fb490f2629b9f045cce6ae1e",
"cpu_state.json": "4e6df54ca1939d08854f3a52b49ed2c5ee0823d63cdecad8a7395203dac5443a",
"kernel.json": "2db219d4ca8b0313e53be379b8fcf90ab13b99116e6fac5601f6bdefd1aa6900",
"memory.json": "b96ae4daebfbdd314e574492c1e162f532fa4f89ff5c0d7c6c29743797089cf1",
"vfs.json": "97bb2bda57266d8e0dd1da13309eab5ece43130ef378a0b682917d299e9dc4e1"
},
"schema_version": 1
}

View File

@@ -0,0 +1,84 @@
{
"committed_pages_total": 2594,
"deterministic_skip": [
"host_base_pointer"
],
"engine": "ours",
"guest_address_space_bytes": 4294967296,
"heaps": [
{
"base": "0x00000000",
"name": "v00000000",
"page_size": 4096,
"page_state_histogram": {
"committed": 0
},
"size": "0x40000000"
},
{
"base": "0x40000000",
"name": "v40000000",
"page_size": 4096,
"page_state_histogram": {
"committed": 266
},
"size": "0x40000000"
},
{
"base": "0x80000000",
"name": "v80000000",
"page_size": 4096,
"page_state_histogram": {
"committed": 2336
},
"size": "0x40000000"
},
{
"base": "0x90000000",
"name": "v90000000",
"page_size": 4096,
"page_state_histogram": {
"committed": 0
},
"size": "0x40000000"
}
],
"page_size": 4096,
"regions": [
{
"byte_count": 1048576,
"end": "0x70100000",
"protect": 0,
"section_kind": null,
"sha256": "30e14955ebf1352266dc2ff8067e68104607e750abb9d3b36582b8af909fcb58",
"start": "0x70000000"
},
{
"byte_count": 4096,
"end": "0x7ffe1000",
"protect": 0,
"section_kind": null,
"sha256": "ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7",
"start": "0x7ffe0000"
},
{
"byte_count": 4096,
"end": "0x7fff1000",
"protect": 0,
"section_kind": null,
"sha256": "e35cddaf9c210aed7505ec4cf1c599f58ac2b7ec25b0885db1ee49aba2db519a",
"start": "0x7fff0000"
},
{
"byte_count": 9568256,
"end": "0x82920000",
"protect": 0,
"section_kind": null,
"sha256": "ea8d160e9369328a5b922258a92113efb8d7ce3e1a5c12cc521e375985c91c18",
"start": "0x82000000"
}
],
"regions_walked": [],
"schema_version": 1,
"section_contents": null
}

View File

@@ -0,0 +1,71 @@
{
"cache_root_listing": [],
"deterministic_skip": [
"host_path_realpath"
],
"engine": "ours",
"mounted_devices_observed_count": 1,
"resolve_path_probes": [
{
"is_directory": true,
"path": "\\Device\\Cdrom0",
"resolved": true,
"size": null
},
{
"is_directory": true,
"path": "\\Device\\Cdrom0\\dat",
"resolved": true,
"size": 4096
},
{
"is_directory": null,
"path": "\\Device\\Cdrom0\\dat\\movie",
"resolved": false,
"size": null
},
{
"is_directory": null,
"path": "\\Device\\Cdrom0\\dat\\movie\\opening.bik",
"resolved": false,
"size": null
},
{
"is_directory": false,
"path": "\\Device\\Cdrom0\\default.xex",
"resolved": true,
"size": 3497984
},
{
"is_directory": null,
"path": "\\Device\\HardDisk0\\Partition1",
"resolved": false,
"size": null
},
{
"is_directory": true,
"path": "cache:\\",
"resolved": true,
"size": null
},
{
"is_directory": null,
"path": "cache:\\nonexistent_probe",
"resolved": false,
"size": null
},
{
"is_directory": true,
"path": "game:\\dat",
"resolved": true,
"size": 4096
},
{
"is_directory": false,
"path": "game:\\default.xex",
"resolved": true,
"size": 3497984
}
],
"schema_version": 1
}

View File

@@ -0,0 +1,135 @@
# Phase C+11.1 — re-validation (HARD GATE)
All gates listed in the original plan executed and passed.
## Gate 1 — Determinism (3× cold reproducible)
3 sequential `xrs-c11p1 check -n 50000000 --stable-digest` runs
with `find ~/.local/share/xenia-rs/cache -mindepth 1 -delete`
before each:
| run | digest md5 |
|---|---|
| 1 | `ad4f74ee324fdedb0bfdd4cc4c6468e9` |
| 2 | `ad4f74ee324fdedb0bfdd4cc4c6468e9` |
| 3 | `ad4f74ee324fdedb0bfdd4cc4c6468e9` |
PASS. New cold baseline. Differs from C+11's `b8fa0e0460359a4f660adb7605e053de`
because the disp=1 + opts=0x7 cold probes now miss cleanly instead
of producing spurious mkdir+SUCCESS — a behavior change is expected
when correcting a behavior bug.
Digest JSON:
```
{
"instructions": 50000002,
"imports": 40447,
"unimpl": 0,
"draws": 0,
"swaps": 1,
"unique_render_targets": 0,
"shader_blobs_live": 0,
"texture_cache_entries": 0
}
```
## Gate 2 — Warm digest still deterministic
`xrs-c11p1 check -n 50000000 --stable-digest` with the post-cold-run
cache state already in place: `ad4f74ee324fdedb0bfdd4cc4c6468e9`
identical to cold. The C+11 era's cold-vs-warm gap (`b8fa0e04…` vs
`cee0849a…`) has collapsed: with disp=1 returning NOT_FOUND
cleanly, the first cache probe always misses and the rest of the
boot path follows the same trajectory whether the cache is empty
or warm.
## Gate 3 — XENIA_CACHE_WIPE=1 opt-out still works
`XENIA_CACHE_WIPE=1 xrs-c11p1 check -n 50000000 --stable-digest`:
`ad4f74ee324fdedb0bfdd4cc4c6468e9` — identical. The AUDIT-038 wipe
mode is preserved; under the new fix it produces the same digest
as the default-persistent mode (because the boot path is identical
when the cache is empty in either case).
## Gate 4 — Phase B `image_loaded_sha256` unchanged
`xrs-c11p1 exec --phase-b-snapshot-dir <dir> --phase-b-snapshot-and-exit`:
```
phase-b-snap/ours/config.json:
"image_loaded_sha256": "ea8d160e9369328a5b922258a92113efb8d7ce3e1a5c12cc521e375985c91c18"
```
PASS — matches the prior Phase B baseline (`ea8d160e…`). The fix
does not perturb the XEX image or its load layout.
## Gate 5 — Cargo tests
`cargo test -p xenia-kernel --release`: **172 passed; 0 failed**.
Baseline pre-C+11.1 was 170 tests. Net +2 tests:
* `cache_open_directory_on_missing_path_returns_not_found` — pins
the cold-boot disp=1 + opts=0x7 behavior across `access`, `ignore`,
`recent`. Asserts NOT_FOUND status, handle=0, and no host
side-effect.
* `cache_disp5_after_disp1_miss_creates_file` — pins the
two-call sequence canary actually executes (disp=1 NOT_FOUND →
disp=5 FILE created).
## Gate 6 — Phase A cold-vs-cold matched-prefix table
See [cold-vs-cold-baseline.md](cold-vs-cold-baseline.md). Headline
main matched-prefix = **102404** (cold-vs-cold canonical; replaces
the deprecated `+1521 to 103925` warm-asymmetric value).
No regression on any of the 6 sister chains:
| canary_tid | ours_tid | matched | first_divergence | regressed? |
|---|---|---|---|---|
| 6 | 1 | 102404 | 102404 | no (= prior cold value) |
| 4 | 11 | 9 | — | no |
| 7 | 2 | 29 | — | no |
| 12 | 7 | 2 | 2 | no (pre-existing, unrelated) |
| 14 | 9 | 39 | 39 | no (pre-existing, unrelated) |
| 15 | 10 | 15 | — | no |
## Gate 7 — Cache layout matches canary structurally
Direct `stat` comparison:
```
~/.local/share/xenia-rs/cache/ ~/.local/share/Xenia/cache/
69d8e45c/ (dir) 69d8e45c/ (dir)
aab216c3/ (dir) aab216c3/ (dir)
d4ea4615/ (dir) d4ea4615/ (dir)
87719002/ (dir, canary-only — not
reached in 50M ours boot)
access (file, 72 B) access (file, 240 B)
recent (file, 48 B) recent (file, 48 B → 160 B over
many warm boots)
```
The `access`/`recent` dir-vs-file bug is **resolved**. ours's file
sizes are smaller because they reflect one boot's worth of manifest
records; canary's larger sizes reflect many warm boots. Structural
layout (file vs directory, no spurious `ignore/`) matches.
## Gate 8 — Build clean
`cargo build --release` from `xenia-rs/` succeeds with no errors.
One unrelated `dead_code` warning on `walk_committed_regions` in
`phase_b_snapshot.rs` is pre-existing and unaffected.
## Snapshots
* `canary-cache-pre-wipe.tar.gz` — canary's 23-file oracle, preserved
for any future warm-cache experiments.
* `canary-cache-post-cold.tar.gz` — identical to pre-wipe (canary
did not populate its cache in the 120 s cold-run window).
* `ours-cache-post-cold.tar.gz` — ours's post-cold cache state
showing the file-not-dir layout the fix produces.
## Summary
ALL 8 gates PASS. The fix is landable.