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,97 @@
# Phase C+23 — Cold-vs-cold verification
**Date:** 2026-05-26
**Mode:** Engine modified (`vd_query_video_flags` constant return).
Cold-vs-cold protocol: fresh `xrs-c23` cold runs vs archived canary
jitter set.
## Methodology
- ours-cold jsonls: `/tmp/ours-c23-vd-cold-{1,2,3}.jsonl` (28.7 MB
each, 108,507 events on tid=1). Captured under
`XENIA_CACHE_WIPE=1` with the freshly built `xrs-c23` binary
(release, post-fix).
- canary jitter set: 3 archived cold runs `canary-jitter-{1,2,3}.jsonl`
(4.4 / 3.5 / 3.7 GB) from
`xenia-canary/build-cross/bin/Windows/Debug/`. Same reference
jitter set used in C+20, C+21, C+22, Phase D Stage 0..4.
- tid map: `--tid-map 6=1` (canary main thread → ours main thread).
## Pre-C+23 baseline (post-C+22)
```
matched=105,138 across all 3 jitters
first divergence: kernel.return VdQueryVideoFlags
canary=3 ours=0
at idx 105,138 (tid=6→1)
```
## Post-C+23 (3-jitter table)
| jitter | matched | first divergence at | first-divergence kind / payload |
|---|---|---|---|
| 1 | **105,286** | 105,286 | `import.call VdGetCurrentDisplayGamma` (canary) vs `import.call KeAcquireSpinLockAtRaisedIrql` (ours) |
| 2 | **105,286** | 105,286 | (same) |
| 3 | **105,286** | 105,286 | (same) |
Delta vs baseline: **+148 events** in main matched-prefix on all
three jitters. The new first divergence (`VdGetCurrentDisplayGamma`
vs `KeAcquireSpinLockAtRaisedIrql`) is genuine and identical across
all three jitters.
## Absorber counters (sanity)
| jitter | floating_create (c/o) | floating_wait (c/o) |
|---|---|---|
| 1 | 0 / 0 | 1 / 0 |
| 2 | 0 / 0 | 0 / 0 |
| 3 | 1 / 0 | 3 / 0 |
Within the expected scheduling-jitter window. Matched-prefix
stable at 105,286 across all three.
## Sister chains
No sister chain regressions: the diff report lists only the main
`tid=6 → tid=1` chain (no sister chains exercised in the new
window). All previously matched sister chains
(11, 32, 4, 41, 16) remain unaffected because they fire outside
the 105,138105,286 window.
## Determinism (3 cold runs)
| run | md5 | matched-prefix vs jitter-1 | ours_total |
|---|---|---|---|
| cold-1 | `4e2e781ff0609f3a0a08f573dee4be4e` | 105,286 | 108,507 |
| cold-2 | `b195d82a1b61e87d6f54a2ac2b3e091b` | 105,286 | 108,507 |
| cold-3 | `e6b94d4dc151007c924b81bbc5c9faf5` | 105,286 | 108,507 |
The byte-level md5 differs (host_ns/guest_cycle wall-time jitter,
expected) but the logical semantic state — matched-prefix,
ours_total, first-divergence index, divergent payloads — is
bit-identical across all 3 runs.
New cold baseline digest (representative cold-1):
**`4e2e781ff0609f3a0a08f573dee4be4e`**.
## Phase B `image_canonical_sha256`
Pinned hash `ea8d160e…` UNCHANGED. No XEX loader changes; only
kernel export logic modified.
## Test suite
xenia-kernel: **226 PASS** (was 224 + 2 new).
## Conclusion
Phase C+23 advances main matched-prefix from 105,138 → 105,286
across all three canary jitter cold runs (+148 events,
+0.0311% of canary total). The next divergence is a real
post-VdSwap control-flow mismatch unrelated to video flags.
Engine MODIFIED (~50 LOC: 1 registration edit + 6 LOC function
body + 40 LOC tests). Diff tool UNCHANGED. Phase B
`image_loaded_sha256` ε class boundary UNCHANGED.
Tripstones 1, 2, 3, 4, 5, 6 honored.

View File

@@ -0,0 +1,50 @@
# 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 | floating_create (c/o) | floating_wait (c/o) |
|---|---|---|---|---|---|---|---|
| 6 | 1 | 105286 | 476943 | 108507 | 105286 | 0/0 | 1/0 |
*`floating_create (c/o)` counts shared-global `handle.create` events absorbed by Phase C+18 cross-tid SID matching. `floating_wait (c/o)` counts `wait.begin` events on shared-global dispatchers absorbed by Phase C+21 (scheduling-jitter window — canary's contention slow path may fire while ours fast-paths or vice versa). See schema-v1.md §"Shared-global SIDs" and §"Wait-begin floating absorb".*
## canary_tid=6 → ours_tid=1
First divergence at `tid_event_idx=105286`: payload.ord: canary=441 ours=77
**Pre-context (last 5 matching events):**
```
canary: [105288] kernel.call VdGetSystemCommandBuffer
ours: [105281] kernel.call VdGetSystemCommandBuffer
canary: [105289] kernel.return VdGetSystemCommandBuffer
ours: [105282] kernel.return VdGetSystemCommandBuffer
canary: [105290] import.call VdSwap
ours: [105283] import.call VdSwap
canary: [105291] kernel.call VdSwap
ours: [105284] kernel.call VdSwap
canary: [105292] kernel.return VdSwap
ours: [105285] kernel.return VdSwap
```
**Divergent event:**
```
canary: [105293] import.call VdGetCurrentDisplayGamma
ours: [105286] import.call KeAcquireSpinLockAtRaisedIrql
```
**Next event after the divergence (if any):**
```
canary: [105294] kernel.call VdGetCurrentDisplayGamma
ours: [105287] kernel.call KeAcquireSpinLockAtRaisedIrql
```
**Raw events (JSON):**
```json
{"deterministic": true, "engine": "canary", "guest_cycle": 0, "host_ns": 1650661700, "kind": "import.call", "payload": {"module": "xboxkrnl.exe", "name": "VdGetCurrentDisplayGamma", "ord": 441}, "schema_version": 1, "tid": 6, "tid_event_idx": 105293}
{"deterministic": true, "engine": "ours", "guest_cycle": 5584999, "host_ns": 1437632028, "kind": "import.call", "payload": {"module": "xboxkrnl.exe", "name": "KeAcquireSpinLockAtRaisedIrql", "ord": 77}, "schema_version": 1, "tid": 1, "tid_event_idx": 105286}
```

View File

@@ -0,0 +1,50 @@
# 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 | floating_create (c/o) | floating_wait (c/o) |
|---|---|---|---|---|---|---|---|
| 6 | 1 | 105286 | 441027 | 108507 | 105286 | 0/0 | 0/0 |
*`floating_create (c/o)` counts shared-global `handle.create` events absorbed by Phase C+18 cross-tid SID matching. `floating_wait (c/o)` counts `wait.begin` events on shared-global dispatchers absorbed by Phase C+21 (scheduling-jitter window — canary's contention slow path may fire while ours fast-paths or vice versa). See schema-v1.md §"Shared-global SIDs" and §"Wait-begin floating absorb".*
## canary_tid=6 → ours_tid=1
First divergence at `tid_event_idx=105286`: payload.ord: canary=441 ours=77
**Pre-context (last 5 matching events):**
```
canary: [105287] kernel.call VdGetSystemCommandBuffer
ours: [105281] kernel.call VdGetSystemCommandBuffer
canary: [105288] kernel.return VdGetSystemCommandBuffer
ours: [105282] kernel.return VdGetSystemCommandBuffer
canary: [105289] import.call VdSwap
ours: [105283] import.call VdSwap
canary: [105290] kernel.call VdSwap
ours: [105284] kernel.call VdSwap
canary: [105291] kernel.return VdSwap
ours: [105285] kernel.return VdSwap
```
**Divergent event:**
```
canary: [105292] import.call VdGetCurrentDisplayGamma
ours: [105286] import.call KeAcquireSpinLockAtRaisedIrql
```
**Next event after the divergence (if any):**
```
canary: [105293] kernel.call VdGetCurrentDisplayGamma
ours: [105287] kernel.call KeAcquireSpinLockAtRaisedIrql
```
**Raw events (JSON):**
```json
{"deterministic": true, "engine": "canary", "guest_cycle": 0, "host_ns": 1666583800, "kind": "import.call", "payload": {"module": "xboxkrnl.exe", "name": "VdGetCurrentDisplayGamma", "ord": 441}, "schema_version": 1, "tid": 6, "tid_event_idx": 105292}
{"deterministic": true, "engine": "ours", "guest_cycle": 5584999, "host_ns": 1437632028, "kind": "import.call", "payload": {"module": "xboxkrnl.exe", "name": "KeAcquireSpinLockAtRaisedIrql", "ord": 77}, "schema_version": 1, "tid": 1, "tid_event_idx": 105286}
```

View File

@@ -0,0 +1,50 @@
# 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 | floating_create (c/o) | floating_wait (c/o) |
|---|---|---|---|---|---|---|---|
| 6 | 1 | 105286 | 445578 | 108507 | 105286 | 1/0 | 3/0 |
*`floating_create (c/o)` counts shared-global `handle.create` events absorbed by Phase C+18 cross-tid SID matching. `floating_wait (c/o)` counts `wait.begin` events on shared-global dispatchers absorbed by Phase C+21 (scheduling-jitter window — canary's contention slow path may fire while ours fast-paths or vice versa). See schema-v1.md §"Shared-global SIDs" and §"Wait-begin floating absorb".*
## canary_tid=6 → ours_tid=1
First divergence at `tid_event_idx=105286`: payload.ord: canary=441 ours=77
**Pre-context (last 5 matching events):**
```
canary: [105291] kernel.call VdGetSystemCommandBuffer
ours: [105281] kernel.call VdGetSystemCommandBuffer
canary: [105292] kernel.return VdGetSystemCommandBuffer
ours: [105282] kernel.return VdGetSystemCommandBuffer
canary: [105293] import.call VdSwap
ours: [105283] import.call VdSwap
canary: [105294] kernel.call VdSwap
ours: [105284] kernel.call VdSwap
canary: [105295] kernel.return VdSwap
ours: [105285] kernel.return VdSwap
```
**Divergent event:**
```
canary: [105296] import.call VdGetCurrentDisplayGamma
ours: [105286] import.call KeAcquireSpinLockAtRaisedIrql
```
**Next event after the divergence (if any):**
```
canary: [105297] kernel.call VdGetCurrentDisplayGamma
ours: [105287] kernel.call KeAcquireSpinLockAtRaisedIrql
```
**Raw events (JSON):**
```json
{"deterministic": true, "engine": "canary", "guest_cycle": 0, "host_ns": 1651635600, "kind": "import.call", "payload": {"module": "xboxkrnl.exe", "name": "VdGetCurrentDisplayGamma", "ord": 441}, "schema_version": 1, "tid": 6, "tid_event_idx": 105296}
{"deterministic": true, "engine": "ours", "guest_cycle": 5584999, "host_ns": 1437632028, "kind": "import.call", "payload": {"module": "xboxkrnl.exe", "name": "KeAcquireSpinLockAtRaisedIrql", "ord": 77}, "schema_version": 1, "tid": 1, "tid_event_idx": 105286}
```

View File

@@ -0,0 +1,201 @@
# Phase C+23 — `VdQueryVideoFlags` constant return
**Date:** 2026-05-26
**Mode:** WRITE — engine change (~5 LOC functional + ~2 LOC registration
edit + ~40 LOC tests). Diff tool UNCHANGED.
**Status:** LANDED. Main matched-prefix 105,138 → 105,286 (+148).
## TL;DR
The post-C+22 first divergence at canary tid=6 ↔ ours tid=1 idx 105,138
is `kernel.return VdQueryVideoFlags`:
```
canary: kernel.return VdQueryVideoFlags { return_value: 3, status: "0x00000003" }
ours: kernel.return VdQueryVideoFlags { return_value: 0, status: "0x00000000" }
```
Canary's `VdQueryVideoFlags_entry`
(`xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc:231-241`)
computes a bitmask from the queried video mode:
```cpp
dword_result_t VdQueryVideoFlags_entry() {
X_VIDEO_MODE mode;
VdQueryVideoMode(&mode, false);
uint32_t flags = 0;
flags |= mode.is_widescreen ? 1 : 0;
flags |= mode.display_width >= 1280 ? 2 : 0;
flags |= mode.display_width >= 1920 ? 4 : 0;
return flags;
}
```
Under canary's shipping defaults (`cvars::widescreen=true` from
`xboxkrnl_video.cc:31`; `cvars::internal_display_resolution=8` from
`graphics_system.cc:26``{1280, 720}` from
`graphics_system.h:38-54`), the computed value is:
```
is_widescreen=1 → +1
display_width=1280 ≥ 1280 → +2
display_width=1280 ≥ 1920 → +0
= 3
```
Ours's previous registration mapped the export to `stub_return_zero`
(`exports.rs:215` pre-change), which placed `0` in `r3`. The fix is a
1:1 mirror of canary's semantics under the same defaults that
ours's `vd_query_video_mode` already reports (width=1280,
is_widescreen=1).
## Why a constant works (no infrastructure needed)
Ours's `vd_query_video_mode`
(`exports.rs:3986-3996` pre-change, now :3997-4007 in the new file)
hard-codes `display_width=1280, is_widescreen=1, refresh_rate=60`
— it has no cvar plumbing. As long as `vd_query_video_mode`'s
payload is itself fixed, the bitmask is also fixed. Implementing a
cvar-driven flags path would require first introducing a
`widescreen` / `internal_display_resolution` cvar machinery; out of
scope per the escalation rule.
A unit test (`vd_query_video_flags_matches_vd_query_video_mode_payload`)
ties the return value to the *actual* payload `vd_query_video_mode`
writes, so the two functions stay in sync if the mode payload is
ever updated to actual cvar-driven values.
## The fix
```rust
// exports.rs:215 (registration)
state.register_export(Xboxkrnl, 0x01C9, "VdQueryVideoFlags", vd_query_video_flags);
// exports.rs:3998-4023 (new function)
fn vd_query_video_flags(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
// is_widescreen=1, display_width=1280 → bits 0 + 1 = 3
ctx.gpr[3] = 0x3;
}
```
Total: 1 export-table line change + ~6 lines of function (with
doc comment) + ~40 lines of unit tests = ~50 LOC.
## Tests
2 new tests in `exports.rs`:
1. `vd_query_video_flags_returns_three` — sentinel-overwrite +
pinned return value `0x3`.
2. `vd_query_video_flags_matches_vd_query_video_mode_payload`
computes the canary bitmask formula over `vd_query_video_mode`'s
actual written payload and asserts equality with the
`vd_query_video_flags` return. Catches drift if either function
is updated without the other.
Total: previous 224 + 2 new = **226 tests, all PASS**.
## Cold-vs-cold verification (3-jitter table)
ours cold-1 jsonl: `/tmp/ours-c23-vd-cold-1.jsonl` (28.7 MB,
108,507 events on tid=1). Captured under `XENIA_CACHE_WIPE=1` with
the freshly built `xrs-c23` binary.
| jitter | matched | first divergence at | first-divergence kind / payload |
|---|---|---|---|
| 1 | **105,286** | 105,286 | `import.call VdGetCurrentDisplayGamma` (canary) vs `import.call KeAcquireSpinLockAtRaisedIrql` (ours) |
| 2 | **105,286** | 105,286 | (same) |
| 3 | **105,286** | 105,286 | (same) |
Delta vs C+22 baseline (105,138): **+148 events** in main matched-
prefix, on all three jitters. The new first divergence is genuine
and identical across all three jitters.
## Absorber counters (sanity)
| jitter | floating_create (c/o) | floating_wait (c/o) |
|---|---|---|
| 1 | 0 / 0 | 1 / 0 |
| 2 | 0 / 0 | 0 / 0 |
| 3 | 1 / 0 | 3 / 0 |
Jitter-to-jitter variance in absorber counts is the expected
scheduling-jitter window. Matched-prefix stable at 105,286 across
all three.
## Sister chains
No sister chains exercised in the 105,138105,286 window. The 148
absorbed events are all on the main `tid=6 → tid=1` chain. The diff
report lists only the main chain row, confirming no regressions on
any sister chain.
## Determinism (3 cold runs)
| run | md5 | matched-prefix vs jitter-1 |
|---|---|---|
| cold-1 | `4e2e781ff0609f3a0a08f573dee4be4e` | 105,286 |
| cold-2 | `b195d82a1b61e87d6f54a2ac2b3e091b` | 105,286 |
| cold-3 | `e6b94d4dc151007c924b81bbc5c9faf5` | 105,286 |
Byte-level digests differ across the 3 cold runs because of
`host_ns` / `guest_cycle` wall-time jitter (unchanged from
pre-C+23 behavior). Logical semantic state — matched-prefix,
ours_total=108,507, first-divergence index — is bit-stable across
all 3 runs.
## Files touched
- `xenia-rs/crates/xenia-kernel/src/exports.rs`:
- Line 215: registration `stub_return_zero``vd_query_video_flags`.
- Lines ~3998-4023: new `vd_query_video_flags` function with
full doc comment.
- Lines ~9750-9803: 2 new unit tests in the `tests` module.
NO Phase B loader changes. NO diff-tool changes. NO new cvars.
NO refactor of video state model.
## Phase B `image_canonical_sha256`
Pinned hash `ea8d160e…` UNCHANGED — only kernel-export logic
modified; XEX loader path untouched.
## Cascade
- A (verify canary return): **PASS** — canary returns 3 under
shipping defaults; verified by direct source read of
`xboxkrnl_video.cc:231-241` + `graphics_system.cc:26` +
`graphics_system.h:38-54`. Confidence HIGH.
- B (implement + tests): **PASS** — 2 new tests, 226 total PASS,
release build clean (1 pre-existing dead-code warning on
`walk_committed_regions` — unrelated).
- C (3-jitter verification): **PASS** — all three jitters advance
105,138 → 105,286 (+148), same downstream divergence.
- D (determinism + sister chains): **PASS** — 3 cold runs converge
to identical matched-prefix=105,286 against jitter-1. No sister
chain regressions.
- E (canary caches unchanged): **PASS** — archived jitter set used,
no fresh canary run made (per C+22 precedent), `cache/` and
`cache_host/` directories unchanged from session start.
## Next divergence (C+24 candidate)
`import.call VdGetCurrentDisplayGamma` at canary idx 105,293 vs
`import.call KeAcquireSpinLockAtRaisedIrql` at ours idx 105,286.
Both engines just exited a `VdSwap` (5 matching prior events
ending in `kernel.return VdSwap`). The two engines then take
different code paths inside the post-VdSwap return path.
Possible interpretations:
- Different control flow inside a Vd post-swap hook on the canary
side (canary calls `VdGetCurrentDisplayGamma` after `VdSwap`;
ours doesn't).
- Different scheduler interleaving: ours main thread re-enters a
spinlock-protected section that canary's post-VdSwap walk avoids.
Investigation should start by looking at the canary `VdSwap`
post-handler to see if canary unconditionally calls
`VdGetCurrentDisplayGamma` (and if so, whether ours stubs it out)
or if this is a game-code branch driven by guest memory state.
Out of scope for C+23.