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,146 @@
# Phase C+9 — XamContentCreateEnumerator investigation
## Phase A oracle (predecessor C+8 baseline)
`canary tid=6 → ours tid=1` main chain, `tid_event_idx = 102197`:
```
canary: kernel.return XamContentCreateEnumerator return_value=1317 status=0x00000525 side_effects=[]
ours: kernel.return XamContentCreateEnumerator return_value=0 status=0x00000000 side_effects=[]
```
`args` and `args_resolved` are empty per the current Phase A emitter
scope (kernel.call args are not yet wired for dword-only signatures).
## Framing verification (reading-error #28 — canary source supersedes
NT-doc semantics or value-name guesses)
**The plan's framing was wrong on the constant identity**:
`0x525 = 1317` is `X_ERROR_NO_SUCH_USER`, NOT `X_ERROR_NO_CONTENT`
(`xbox.h:113`):
```cpp
#define X_ERROR_NO_SUCH_USER X_RESULT_FROM_WIN32(0x00000525L)
```
`X_ERROR_NO_CONTENT` is not even defined in canary's xbox.h. Always
verify constants against canary source, never the plan's inline
annotations.
## Canary body (`xenia-canary/src/xenia/kernel/xam/xam_content.cc:129-220`)
```cpp
dword_result_t XamContentCreateEnumerator_entry(
dword_t user_index, dword_t device_id, dword_t content_type,
dword_t content_flags, dword_t items_per_enumerate,
lpdword_t buffer_size_ptr, lpdword_t handle_out) {
assert_not_null(handle_out);
auto device_info = device_id == 0 ? nullptr : GetDummyDeviceInfo(device_id);
if ((device_id && device_info == nullptr) || !handle_out) {
if (buffer_size_ptr) *buffer_size_ptr = 0;
return X_E_INVALIDARG; // 0x80070057
}
if (buffer_size_ptr) {
*buffer_size_ptr = sizeof(XCONTENT_DATA) * items_per_enumerate;
} // sizeof(XCONTENT_DATA) = 0x134
uint64_t xuid = 0;
if (user_index != XUserIndexNone /* 0xFE */) {
const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);
if (!user) return X_ERROR_NO_SUCH_USER; // 0x525 ← THIS BRANCH
xuid = user->xuid();
}
// ... enumerator init, ListContent over HDD/ODD, fill items ...
*handle_out = e->handle();
return X_ERROR_SUCCESS; // 0
}
```
Only ONE control-flow path returns `0x525`:
1. `user_index != 0xFE` (i.e. a specific signed-in user is being queried)
2. `XamState::GetUserProfile(user_index)` returns `nullptr` — i.e. no
profile is installed at that slot.
Under canary's default `--mute=true` config (no `--profile_slot_*`
flags), `ProfileManager` has zero profiles, so `GetProfile(N)` returns
`nullptr` for every `N`.
## Side-effects check (rule out classification C)
Canary's `kernel.return` payload has `side_effects=[]`. Combined with
the body shape: the `*handle_out` write and the `*buffer_size_ptr`
write happen **inside the function** but are not handle-creation
side effects (no Phase A `handle.create` emit because no `XEnumerator`
was minted). The empty `side_effects` confirms canary reached the
`X_ERROR_NO_SUCH_USER` early-return at line 153-155, **before** the
`new ContentEnumerator(...)` allocation at line 160-161. So:
* No handle-table mutation to mirror.
* The `*buffer_size_ptr` write happens BEFORE the user check (line
145-147), so it does fire on the 0x525 path. Ours must mirror.
## Classification
**Class A — Unconditional (under ours's state model).**
Ours has no profile manager; all `user_index < 4` queries return
"no such user". This is effectively unconditional from Sylpheed's
perspective. Mirror canary's early-return.
**NOT class B** (state-dependent over real disk content) — we don't
reach the `ListContent()` enumeration path, so we don't need a
filesystem scan. No escalation.
**NOT class C** (side-effect mismatch) — both engines have empty
`side_effects` for this idx (above).
**NOT class D** (Phase A coverage asymmetry) — both engines emit the
same 3-event sequence (import.call, kernel.call, kernel.return).
## Ours pre-fix state
`crates/xenia-kernel/src/xam.rs:59`:
```rust
state.register_export(Xam, 0x025C, "XamContentCreateEnumerator", stub_success);
```
`stub_success` sets `r3 = 0`. Hence `return_value=0`, `status=0x00000000`,
no buffer_size_ptr write, no handle_out write.
## Fix shape
Replace the `stub_success` registration with a dedicated handler that:
1. Reads r3..r9 (user_index, device_id, content_type, content_flags,
items_per_enumerate, buffer_size_ptr, handle_out).
2. Returns `X_E_INVALIDARG` (0x80070057) when device_id is unknown
(>2; canary's GetDummyDeviceInfo accepts HDD=1, ODD=2) or
handle_out is NULL; writes `*buffer_size_ptr = 0` in that arm.
3. Otherwise writes `*buffer_size_ptr = 0x134 * items_per_enumerate`
(the sizeof(XCONTENT_DATA) computation).
4. If `user_index != 0xFE`: return `X_ERROR_NO_SUCH_USER` (0x525) —
ours has no profile manager.
5. Otherwise (user_index == 0xFE, XUserIndexNone): proceed to a
stubbed enumerator-success path — write `*handle_out = 0` and
return 0. Defensive; not exercised by current Phase A.
LOC: ~85 body + ~125 tests = ~210 additive in xam.rs only.
## Internal-consistency caveat (deferred)
Ours's `xam_user_get_signin_state` (xam.rs:380-384) returns 1 for
`user_index == 0`, claiming user 0 is signed in. This contradicts the
"no profile manager" model used by the C+9 fix. The conflict surfaces
at Phase A idx 107996 (canary returns 0 for `XamUserGetSigninState`)
and will be a separate fix in a future session. Single-fix discipline:
do not widen scope here.
## Internal-consistency caveat #2 (cosmetic)
The doc comment on `xam_user_get_signin_state` says "user0 signed in
locally" but canary returns 0 here. That's evidence of an older fix
that needs reconciliation. Out of scope for C+9.