Xenon is a 64-bit PPC core (32-bit *pointer* ABI, but 64-bit registers and integer arithmetic). The interpreter was truncating every word-form integer ALU writeback to 32 bits and zero-extending, on a false "MSR.SF=0 / 32-bit ABI" premise. This silently corrupted any genuine 64-bit value flowing through word-form arithmetic. Confirmed load-bearing via runtime ours-vs-canary capture: Sylpheed's millisecond->LARGE_INTEGER timeout converter sub_824ACA88 does `clrldi; mulli r11,r11,-10000; std`. For a 16 ms wait the correct result is -160000 = 0xFFFFFFFF_FFFD8F00 (relative). canary stores exactly that; ours' truncating `mulli` stored 0x00000000_FFFD8F00 (positive) -> the i64 timeout read as a huge *absolute* deadline -> a ~26000x over-wait that froze the main frame loop. After the fix the timeout matches canary and the previously-frozen frame/worker loops run (parallel boot NtWaitForMultipleObjectsEx 94 -> 30428; KeWaitForSingleObject/critical-section loops resume). Fix mirrors canary's INT64 emitters (ppc_emit_alu.cc) op-by-op for the 17 data-losing word-form ops: addis, addic(.), subfic(.), mulli, add(c/e/ze/me)x, subf(c/e/ze/me)x, negx, mullwx. Only the result *writeback* widens to full 64 bit; the 32-bit carry (XER[CA]) and overflow (XER[OV]) computations and the CR0 i32 view are preserved byte-identical (the low 32 bits of the new result equal the old truncated result), so this is a strict no-op for clean 32-bit values and only restores the previously-zeroed upper bits for genuine 64-bit values. Genuinely-32-bit ops (rlwinm/slw/srw/cmpw, mulhw/divw whose upper bits are ISA-undefined) are left untouched. Updated 7 unit tests that asserted the truncation (they encoded the bug) to the canary-correct full-64-bit values. Re-baselined the sylpheed_n50m golden (imports 40454 -> 1790936: the unwedged frame/worker loops now cycle under the instruction-count timebase); sylpheed_n2m unchanged (pre-frame-loop). Lockstep determinism preserved (two 50M runs identical). Full suite 660/660. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sylpheed regression goldens
These JSON files anchor xenia-rs check digest output for Project Sylpheed.
Files
| File | -n | Mode | Captures |
|---|---|---|---|
sylpheed_n2m.json |
2_000_000 | full digest | early boot (swaps=0, no rendering) |
sylpheed_n50m.json |
50_000_000 | stable-digest | first VdSwap pair (swaps=2 post-Phase-A) |
Stable-digest mode
sylpheed_n50m.json is captured with --stable-digest, which omits
timing-sensitive counters: packets (±2–8% lockstep noise from a GPU thread
race), resolves, interrupts_delivered, interrupts_dropped,
texture_decodes. The remaining fields are byte-identical across repeated
lockstep runs at a fixed -n.
sylpheed_n2m.json predates the stable-digest flag and uses full-digest
compare. It still works because at -n 2M the GPU pipeline has not produced any
packets yet — packets=0 is trivially deterministic.
Circularity hazard
Per ORACBUG-001/002/003, these goldens were captured by running the same code
they validate. They detect regression from a known-good snapshot, not
correctness. When a planned fix intentionally moves the digest (e.g. a
shader fix landing draws > 0 for the first time), re-baseline the golden as
a separate commit and reference the audit ID in the message.
Re-baselining
cargo build --release -p xenia-app
target/release/xenia-rs check \
"$SYLPHEED_ISO" \
-n 50000000 \
--stable-digest \
--out crates/xenia-app/tests/golden/sylpheed_n50m.json
Running the goldens
cargo test --release -p xenia-app --test sylpheed_oracles -- --ignored --nocapture
The tests are #[ignore]-gated because each run takes a few seconds, which is
unacceptable in the default cargo test cycle. The ISO path defaults to the
contributor's local ~/RE Project Sylpheed/Project Sylpheed*.iso and can be
overridden via SYLPHEED_ISO=/path/to/sylpheed.iso.
n4b canonical-invocation regression anchor (deferred)
The audit's recommended next sprint also called for a sylpheed_n4b.json
golden capturing the canonical reference invocation
xenia-rs check sylpheed.iso -n 4_000_000_000 --parallel --reservations-table.
This is deferred because:
- The
--parallel --reservations-tablecombination is empirically pathologically slow at -n 100M (>32 min per run per the audit memory). At -n 4B the run cost is many hours, not the single-session-friendly 5–15 min the original plan estimated. - Each phase that intentionally moves rendering counters (C, D, E, F) would need a re-baseline of n4b — a significant time cost compounding over the sprint.
Once the renderer-unblock phases (C+D+E) land and draws > 0 is confirmed at
-n 100M lockstep, an n4b artifact may be captured one-shot and stored under
audit-runs/post-fix/ (not as a test golden) as a manual regression anchor for
the canonical invocation.