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>
128 lines
4.5 KiB
Markdown
128 lines
4.5 KiB
Markdown
# Phase Host-Audio-Eager — Re-validation (2026-05-19)
|
||
|
||
## Progression metric (primary gate)
|
||
|
||
| metric | pre-fix baseline | post-fix | delta |
|
||
|-------:|:----------------:|:--------:|:-----:|
|
||
| **swaps** | 1 | **1** | **0** |
|
||
| **draws** | 0 | **0** | **0** |
|
||
|
||
**The progression metric did NOT move.** Despite landing the eager-seed
|
||
implementation cleanly with 3× reproducibility, neither swaps nor draws
|
||
advanced. This matches the prior agent's diagnosis: the audio worker
|
||
ordering issue is real, but the deeper root cause is voice-struct state
|
||
divergence — the audio callback at `0x824D6640` in ours blocks on
|
||
`KeWaitForMultipleObjects([0x82928B04, 0x82928AE0])` immediately
|
||
because the voice struct at `[r31+356]` reads `0x01` (ours) vs `0x00`
|
||
(canary). Pre-seeding 8 fires lets `try_inject_audio_callback` deliver
|
||
the first callback earlier, but the callback still blocks on the same
|
||
guest dispatchers — fires 2-8 sit in the queue because
|
||
`interrupts.is_in_callback()` stays true.
|
||
|
||
## 3× determinism
|
||
|
||
```
|
||
73e99d60029128b4d5c3dd98e540457d82a52b8a962e7495132be2be31411aca /tmp/digest_eager_1.json
|
||
73e99d60029128b4d5c3dd98e540457d82a52b8a962e7495132be2be31411aca /tmp/digest_eager_2.json
|
||
73e99d60029128b4d5c3dd98e540457d82a52b8a962e7495132be2be31411aca /tmp/digest_eager_3.json
|
||
```
|
||
|
||
All three cold runs produce byte-identical digest JSON. The
|
||
seed-at-register implementation is fully deterministic in lockstep
|
||
mode (the ticker accumulator gets pre-populated synchronously inside
|
||
the register handler, no host-thread non-determinism).
|
||
|
||
## Digest JSON
|
||
|
||
```json
|
||
{
|
||
"instructions": 50000007,
|
||
"imports": 40390,
|
||
"unimpl": 0,
|
||
"draws": 0,
|
||
"swaps": 1,
|
||
"unique_render_targets": 0,
|
||
"shader_blobs_live": 0,
|
||
"texture_cache_entries": 0
|
||
}
|
||
```
|
||
|
||
`imports`/`unimpl` unchanged from the C+22 baseline (40390/0).
|
||
|
||
## Phase B invariant
|
||
|
||
```
|
||
image_loaded_sha256 = ea8d160e9369328a5b922258a92113efb8d7ce3e1a5c12cc521e375985c91c18
|
||
```
|
||
|
||
UNCHANGED — Phase B is not affected by audio-runtime changes.
|
||
|
||
## Per-chain matched-prefix
|
||
|
||
100M-instruction cold trace vs canary baseline
|
||
(`xenia-rs/audit-runs/phase-d-stage1/canary-cvaroff-trunc.jsonl` —
|
||
pre Phase D D-extension absorber, so main reads at the C+18
|
||
102,424 value, NOT the post-D-extension 105,046).
|
||
|
||
| chain | pre-fix | post-fix | delta | first divergence |
|
||
|------:|--------:|---------:|------:|:-----------------|
|
||
| canary tid=4 → ours tid=11 | 11 | **11** | 0 | (preserved) |
|
||
| canary tid=6 → ours tid=1 | 102,424 | **102,424** | **0** | `NtQueryFullAttributesFile` (C+18-era) |
|
||
| canary tid=7 → ours tid=2 | 32 | **32** | 0 | (preserved) |
|
||
| canary tid=12 → ours tid=7 | 4 | **4** | 0 | C+23 idx=4 |
|
||
| canary tid=14 → ours tid=9 | 41 | **41** | 0 | (no advance — primary target) |
|
||
| canary tid=15 → ours tid=10 | 16 | **16** | 0 | (no advance — primary target) |
|
||
|
||
The two primary targets (tid=14→9 and tid=15→10) were the audio worker
|
||
guest threads spinning on the uninitialized voice struct. Their
|
||
matched-prefix did NOT advance.
|
||
|
||
## Kernel tests
|
||
|
||
- Pre: 217 passed
|
||
- Post: **221 passed** (+4 new `seed_fires_for_*` tests)
|
||
- Failures: 0
|
||
- All existing tests pass
|
||
|
||
## Build
|
||
|
||
`cargo build --release` clean. One pre-existing dead-code warning
|
||
unrelated to this fix.
|
||
|
||
## Total LOC
|
||
|
||
| file | added | removed |
|
||
|------|------:|--------:|
|
||
| `crates/xenia-kernel/src/xaudio.rs` | 86 | 0 |
|
||
| `crates/xenia-kernel/src/exports.rs` | 18 | 5 |
|
||
| **total** | **104** | **5** |
|
||
|
||
Net ~100 LOC, of which ~60 LOC are tests + doc comments. Engine logic
|
||
delta is ~25 LOC.
|
||
|
||
## Conclusion
|
||
|
||
The implementation lands cleanly:
|
||
- 3× cold-deterministic
|
||
- Phase B unchanged
|
||
- All tests pass
|
||
- Sister chains preserved
|
||
|
||
But the progression metric (swaps/draws) did NOT move. This is an
|
||
HONEST NEGATIVE RESULT: the eager-seed approach addresses the symptom
|
||
(ticker delays the first callback) but not the root cause (the
|
||
callback at 0x824D6640 still blocks on guest dispatchers that only
|
||
tid=9/10 can signal, and tid=9/10 are stuck on a voice-struct field
|
||
that the callback would need to clear — but doesn't, because canary's
|
||
callback takes a DIFFERENT control-flow path that doesn't reach the
|
||
KeWaitForMultipleObjects in the first place).
|
||
|
||
The deeper fix requires either:
|
||
- Identifying the guest write that initializes `[r31+356]` to 0 in
|
||
canary's boot path and ensuring ours produces the same write.
|
||
- A true host-side audio worker thread that can run the callback
|
||
in a host context (substantial threading-model rework).
|
||
|
||
Both are out of scope for this session per the brief's "Don't widen
|
||
scope" tripstone.
|