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,134 @@
# Phase C+7 — broad-impact verification
The user explicitly asked for thorough side-effect analysis beyond
standard gates. This document covers (1) new-divergence enumeration,
(2) KeSetEvent call-site sampling, (3) wake-cascade check,
(4) spawn/exit pattern check, (5) determinism stability over a
longer horizon.
## 1. New-divergence enumeration
Comparing pre-fix (`audit-runs/phase-c6half-xam-audit/diff-report.md`)
to post-fix (`audit-runs/phase-c7-keSetEvent/diff-report.md`):
| chain | pre-fix first-divergence | post-fix first-divergence | category |
|---|---|---|---|
| tid=6→1 (main) | idx 102158 `XamTaskCloseHandle ret 1/0` | idx 102158 `XamTaskCloseHandle ret 1/0` | **persisted** (unrelated to KeSetEvent fix) |
| tid=4→11 | idx 5 `KeSetEvent ret 1/0` | none (full match in 9-event ours window) | **resolved** |
| tid=7→2 | idx 26 `KeSetEvent ret 1/0` | none (full match in 29-event canary window) | **resolved** |
| tid=12→7 | idx 2 `KeWaitForSingleObject ret 258/0` | idx 2 `KeWaitForSingleObject ret 258/0` | **persisted** (different bug) |
| tid=14→9 | idx 39 `XAudioGetVoiceCategoryVolumeChangeMask vs RtlEnterCS` | idx 39 same | **persisted** (different bug) |
| tid=15→10 | none | none | unchanged |
* **Resolved: 2** (both sister chains where KeSetEvent was the first
divergence)
* **Advanced: 0**
* **Persisted: 3** (XamTaskCloseHandle, KeWaitForSingleObject=258,
XAudio call-name divergence — all on different functions, none
related to KeSetEvent)
* **NEW: 0** — no new divergence surfaced. The fix neither
unblocked a new code path that then re-diverged nor introduced any
regression.
This is the clean-fix outcome (per task description language: "NEW
divergences are EXPECTED for a widely-used fix"). The clean-zero
outcome here is itself a positive finding — within the current 50M
horizon, the boot path was not hiding any downstream divergence
behind the wrong KeSetEvent return.
Per-tid event totals are byte-identical pre/post fix
(`(0,1),(1,108486),(2,30),(3,36),(4,2022),(5,9945),(6,315),(7,3),
(8,36),(9,75),(10,15),(11,9),(12,6),(13,426)`), confirming no
secondary boot-trajectory shift from the return-value change. Same
boot, same paths, same imports — the only delta is the value in
the `return_value` field on KeSetEvent / NtSetEvent emits.
## 2. KeSetEvent call-site sampling
Within the 50M Phase A window, ours emits **2** KeSetEvent
kernel.return events (one on each of tid=2 and tid=11). Canary emits
**7,495** KeSetEvent returns (spread across many threads that ours
doesn't reach in this window). Below: every call-site where
both engines have data, plus 3 canary-only samples to characterize
the unreached space:
| # | canary tid → ours tid | idx | canary ret | pre-fix ours ret | post-fix ours ret | match? |
|---|---|---|---|---|---|---|
| 1 | 4 → 11 | 5 | 1 | 0 | 1 | YES |
| 2 | 7 → 2 | 26 | 1 | 0 | 1 | YES |
| 3 | 4 → 11 | 20 | 1 | (ours stream ended at idx 9) | (same) | n/a — ours blocked upstream |
| 4 | 14 → 9 | 107 | 1 | (tid=9 diverges at idx 39 on XAudio) | (same) | n/a — ours blocked upstream |
| 5 | 14 → 9 | 215 | 1 | (same) | (same) | n/a — ours blocked upstream |
Both call-sites with comparable data are now in **bit-identical
return-value alignment with canary**. Sites 3-5 are downstream of
unrelated divergences; the KeSetEvent return on each (canary always
returns 1) will trivially match the moment our boot reaches them.
## 3. Wake-cascade check
Phase A's wake-cascade event kinds (`wait.end`, `handle.*`, etc.) are
not wired in ours's emitter at the time of writing (per MEMORY.md
Phase A index: "4 of 13 schema kinds wired"). Therefore we cannot
observe wake events directly. Indirect signal: per-tid event counts
are identical pre/post fix, suggesting no new threads progress past
prior parking points — i.e. the KeSetEvent return-value flip did
not visibly change wake-cascade behavior within 50M.
This is consistent with internal-state inspection: ours's
`ke_set_event` already mutated `signaled = true` and called
`wake_eligible_waiters` correctly pre-fix; only the return-value
emission was wrong. Wake semantics never depended on the return.
## 4. Spawn/exit pattern check
`thread.create` and `thread.exit` events are also not wired in ours's
emitter (same 4-of-13 reason). Phase A logs 0 thread.create / 0
thread.exit events in both pre and post fix. We cannot independently
verify thread count from Phase A.
From the per-tid breakdown (tids present in the log), ours has the
same 14 distinct tids pre and post fix (0,1,2,3,4,5,6,7,8,9,10,11,
12,13) with identical event counts. No new tid spawned and no tid
disappeared.
## 5. Determinism stability over time
50M `--stable-digest`: 3× identical (`c6d89582…`). Matches C+6½
baseline byte-for-byte. Sample fields:
```
{
"instructions": 50000000,
"imports": 40470,
"unimpl": 0,
"draws": 0,
"swaps": 1,
...
}
```
200M `--stable-digest`: 2× identical (`8186841b…`). New baseline.
Field values at 200M: imports=40470 still (no new imports between
50M and 200M — boot still plateaus on the same wait), draws=0,
swaps=1. Same as 50M. The boot is still parked on the same upstream
gate (XamTaskCloseHandle / KeWaitForSingleObject in the main
thread); the KeSetEvent fix alone is not sufficient to unblock the
next phase.
## Conclusion
The fix is **clean-positive**: resolved exactly the 2 sister-chain
divergences it was scoped to (idx 5 / idx 26), preserved main chain
(no #23 redux), preserved all 6 unit tests, added 6 new tests, and
introduced zero new divergences. Per-tid event totals are
byte-identical pre/post fix — the fix is observation-only (changes
what the emitter reports, not what the kernel does). The return-value
flip from 0 to 1 propagates through Phase A's kernel.return payloads
and nothing else, exactly matching canary's behavior.
Next session's target: main-chain divergence at idx 102158
(XamTaskCloseHandle), per C+6½ XAM-audit memory note. tid=4→11 and
tid=7→2 fully aligned; if those chains develop new divergences past
their current canary-stream ends, that's a future-boot horizon
problem, not this session's.

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 | 47573 | 9 | — |
| 6 | 1 | 102158 | 329948 | 108486 | 102158 |
| 7 | 2 | 29 | 29 | 30 | — |
| 12 | 7 | 2 | 6689 | 3 | 2 |
| 14 | 9 | 39 | 1371603 | 75 | 39 |
| 15 | 10 | 15 | 863209 | 15 | — |
## canary_tid=4 → ours_tid=11
No divergence within the 9 compared events (canary has 47573, ours has 9).
## canary_tid=6 → ours_tid=1
First divergence at `tid_event_idx=102158`: payload.return_value: canary=1 ours=0
**Pre-context (last 5 matching events):**
```
canary: [102153] import.call XamTaskSchedule
ours: [102153] import.call XamTaskSchedule
canary: [102154] kernel.call XamTaskSchedule
ours: [102154] kernel.call XamTaskSchedule
canary: [102155] kernel.return XamTaskSchedule
ours: [102155] kernel.return XamTaskSchedule
canary: [102156] import.call XamTaskCloseHandle
ours: [102156] import.call XamTaskCloseHandle
canary: [102157] kernel.call XamTaskCloseHandle
ours: [102157] kernel.call XamTaskCloseHandle
```
**Divergent event:**
```
canary: [102158] kernel.return XamTaskCloseHandle
ours: [102158] kernel.return XamTaskCloseHandle
```
**Next event after the divergence (if any):**
```
canary: [102159] import.call KeWaitForSingleObject
ours: [102159] import.call KeWaitForSingleObject
```
**Raw events (JSON):**
```json
{"deterministic": true, "engine": "canary", "guest_cycle": 0, "host_ns": 727355400, "kind": "kernel.return", "payload": {"name": "XamTaskCloseHandle", "return_value": 1, "side_effects": [], "status": "0x00000001"}, "schema_version": 1, "tid": 6, "tid_event_idx": 102158}
{"deterministic": true, "engine": "ours", "guest_cycle": 5378571, "host_ns": 515719495, "kind": "kernel.return", "payload": {"name": "XamTaskCloseHandle", "return_value": 0, "side_effects": [], "status": "0x00000000"}, "schema_version": 1, "tid": 1, "tid_event_idx": 102158}
```
## 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": 904485700, "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": 543428219, "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": 1082563200, "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": 1698374579, "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 863209, ours has 15).

View File

@@ -0,0 +1,10 @@
{
"instructions": 50000000,
"imports": 40470,
"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": 50000000,
"imports": 40470,
"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": 200000000,
"imports": 40470,
"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": 200000000,
"imports": 40470,
"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": 50000000,
"imports": 40470,
"unimpl": 0,
"draws": 0,
"swaps": 1,
"unique_render_targets": 0,
"shader_blobs_live": 0,
"texture_cache_entries": 0
}

View File

@@ -0,0 +1,106 @@
# Phase C+7-only diff (synthesized from working tree minus cumulative C+5/C+6/C+6½/XAM changes)
#
# Full uncommitted diff is in `fix.diff` (1431 lines, includes all
# pre-existing Phase C work since HEAD).
#
# This file isolates the ~30 LOC body changes + ~160 LOC test additions
# that constitute Phase C+7.
--- a/crates/xenia-kernel/src/exports.rs (C+6½ baseline)
+++ b/crates/xenia-kernel/src/exports.rs (C+7)
# Body 1: ke_set_event — match canary's constant-1 return (xevent.cc:60-64)
-fn ke_set_event(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
- let h = ctx.gpr[3] as u32;
- ensure_dispatcher_object(state, mem, h);
- let previous = match state.objects.get_mut(&h) {
- Some(KernelObject::Event { signaled, .. }) => {
- let prev = *signaled;
- *signaled = true;
- prev as u32
- }
- _ => 0,
- };
- state.audit_signal(h, ctx.lr as u32, "KeSetEvent", previous as u64);
- wake_eligible_waiters(state, h);
- ctx.gpr[3] = previous as u64;
-}
+fn ke_set_event(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
+ let h = ctx.gpr[3] as u32;
+ ensure_dispatcher_object(state, mem, h);
+ // Canary parity (xevent.cc:60-64): `XEvent::Set` returns constant `1`
+ // on success, NOT the prior signaled state as the NT contract claims.
+ // We compute `previous` for internal bookkeeping (audit_signal,
+ // wake_eligible_waiters honor the prior-state read), but report
+ // `1` for success / `0` for "no dispatcher found" to match the
+ // canary Phase A oracle. See Phase C+7 investigation.md.
+ let (previous, found) = match state.objects.get_mut(&h) {
+ Some(KernelObject::Event { signaled, .. }) => {
+ let prev = *signaled;
+ *signaled = true;
+ (prev as u32, true)
+ }
+ _ => (0u32, false),
+ };
+ state.audit_signal(h, ctx.lr as u32, "KeSetEvent", previous as u64);
+ wake_eligible_waiters(state, h);
+ ctx.gpr[3] = if found { 1 } else { 0 };
+}
# Body 2: nt_set_event — out-pointer writes constant 1 not prior state
# (xboxkrnl_threading.cc:610-628 + xevent.cc:60-64 chain)
-fn nt_set_event(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
- let handle = ctx.gpr[3] as u32;
- let prev_ptr = ctx.gpr[4] as u32;
- let previous = match state.objects.get_mut(&handle) {
- Some(KernelObject::Event { signaled, .. }) => {
- let prev = *signaled;
- *signaled = true;
- prev as u32
- }
- _ => 0,
- };
- state.audit_signal(handle, ctx.lr as u32, "NtSetEvent", previous as u64);
- wake_eligible_waiters(state, handle);
- if prev_ptr != 0 {
- mem.write_u32(prev_ptr, previous);
- }
- ctx.gpr[3] = STATUS_SUCCESS;
-}
+fn nt_set_event(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
+ let handle = ctx.gpr[3] as u32;
+ let prev_ptr = ctx.gpr[4] as u32;
+ // Canary parity (xboxkrnl_threading.cc:610-628): the optional out-pointer
+ // is filled with `was_signalled` = `ev->Set()` = constant 1 (see
+ // xevent.cc:60-64), NOT the prior signaled state. r3 carries
+ // STATUS_SUCCESS. We retain `previous` for internal audit/wake plumbing.
+ let (previous, found) = match state.objects.get_mut(&handle) {
+ Some(KernelObject::Event { signaled, .. }) => {
+ let prev = *signaled;
+ *signaled = true;
+ (prev as u32, true)
+ }
+ _ => (0u32, false),
+ };
+ state.audit_signal(handle, ctx.lr as u32, "NtSetEvent", previous as u64);
+ wake_eligible_waiters(state, handle);
+ if prev_ptr != 0 && found {
+ mem.write_u32(prev_ptr, 1);
+ }
+ ctx.gpr[3] = STATUS_SUCCESS;
+}
# Test additions (in tests module): 6 new unit tests; see file lines 7179-7340
# - ke_set_event_returns_constant_one_on_unsignaled_auto_reset
# - ke_set_event_returns_constant_one_on_already_signaled_manual_reset
# - nt_set_event_null_prev_ptr_returns_status_success_no_write
# - nt_set_event_valid_prev_ptr_writes_constant_one_and_returns_success
# - nt_set_event_on_signaled_event_writes_one
# - ke_set_event_post_fix_still_wakes_waiter
# LOC summary (Phase C+7 only, additive on C+6½ baseline):
# body changes: ~14 net added lines across 2 functions
# test additions: ~160 lines (6 tests)
# total: ~174 LOC, 1 file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,116 @@
# Phase C+7 KeSetEvent — Phase 1 investigation
## Headline (surprise)
The task description (and the C+6½ memory note) framed this as
"canary returns prior signaled state; ours returns STATUS_SUCCESS".
**That framing is incorrect** for canary. Canary's `XEvent::Set`
unconditionally `return 1;` — see
`xenia-canary/src/xenia/kernel/xevent.cc:60-64`:
```cpp
int32_t XEvent::Set(uint32_t priority_increment, bool wait) {
set_priority_increment(priority_increment);
event_->Set();
return 1;
}
```
Canary's `xeKeSetEvent` returns the result of `ev->Set(...)` — i.e.
constant **1**. The Phase A trampoline (`shim_utils.h:620`) emits
`ppc_context->r[3]` post-Store, so the kernel.return event carries
`return_value = 1` for every successful KeSetEvent call. That matches
the canary side of every observed divergence (idx 5 on tid=4, idx 26
on tid=7, …).
Ours's `ke_set_event` (`exports.rs:4136-4152`) ALREADY computes prior
signaled state and returns it via `ctx.gpr[3] = previous as u64`. On
a freshly-initialized event whose `signaled = false`, this writes 0
into r3 — which is the observed ours-side value.
**True semantic gap**: canary's KeSetEvent return == constant 1 on
success; ours's KeSetEvent return == prior signaled state (0 or 1).
Canary is technically wrong by NT semantics, but it is the reference
oracle, so ours must match.
NtSetEvent has a DIFFERENT shape — canary returns NTSTATUS (always
STATUS_SUCCESS on success) AND writes `was_signalled` (return of
`ev->Set()` = constant 1) to the optional out-pointer
(`xboxkrnl_threading.cc:610-628`). Ours's `nt_set_event`
(`exports.rs:4168-4185`) already returns STATUS_SUCCESS and writes
`previous as u32` to the out-pointer. For canary parity, ours must
write **1** (not prior) to the out-pointer.
## Source evidence
### Canary
* `xenia-canary/src/xenia/kernel/xevent.cc:60-64``XEvent::Set`
returns constant `1`.
* `xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc:537-551`
`xeKeSetEvent` returns `ev->Set(increment, !!wait)` =
pass-through of the constant.
* `xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc:610-633`
`xeNtSetEvent`/`NtSetEvent_entry` returns `X_STATUS_SUCCESS`,
writes `was_signalled` (= `ev->Set` return = 1) to
`previous_state_ptr` if non-null.
* `xenia-canary/src/xenia/kernel/util/shim_utils.h:615-624` — Phase A
trampoline emits `kernel.return` with `return_value = r[3]` after
`result.Store(ppc_context)`.
### Ours
* `xenia-rs/crates/xenia-kernel/src/exports.rs:4136-4152``ke_set_event`
writes `previous as u64` (prior signaled state) to `ctx.gpr[3]`.
* `xenia-rs/crates/xenia-kernel/src/exports.rs:4168-4185``nt_set_event`
writes `previous as u32` (prior signaled state) to the optional out
pointer; returns `STATUS_SUCCESS` in r3.
## Event records (from `audit-runs/phase-c6half-xam-audit/diff-report.md`)
### tid=4 → tid=11 idx=5 (chain 5/47573/9)
```
canary: kernel.return KeSetEvent return_value=1
ours: kernel.return KeSetEvent return_value=0
```
### tid=7 → tid=2 idx=26 (chain 26/29/30 — close to full alignment)
```
canary: kernel.return KeSetEvent return_value=1
ours: kernel.return KeSetEvent return_value=0
```
### tid=12 → tid=7 idx=2 is a DIFFERENT bug
`KeWaitForSingleObject return_value=258 vs 0` — that's a wait-timeout
return (`X_STATUS_TIMEOUT = 0x102`), not the KeSetEvent bug. Out of
scope for this session.
## Fix shape
Two body changes, both in `crates/xenia-kernel/src/exports.rs`:
1. `ke_set_event`: compute `previous` for internal bookkeeping
(audit_signal, wake_eligible_waiters), but write **constant 1**
(or specifically: 1 if the event was found, 0 if handle invalid)
to `ctx.gpr[3]`. Match canary's hardcoded `XEvent::Set` return.
2. `nt_set_event`: continue writing STATUS_SUCCESS to r3. Change the
out-pointer write from `previous as u32` to **`1u32`** when the
handle is valid — match canary's `was_signalled = ev->Set()` =
constant 1.
Tests will pin both behaviors. Existing tests assert that the shadow
signaled flag flips to true — those still pass. New/updated tests
verify return values.
## Note on NT semantics
Real NT `KeSetEvent` does return prior signaled state. Canary's
choice to hardcode 1 is technically a bug — but documented and stable
behavior the game has been built/run against. Once we land Phase C+7
the kernel-export emitter parity is restored; if real NT semantics
ever become required (e.g. real-hw oracle is added), this becomes a
revisit point. For now: parity > correctness.

View File

@@ -0,0 +1,91 @@
# Phase C+7 — re-validation
## Gate table
| gate | result |
|---|---|
| 1. cvar-OFF determinism (3 runs, 50M, `--stable-digest`) | **PASS** all 3 = `c6d895829b4611964978990ae1cb8a6a` (UNCHANGED from C+6½) |
| 1b. cvar-OFF determinism (2 runs, 200M, `--stable-digest`) | **PASS** both = `8186841b38737f3cd1b3d2e91a35d108` (new 200M baseline) |
| 2. Phase B `image_loaded_sha256` | **PASS** `ea8d160e9369328a5b922258a92113efb8d7ce3e1a5c12cc521e375985c91c18` matches baseline |
| 3. Phase A main matched prefix ≥ 102158 | **PASS** 102158 (preserved — no main-chain regression, no #23 redux) |
| 3b. Phase A sister chain tid=4→11 advances | **PASS** 5 → **9 (full alignment, no divergence in ours's 9-event window)** |
| 3c. Phase A sister chain tid=7→2 advances | **PASS** 26 → **29 (full alignment, no divergence in canary's 29-event window)** |
| 4. Both engines build clean | **PASS** ours: `cargo build --release` ok (pre-existing `walk_committed_regions` dead-code warning) |
| 5. Phase A emitter determinism (det fields only) | **PASS** 2 runs `90fb28202b70cb43a63def7a2f8b470d` byte-identical |
| 6. Unit tests | **PASS** 146 → **152** (6 new, 0 modified, 0 regressed) |
## Gate 1 — 50M determinism
```
c6d895829b4611964978990ae1cb8a6a digest-cvaroff-1.json
c6d895829b4611964978990ae1cb8a6a digest-cvaroff-2.json
c6d895829b4611964978990ae1cb8a6a digest-cvaroff-3.json
```
Digest is **unchanged** from C+6½ baseline. This is expected:
`--stable-digest` reports `(instructions, imports, draws, swaps,
unique_render_targets, shader_blobs_live, texture_cache_entries)`,
none of which are sensitive to KeSetEvent return-value within the 50M
horizon. Imports stay at 40470 (the KeSetEvent change moves no new
import call; only the return value of existing calls flips from 0 to
1). Draws stay at 0, swaps stay at 1.
## Gate 1b — 200M stability
```
8186841b38737f3cd1b3d2e91a35d108 digest-cvaroff-200M-1.json
8186841b38737f3cd1b3d2e91a35d108 digest-cvaroff-200M-2.json
```
200M run is byte-deterministic across 2 runs. No 200M C+6½ baseline
to compare against (none was captured), but the new digest is itself
a regression marker for any future fix. Field values at 200M:
imports=40470, draws=0, swaps=1 — same plateau as 50M.
## Gate 3 — Phase A diff (all chains)
| chain | C+6½ XAM | C+7 | Δ |
|---|---|---|---|
| canary tid=6 → ours tid=1 (main) | 102158 | 102158 | 0 (preserved) |
| canary tid=4 → ours tid=11 | 5 | **9** | **+4 (all ours events match)** |
| canary tid=7 → ours tid=2 | 26 | **29** | **+3 (all canary events match)** |
| canary tid=12 → ours tid=7 | 2 | 2 | 0 (different bug — KeWaitForSingleObject=258 vs 0) |
| canary tid=14 → ours tid=9 | 39 | 39 | 0 (different bug — XAudio vs RtlEnterCS) |
| canary tid=15 → ours tid=10 | (no div) | (no div) | 0 |
Two sister chains advanced exactly as predicted by the C+6½ memory
note. Main chain (XamTaskCloseHandle at 102158) is **explicitly
unchanged** — the KeSetEvent fix doesn't touch the XamTaskHandle
path; that divergence remains the next session's target.
## Gate 5 — emitter determinism
Phase A emitter run twice over the same boot, with all
non-deterministic fields (`host_ns`, `guest_cycle`, `engine`,
`deterministic`) stripped:
```
90fb28202b70cb43a63def7a2f8b470d ours.jsonl
90fb28202b70cb43a63def7a2f8b470d ours-determ.jsonl
```
Byte-identical det fields. New emitter-det baseline replaces
C+6½'s `11a07772…` — the return-value field is included in the
deterministic schema, and KeSetEvent's contribution flipped from 0
to 1 on every emit.
## Gate 6 — unit tests
`cargo test -p xenia-kernel --release`**152/152 pass**. New 6
tests:
* `ke_set_event_returns_constant_one_on_unsignaled_auto_reset`
* `ke_set_event_returns_constant_one_on_already_signaled_manual_reset`
* `nt_set_event_null_prev_ptr_returns_status_success_no_write`
* `nt_set_event_valid_prev_ptr_writes_constant_one_and_returns_success`
* `nt_set_event_on_signaled_event_writes_one`
* `ke_set_event_post_fix_still_wakes_waiter`
All existing 146 tests still pass — no regressions from the
return-value flip (other test bodies don't assert on the return
value of `ke_set_event` / `nt_set_event`).

View File

@@ -0,0 +1,25 @@
{
"build_id": "ours-phaseB",
"cvars": {
"phase_b_dump_section_content": false,
"phase_b_snapshot_and_exit": false,
"phase_b_snapshot_dir": "xenia-rs/audit-runs/phase-c7-keSetEvent/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": "3314b5a9beb44d499fcfaf2b7ffdf40f7dc8198595aa8058680c7ffa97ce7d89",
"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
}