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

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

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

7.2 KiB

Phase 2.0 — Suspect re-verification (Stage 2, 2026-05-14)

Stage 1 flagged 7 imports as "MATCH-but-suspect": classified MATCH at inventory time but with body shorter than canary, suggesting possible DIVERGENT-shallow status. This document records the verdict for each after byte-level scrutiny of both engines' source.

Methodology: read canary impl + ours impl side-by-side, ask "for the observable inputs (Phase A kernel.return.payload.* fields, guest-memory writes flowing to subsequent kernel.call.payload.* fields) is the output bit-equivalent?". Cite the exact axis when divergent.

Verdict summary

# Import Verdict In-scope this stage? Notes
A RtlFillMemoryUlong MATCH (false alarm) n/a See §A
B XMACreateContext DIVERGENT (signature) NO (T3 audio) See §B
C XamUserGetSigninState DIVERGENT (state coupling) NO (T3 XAM) See §C
D XamUserGetXUID DIVERGENT (signature + state) NO (T3 XAM) See §D
E MmGetPhysicalAddress MATCH-by-equivalence (already reclassified in Batch 0) n/a See §E
F KeQuerySystemTime DIVERGENT (fake constant vs live clock) DEFER (needs deterministic guest-tick clock; not LOW) See §F
G KeSetAffinityThread DIVERGENT (return mechanism) YES (Batch 3) See §G

Net: 3 false alarms (A, E, by analysis; plus implicit re-confirmation of others as DIVERGENT). 4 actual DIVERGENT cases (B/C/D Tier-3 defer; F defer pending clock infra; G in-scope).

§A RtlFillMemoryUlong — MATCH (false alarm)

Canary (xboxkrnl_rtl.cc:72-83):

uint32_t swapped_pattern = xe::byte_swap(pattern.value());
for (uint32_t n = 0; n < count; n++, p++) {
  *p = swapped_pattern;  // direct write to uint32_t* on x86 host → LE
}

Ours (exports.rs:2380-2389):

let pattern = ctx.gpr[5] as u32;
for i in 0..count {
    mem.write_u32(dest + i * 4, pattern);  // GuestMemory.write_u32 uses val.to_be_bytes()
}

Byte-level equivalence: canary writes byte_swap(P) as LE on x86, producing bytes [P_byte0, P_byte1, P_byte2, P_byte3] (where byte_n is the n-th byte of the original P in BE convention). Ours's write_u32 calls val.to_be_bytes() and copies, producing the same bytes [P_byte0, ...]. For pattern=0x11223344: both produce [0x11, 0x22, 0x33, 0x44] in memory.

Verdict: MATCH. The Stage 1 endianness suspicion was an artifact of comparing C++ source semantics (xe::byte_swap is loud) to Rust source semantics (to_be_bytes is buried in write_u32). The actual output bytes are bit-identical. No engine fix.

§B XMACreateContext — DIVERGENT (deferred Tier-3)

Canary (xboxkrnl_audio_xma.cc:57): writes allocated context VA to *context_out_ptr, returns X_STATUS_NO_MEMORY or _SUCCESS in r3 depending on alloc result.

Ours (exports.rs near 3338): allocates a handle, stores in ctx.gpr[3] as a u32 return. No OUT-ptr write.

Verdict: DIVERGENT (signature mismatch). Tier-3 audio subsystem. Defer to a future XMA/audio session.

§C XamUserGetSigninState — DIVERGENT (deferred Tier-3)

Canary (xam_user.cc:89-99): reads XamState, looks up user_profile[user_index]->signin_state(). Returns 0 for invalid index or unsigned user.

Ours (xam.rs near 332): returns 1 for user_index==0, else 0. Hardcoded.

Verdict: DIVERGENT (state coupling). Tier-3 XAM. Defer.

§D XamUserGetXUID — DIVERGENT (deferred Tier-3)

Canary (xam_user.cc:29-66): 3-param signature (user_index, type_mask, xuid_ptr); validates each input; reads profile XUID; writes OUT-ptr.

Ours (xam.rs near 309): 2-param (user_index, xuid_ptr); ignores type_mask; writes 0; always returns SUCCESS.

Verdict: DIVERGENT (signature + state). Tier-3 XAM. Defer.

§E MmGetPhysicalAddress — MATCH-by-equivalence

Canary (xboxkrnl_memory.cc:642-654): heap-lookup; returns mapped physical address or 0 on lookup failure.

Ours (exports.rs:705-708): masks input with 0x1FFF_FFFF. For any address allocated through ours's heap (in the 0x4xxxxxxx range), the result is identity (i.e. va & 0x1FFF_FFFF is the physical address). The lookup-failure return path is unreachable for game-allocated VAs.

Verdict: MATCH-by-equivalence. Stage 1 classified STUB; Batch 0 re-classified MATCH (this document confirms). No engine fix in Stage 2. A future Stage may surface a divergence on the lookup-failure branch (e.g. game arithmetic on an unallocated VA), at which point the C+2 deferred 3-physical-heap memory model would need attention.

§F KeQuerySystemTime — DIVERGENT (deferred — clock infra)

Canary (xboxkrnl_threading.cc:458-473): reads Clock::QueryGuestSystemTime() (a deterministic tick-based guest clock), writes the u64 FILETIME via OUT-ptr, also updates a KeTimestampBundle kernel-state struct. Void return.

Ours (exports.rs:495-502): writes a hardcoded fake constant 132_500_000_000_000_000 (≈ year 2021 FILETIME). No clock read, no timestamp bundle. Void return framing was fixed in Phase C+1.

Verdict: DIVERGENT in semantics. The fake constant has produced Phase A matched-prefix 102,032 because the game has not yet read this value into a control-flow decision. Replacing with a deterministic guest-tick clock requires new infrastructure:

  • A KernelState::guest_filetime() method that derives a u64 FILETIME from scheduler.current_tick() or similar.
  • A separate KeTimestampBundle guest-memory struct allocated at startup and updated on each call (mirrors canary's behavior).

Estimated 60-100 LOC additive plus reading-error #23 risk (the live clock value flows to the game's branch decisions). Out of scope for Stage 2 LOW sweep. Recorded in deferred.md.

§G KeSetAffinityThread — DIVERGENT (in-scope Batch 3)

Canary (xboxkrnl_threading.cc:323-346):

dword_result_t KeSetAffinityThread_entry(lpvoid_t thread_ptr,
                                          dword_t affinity,
                                          lpdword_t previous_affinity_ptr) {
  if (!affinity) return X_STATUS_INVALID_PARAMETER;
  auto thread = XObject::GetNativeObject<XThread>(kernel_state(), thread_ptr);
  if (!thread) return X_STATUS_INVALID_HANDLE;
  if (previous_affinity_ptr) {
    *previous_affinity_ptr = uint32_t(1) << thread->active_cpu();
  }
  thread->SetAffinity(affinity);
  return X_STATUS_SUCCESS;
}

Ours (exports.rs:465-474):

let handle = resolve_pseudo_handle(state, ctx.gpr[3] as u32);
let new_mask = (ctx.gpr[4] as u32) as u8;
let old = state.set_affinity(handle, new_mask, mem);
ctx.gpr[3] = old as u64;

Verdict: DIVERGENT (return mechanism). Canary writes prev to OUT-ptr (r5 = previous_affinity_ptr) and returns STATUS_SUCCESS (0) in r3. Ours stores prev in r3 and ignores r5. Game callers expecting status in r3 will treat ours's prev-affinity as a non-zero NTSTATUS error.

Stage 2 fix (Batch 3): write prev_affinity to *r5, return 0 in r3, preserve canary's invalid-parameter / invalid-handle early returns.

#23 risk: MED. Game's CRT may have been treating ours's r3 (prev affinity = small bitmask, 1..63 in practice) as a "success" code via 0-vs-nonzero check — i.e. always failing. Fixing to return SUCCESS=0 flips that branch direction. If matched-prefix regresses, revert.