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,21 @@
# Cold-vs-cold result — Phase C+12 (2026-05-14)
| canary_tid | ours_tid | matched | first_divergence | event-kind at divergence |
|---|---|---|---|---|
| 6 | 1 | **103862** | 103862 | `kernel.return NtCreateFile game:\dat\files.tbl`: canary=STATUS_OBJECT_NAME_NOT_FOUND / ours=STATUS_SUCCESS (synth-empty stub) |
| 4 | 11 | 9 | — | no divergence in 9 events |
| 7 | 2 | 29 | — | no divergence in 29 events |
| 12 | 7 | 2 | 2 | KeWaitForSingleObject — pre-existing, not regressed |
| 14 | 9 | 39 | 39 | XAudio init — pre-existing, not regressed |
| 15 | 10 | 15 | — | no divergence in 15 events |
Main matched-prefix advance: **+1458 events** (102,404 → 103,862).
New cold digest baseline (stable mode):
`ad4f74ee324fdedb0bfdd4cc4c6468e9` (UNCHANGED from C+11.1 — the
Phase C+12 fix is a pure kernel-export observation change that
doesn't shift any of the stable counters in the digest).
Phase B `image_loaded_sha256`:
`ea8d160e9369328a5b922258a92113efb8d7ce3e1a5c12cc521e375985c91c18`
(unchanged).

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 | 20000 | 9 | — |
| 6 | 1 | 103862 | 250000 | 108471 | 103862 |
| 7 | 2 | 29 | 29 | 30 | — |
| 12 | 7 | 2 | 10532 | 3 | 2 |
| 14 | 9 | 39 | 20000 | 75 | 39 |
| 15 | 10 | 15 | 20000 | 15 | — |
## canary_tid=4 → ours_tid=11
No divergence within the 9 compared events (canary has 20000, ours has 9).
## canary_tid=6 → ours_tid=1
First divergence at `tid_event_idx=103862`: payload.return_value: canary=18446744072635809844 ours=0
**Pre-context (last 5 matching events):**
```
canary: [103857] import.call RtlInitAnsiString
ours: [103857] import.call RtlInitAnsiString
canary: [103858] kernel.call RtlInitAnsiString
ours: [103858] kernel.call RtlInitAnsiString
canary: [103859] kernel.return RtlInitAnsiString
ours: [103859] kernel.return RtlInitAnsiString
canary: [103860] import.call NtCreateFile
ours: [103860] import.call NtCreateFile
canary: [103861] kernel.call NtCreateFile
ours: [103861] kernel.call NtCreateFile
```
**Divergent event:**
```
canary: [103862] kernel.return NtCreateFile
ours: [103862] kernel.return NtCreateFile
```
**Next event after the divergence (if any):**
```
canary: [103863] import.call RtlNtStatusToDosError
ours: [103863] import.call RtlEnterCriticalSection
```
**Raw events (JSON):**
```json
{"deterministic": true, "engine": "canary", "guest_cycle": 0, "host_ns": 805501600, "kind": "kernel.return", "payload": {"name": "NtCreateFile", "return_value": 18446744072635809844, "side_effects": [], "status": "0xc0000034"}, "schema_version": 1, "tid": 6, "tid_event_idx": 103862}
{"deterministic": true, "engine": "ours", "guest_cycle": 5470532, "host_ns": 463681670, "kind": "kernel.return", "payload": {"name": "NtCreateFile", "return_value": 0, "side_effects": [], "status": "0x00000000"}, "schema_version": 1, "tid": 1, "tid_event_idx": 103862}
```
## 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": 935048400, "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": 479443976, "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": 1106484200, "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": 1658972624, "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 20000, 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
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,179 @@
# Phase C+12 — investigation (canary's NtQueryFullAttributesFile)
## Canary's verified behavior
### Entry point
`NtQueryFullAttributesFile_entry` lives in
`xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc:474-513`. It
takes `(X_OBJECT_ATTRIBUTES*, X_FILE_NETWORK_OPEN_INFORMATION*)` and:
1. Translates the ANSI object name to a guest VFS path
(`util::TranslateAnsiPath`).
2. Calls `kernel_state()->file_system()->ResolvePath(target_path)`.
3. On hit, populates the OUT struct from `entry->{create,access,
write}_timestamp()` + `entry->allocation_size()` + `entry->size()`
+ `entry->attributes()`. Returns `X_STATUS_SUCCESS`.
4. On miss, returns `X_STATUS_NO_SUCH_FILE` (NOT
`OBJECT_NAME_NOT_FOUND`).
`xboxkrnl_io.cc:498-512`:
```cpp
auto entry = kernel_state()->file_system()->ResolvePath(target_path);
if (entry) {
file_info->creation_time = entry->create_timestamp();
file_info->last_access_time= entry->access_timestamp();
file_info->last_write_time = entry->write_timestamp();
file_info->change_time = entry->write_timestamp(); // <-- same as write
file_info->allocation_size = entry->allocation_size();
file_info->end_of_file = entry->size();
file_info->attributes = entry->attributes();
return X_STATUS_SUCCESS;
}
return X_STATUS_NO_SUCH_FILE;
```
### Resolution mechanism
`VirtualFileSystem::ResolvePath`
(`xenia-canary/src/xenia/vfs/virtual_file_system.cc:129-158`) walks
the in-memory `Entry` tree. It does NOT re-stat the host FS. The
tree is populated:
* **Eagerly at mount time** by
`HostPathDevice::Initialize` →
`HostPathDevice::PopulateEntry`
(`xenia-canary/src/xenia/vfs/devices/host_path_device.cc:31-75`)
which recursively walks the host directory and builds
`HostPathEntry` instances via `HostPathEntry::Create`.
* **Per-create** by `Entry::CreateEntry` /
`HostPathEntry::CreateEntryInternal`
(`xenia-canary/src/xenia/vfs/entry.cc:88` /
`host_path_entry.cc:78-98`), called from
`VirtualFileSystem::OpenFile` → `CreatePath`
(`virtual_file_system.cc:160-187`) on successful NtCreateFile create.
### Struct layout
`X_FILE_NETWORK_OPEN_INFORMATION` in
`xenia-canary/src/xenia/kernel/info/file.h:117-127`:
```cpp
struct X_FILE_NETWORK_OPEN_INFORMATION { // 56 bytes
be<uint64_t> creation_time;
be<uint64_t> last_access_time;
be<uint64_t> last_write_time;
be<uint64_t> change_time;
be<uint64_t> allocation_size;
be<uint64_t> end_of_file;
be<uint32_t> attributes;
be<uint32_t> pad;
};
```
All multibyte fields are `be<>`, i.e. byte-swapped on write to guest
memory (Xbox 360 is big-endian).
### Attribute / size derivation
`HostPathEntry::Create`
(`xenia-canary/src/xenia/vfs/devices/host_path_entry.cc:32-54`):
* `attributes_ = kFileAttributeDirectory (0x10)` if host is a dir.
* `attributes_ = kFileAttributeNormal (0x80)` for regular files.
`kFileAttributeReadOnly (0x01)` is OR'd in iff
`device->is_read_only()`. `cache:` mounts are read-WRITE
(`xenia_main.cc:641-651`), so the bit is NOT set.
* `size_ = file_info.total_size`.
* `allocation_size_ = round_up(total_size, device->bytes_per_sector())`.
The base `Device` ctor defaults `bytes_per_sector_ = 512`
(`xenia-canary/src/xenia/vfs/device.h`); host-path devices don't
override.
### Timestamp format
Windows FILETIME — 100-ns ticks since 1601-01-01 UTC. On the Windows
debug build, canary derives these via
`COMBINE_TIME(WIN32_FILE_ATTRIBUTE_DATA::ftCreationTime)`
(`filesystem_win.cc:226-228`), which is already FILETIME-format.
### Why ours diverged at idx 102404
ours's `nt_query_full_attributes_file` previously consulted
`std::fs::metadata` directly. On TRUE cold-vs-cold (both engines'
cache wiped), the host file `cache/d4ea4615/e/46ee8ca` doesn't
exist → ours returned `STATUS_NO_SUCH_FILE`. So why does canary
return SUCCESS?
The original Phase C+11.1 protocol wiped `~/.local/share/Xenia/cache`
but the Windows-debug canary running under wine actually stores its
cache under
`xenia-canary/build-cross/bin/Windows/Debug/cache/` (canary's
storage root resolves to `GetUserFolder()` when no `portable.txt`
is present, but the build-cross binary writes there anyway — verified
by `Storage root: Z:\home\fabi\RE - Project Sylpheed\xenia-canary\
build-cross\bin\Windows\Debug` in the canary stdout). So the
"cold-vs-cold" baseline at C+11.1 was actually
ours-cold-vs-canary-warm: canary's binary-dir cache survived the
wipe and the pre-existing `46ee8ca` file (May 12 timestamp) made
canary's mount-time scan populate the in-memory tree.
A TRUE cold-vs-cold (Phase C+12 protocol, see `re-validation.md`)
that ALSO wipes the binary-dir cache shows canary ALSO returns
NO_SUCH_FILE for `cache:\d4ea4615\e\46ee8ca` at idx 102404 — and
the divergence at 102404 dissolves.
But the underlying gap remains: canary maintains an in-memory entry
tree that ours did not, and any boot where canary has a populated
host cache (Sylpheed's normal warm/persistent operation) would see
canary succeed where ours misses. Fixing this is in scope per the
user directive.
## Architecture decision
Architecture **B** (in-memory entry tree mirroring canary):
* Add `cache_entries: HashMap<String, CacheEntryMeta>` to `KernelState`.
* Eager mount-time scan via `populate_cache_entries_from_host`
(mirrors canary's `HostPathDevice::PopulateEntry`).
* Per-create registration via `register_cache_entry` (mirrors canary's
`Entry::CreateEntry`).
* `nt_query_full_attributes_file` consults the tree first;
defensive host-FS fallback only fires when the tree missed but
the file is on disk (refreshes tree as a side effect).
Architecture A alone (just stat host FS) would technically work for
the specific 102404 case after the corrected cold-vs-cold protocol,
but would diverge from canary on any sequence where the in-memory
tree's freshness matters (e.g. an entry created earlier this same
boot whose host write hasn't flushed yet — rare but possible if a
future game pre-allocates entries before the corresponding write).
Architecture B keeps semantic parity with canary regardless of the
host-FS flush timing.
## Cold-vs-cold protocol correction
The C+11.1 protocol wiped `~/.local/share/Xenia/cache`. The Phase
C+12 protocol additionally wipes
`xenia-canary/build-cross/bin/Windows/Debug/cache/`
(plus `cache0/`, `cache1/`) because that's where the Windows-debug
canary actually writes its cache under wine. Backup is preserved at
`/tmp/canary-binary-cache-backup.tar.gz` (4.7 MB, 23 files) and a
copy lives in this audit-run dir as
`canary-binary-cache-pre-wipe.tar.gz`.
## File:line citations
| canary source | line(s) | what |
|---|---|---|
| `xboxkrnl_io.cc` | 474-513 | `NtQueryFullAttributesFile_entry` body |
| `xboxkrnl_io.cc` | 504 | `change_time = write_timestamp()` |
| `xboxkrnl_io.cc` | 512 | `return X_STATUS_NO_SUCH_FILE` |
| `info/file.h` | 117-127 | `X_FILE_NETWORK_OPEN_INFORMATION` layout |
| `vfs/entry.h` | 67-95 | `kFileAttribute*` consts + accessors |
| `vfs/entry.cc` | 88-104 | `Entry::CreateEntry` |
| `vfs/virtual_file_system.cc` | 129-158 | `ResolvePath` |
| `vfs/devices/host_path_device.cc` | 31-75 | `Initialize` + `PopulateEntry` |
| `vfs/devices/host_path_entry.cc` | 32-54 | `HostPathEntry::Create` (attrs + size) |
| `base/filesystem_win.cc` | 208-230 | `GetInfo` (FILETIME timestamps) |
| `app/xenia_main.cc` | 641-651 | `cache:` mount registration |

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": "/tmp/phase-b-c12"
},
"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": "f56c3d4f8e36876c5161c3a1d2196b1666e1a98ed0a16484a152dace7735e0a9",
"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,101 @@
# 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 "<ISO>"`
(~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 "<ISO>"`
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 <truncated> --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.