Files
xenia-rs/crates/xenia-app/tests/sylpheed_oracles.rs
MechaCat02 7e9ee1ac33 intro-video RE: deep demux-core instrumentation + handoff hygiene
Investigation probes for the ADV.wmv intro-video deadlock (feeder's demux
source-read 0x825211a0 returns no-data 0x80500000 where canary returns 0).

- xenia-kernel/state.rs: extend AUDIT-DEREF (fire_audit_pc_probe_if_match) to
  deep-dump the demux-state object (to +0xfc), its buffer descriptor [sub+4],
  the byte-source sub-objects ([sub+0]/[+0x30]/[+0x94] and [src0+0x2c]), and
  to WALK the windowed-buffer cached-block linked list (AUDIT-BLK: per-block
  64-bit start/size/dataptr) exactly as mapper 0x82522118 does. Read-only,
  env-gated (XENIA_AUDIT_DEREF); lockstep digest unaffected.
- xenia-app/main.rs: XENIA_DUMP_SLOTS observe-only scheduler runqueue dump.
- xenia-app/tests/sylpheed_oracles.rs: resolve ISO via SYLPHEED_ISO, then the
  repo-root sylpheed.iso symlink, then default; fix path typo.
- .gitignore: exclude .claude/ (71k files / 66GB agent worktrees) and the
  local investigation artifacts (audit-runs/, exit-thread-state.json, zq_*.py).

Findings (full chain in handoff notes): all ASF header metadata parses
correctly (packet size 16415, count 4728, 2 streams); cursors correct
(cur=5868, limit=77.6M); the windowed buffer works and slides (512B blocks);
no-data originates deep in the byte-read/parse chain, not from an empty buffer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 07:54:15 +02:00

111 lines
4.7 KiB
Rust

//! Sylpheed boot-sequence regression oracles.
//!
//! These goldens trigger `xenia-rs check` against the Project Sylpheed ISO and
//! compare the resulting digest to a checked-in JSON file via `--stable-digest`,
//! which excludes timing-sensitive counters (`packets`, `interrupts_*`,
//! `resolves`, `texture_decodes`). The remaining fields are deterministic in
//! lockstep at a fixed instruction budget — verified empirically across 3
//! consecutive runs.
//!
//! Goldens are CIRCULAR per ORACBUG-001/002/003: they were captured by running
//! the same code they validate. Treat them as **regression anchors** (catch
//! drift from a known-good snapshot) not **correctness anchors** (no claim
//! about absolute behavior). When a planned fix intentionally moves the
//! digest (e.g. swap fix → `swaps` increments; renderer fix → `draws` becomes
//! non-zero), re-baseline the golden as a separate commit.
//!
//! Tests are `#[ignore]`-gated because the runs take ~4 seconds each, which
//! is unacceptable for the default `cargo test` cycle. Run explicitly:
//! cargo test --release -p xenia-app --test sylpheed_oracles -- --ignored --nocapture
//!
//! ISO path is read from the `SYLPHEED_ISO` env var, falling back to the
//! repo-relative default. CI/contributors without the ISO will see the test
//! skip gracefully.
use std::process::Command;
const ISO_DEFAULT: &str = "/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso";
/// Resolve the Sylpheed ISO, in priority order:
/// 1. `SYLPHEED_ISO` env var (explicit override),
/// 2. the repo-root `sylpheed.iso` symlink (the documented per-machine
/// setup — see HANDOFF §0b), so a standard checkout runs without env vars,
/// 3. a last-resort absolute default.
/// Returns the first existing path, else the default string so the caller's
/// existence check still produces the "SKIPPING" message.
fn iso_path() -> String {
if let Ok(p) = std::env::var("SYLPHEED_ISO") {
return p;
}
// Repo root is two levels up from this crate's manifest dir.
let symlink = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../sylpheed.iso");
if symlink.exists() {
return symlink.to_string_lossy().into_owned();
}
ISO_DEFAULT.to_string()
}
fn run_oracle(label: &str, max_instr: u64, golden_rel: &str) {
let bin = env!("CARGO_BIN_EXE_xenia-rs");
let iso = iso_path();
if !std::path::Path::new(&iso).exists() {
eprintln!("{label}: iso not found at {iso}; set SYLPHEED_ISO to override. SKIPPING.");
return;
}
// Resolve the golden path relative to the test's CARGO_MANIFEST_DIR so the
// test runs correctly from any cwd.
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let golden = std::path::Path::new(manifest_dir).join(golden_rel);
assert!(
golden.exists(),
"{label}: golden file missing at {}",
golden.display()
);
let max_instr_str = max_instr.to_string();
let golden_str = golden.to_string_lossy().to_string();
let out = Command::new(bin)
.args([
"check",
&iso,
"-n",
&max_instr_str,
// Pin the inline (single-threaded) GPU backend. The default
// threaded backend drains the ring on a separate host thread,
// so the exact instruction at which a CP interrupt is queued —
// and therefore when the guest's swap-complete ISR callback runs
// (iterate-2S armed it via SCRATCH_REG writeback) — varies run to
// run. Inline draining is instruction-count-deterministic, which
// is what a regression golden needs. (The threaded path is the
// documented "GPU thread race" the stable-digest already warns
// about.)
"--gpu-inline",
"--stable-digest",
"--expect",
&golden_str,
])
.output()
.expect("failed to spawn xenia-rs");
if !out.status.success() {
eprintln!(
"{label}: STDOUT:\n{}\nSTDERR:\n{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr),
);
panic!("{label}: digest mismatch (exit {:?})", out.status.code());
}
}
/// Sylpheed boot to first VdSwap pair, captured at -n 50M lockstep.
/// Catches regressions in: addi/addic semantics, kernel HLE for VdSwap path,
/// thread spawning, file I/O for sound/config. With Phase A's swap fix landed,
/// `swaps` should be 2 and `draws` 0 (Phase E gates draws>0).
#[test]
#[ignore = "long-running; run via `cargo test ... -- --ignored sylpheed_n50m`"]
fn sylpheed_n50m() {
run_oracle("sylpheed_n50m", 50_000_000, "tests/golden/sylpheed_n50m.json");
}