# Handoff — branch `iterate-4A/apu-xma-stage1` (Milestone 2: intro-video / XMA audio + RE tooling) Reverse-engineering Project Sylpheed under this Rust Xbox-360 emulator (`xenia-rs`), using Wine xenia-canary as the ground-truth oracle. This branch carries **Milestone 2** work plus major RE-tooling improvements, on top of the (uncommitted-until-now) Milestone-1 renderer history. > Method: first-divergence vs canary · fix causes not symptoms · NO faking/masking · measure the > oracle, never infer · refute before believing · ground every claim in evidence. --- ## 0. SET UP ON A NEW MACHINE (do this first) ### a) FFmpeg system libraries — **REQUIRED to build** (crate `xenia-apu` links them via pkg-config) The XMA audio decoder uses `ffmpeg-sys-next` (`crates/xenia-apu/Cargo.toml`: `ffmpeg-sys-next = { version = "6.1", default-features = false, features = ["avcodec"] }`), which links the **system** FFmpeg dev libraries. Install them: ```bash sudo apt update sudo apt install -y libavcodec-dev libavformat-dev libavutil-dev libswresample-dev pkg-config ffmpeg ``` Verify the toolchain (the XMA path needs the `xma1`/`xma2` decoders — present in distro FFmpeg ≥ ~2015): ```bash pkg-config --modversion libavcodec # expect 60.x (this branch built against 60.31) ffmpeg -hide_banner -decoders | grep -iE 'xma1|xma2' # expect: A....D xma1 / A....D xma2 ``` (Decoder note: distro FFmpeg has **no** `AV_CODEC_ID_XMAFRAMES`; we use `AV_CODEC_ID_XMA2` — see `crates/xenia-apu/src/xma2_codec.rs`.) On non-Debian distros install the equivalent `-dev` packages. ### b) The game ISO (gitignored — `*.iso`) Not in the repo. Place the Project Sylpheed ISO somewhere and create a `sylpheed.iso` symlink to it in the repo root (the run/test commands use `sylpheed.iso`): ```bash ln -s "/path/to/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso" sylpheed.iso ``` ⚠️ For **canary** runs, point at the REAL ISO path, not the symlink (Wine can't resolve the symlink). ### c) Build — **always cap parallelism** (a default `-j` build OOM-crashed a 15 GB box) ```bash export CARGO_BUILD_JOBS=4 # NEVER default -j12; check `free -h` first, drop to -j2 if <4GB free cargo build --release ``` ### d) Regenerate the static-analysis DB `sylpheed.db` (gitignored — `*.db`, ~586 MB, ~1h35m) Used by the RE/analysis queries (NOT needed to run the emulator). Rebuild from the ISO: ```bash cargo run --release -- dis "/path/to/" --db sylpheed.db # analysis passes run in <1s; the ~1h35m is DuckDB persisting ~1.8M dispatch rows. Be patient. ``` This branch's analyzer fix (see §3) makes the regenerated DB include the previously-missing XMV engine vtables (`0x8200a1e8`/`0x8200a908`). A local pre-fix backup may exist as `sylpheed.db.bak-pre-vtablefix` (gitignored, not pushed). --- ## 1. WHAT'S ON THIS BRANCH (all in this one commit, on top of `acb29db` = iterate-3AL) **Milestone-1 renderer history** (publisher/dev splash renders) is in the ancestry (iterate-2x → 3M → 3O → 3AL); pushing this branch carries it. **Milestone 2** + tooling added here: ### ✅ XMA AUDIO path — BUILT, WORKING, deterministic, tested - `crates/xenia-apu/src/xma.rs` — register-mapped XMA context system (MMIO `0x7FEA0000`, 320×64B context array, Kick/Lock/Clear decode). `xma_decode.rs` + `xma2_codec.rs` — the real FFmpeg `xma2` decoder (XMA_CONTEXT_DATA bitfields, BitStream packet parse, planar-f32→S16BE PCM). Decode runs synchronously on the CPU thread (deterministic, no host thread). Wired via `KernelState.xma` (`state.rs`), exports (`exports.rs`), `xaudio.rs` (`XAudioSubmitRenderDriverFrame` made faithful), `main.rs` (MMIO install + per-round pump). - **Audio-worker scheduler fix** (`main.rs` LR_HALT restore + `scheduler.rs`): the XAudio render callback worker was wrongly exited after ~2 deliveries → fixed → the guest now drives XMA decode. - Verified: real PCM out; golden `sylpheed_n50m` **re-baselined** (`crates/xenia-app/tests/golden/`) and PASSES; milestone-1 splash intact; apu/cpu/kernel tests pass. ### 🛠️ RE TOOLING (this branch's lasting wins) - **Runtime dispatch-recorder** `crates/xenia-cpu/src/dispatch_rec.rs` — records `(call-site → target, r3, lr)` for every indirect (`bcctr`-family) call. Off by default; enable with `XENIA_DISPATCH_REC=1`, optional filters `XENIA_DISPATCH_REC_TARGETS=` / `_SITES=`, dumps to `XENIA_DISPATCH_REC_OUT` (default `/tmp/dispatch_rec.txt`). Deterministic, observe-only. - **Repaired static analyzer** `crates/xenia-analysis/src/vtables.rs` — the vtable extractor silently **fragmented vtables with non-function head slots** (missed the XMV engine vtable entirely → blocked ~6 investigations). Fixed via **vptr-write-anchoring** (find `addis/addi → stw rX,0(rThis)` constant-vptr installs; read the fnptr run from each anchor). Result on rebuild: vtables 722→1150, dispatch candidates 688K→1.83M, engine fully typed. (Requires the §0d DB rebuild to take effect.) - **Probe Heisenbug FIXED** (`main.rs run_superblock`) — `--audit-pc-probe-hex` / `--mem-watch` used to **disable superblock chaining**, which changed thread scheduling and *starved the movie subsystem* so the probes couldn't observe it. Now probes fire *inside* the chain loop → scheduling is identical armed-vs-unarmed (verified byte-identical golden) → the probe suite is finally usable on the movie subsystem. Also fixed a `--quiet` bug that swallowed armed `--trace-handles`/`--dump-addr` reports. --- ## 2. CURRENT STATE & WHERE TO CONTINUE (the video still doesn't play) **Audio works; the intro VIDEO doesn't play yet.** Root, runtime-pinned: a 2000ms readiness timeout (`sub_821B66B8`) abandons because the XMV engine (`0x40d101c0`, runtime vtable `0x8200a1e8`) never **primes** — engine begin-playback `sub_825076F0` (slot 21) is **never dispatched** (0×), so the per-frame full-start always takes its skip branch and the playback clock never starts. - **Classification: (B) guest-side state machine.** The gate fields are the engine's *correct* reset defaults → there is **NO honest our-side fix at the gate** (forcing them = masking, forbidden). The defect is upstream: the guest SM reaches "create decoder (success)" but never issues begin-playback. - **Latest narrowing (evidence, fixed probes):** ARM2-setup `sub_821B55D8` runs once, create-decoder `sub_824F8398` succeeds, and ARM2 then calls engine-setup wrappers **`sub_824F7778` / `sub_824F7630` / `sub_824F7558` / `sub_824F7538` / `sub_824FCB68`** (on `[movie+104]`=engine) — the begin-playback dispatch is gated **inside one of these**. Tracing them (now possible with the fixed probes) for the begin-playback gate + why ours never satisfies it is **the next step**. The likely ultimate unlock is **measuring canary** (same XEX reaches begin-playback) to find the upstream state/signal we don't produce. Full, evidence-grounded detail (engine/vtable/slot map, the eliminations, the investigation arc, the method lessons) lives in the agent-memory grounding file referenced in the project memory index (`milestone2_xma_grounding`). Key anchors: engine `0x40d101c0` vtable `0x8200a1e8` — PUMP slot19 `sub_825078D8`, begin-playback slot21 `sub_825076F0`, submit slot27 `sub_82505C08`, full-start slot40 `sub_825061E0`; movie host `0x40bb0440` (engine at `[host+104]`); SM ARM1 `sub_821B4C98` → ARM2 `sub_821B55D8` → ARM3 `sub_821B5FB8` → poll `sub_821B66B8`. ### Useful commands ```bash # Headless run to the video state (~30-40s, ~1B instr); add diagnostic flags as needed: ./target/release/xenia-rs exec sylpheed.iso -n 6000000000 --quiet # Non-perturbing PC probes (now usable on the movie subsystem): RUST_LOG=warn,xenia_apu=info XENIA_AUDIT_PC_PROBE=0x825078d8,0x82505c08 \ ./target/release/xenia-rs exec sylpheed.iso -n 6000000000 --quiet # Dispatch recorder (filtered): XENIA_DISPATCH_REC=1 XENIA_DISPATCH_REC_TARGETS=0x825076f0,0x82505c08 \ ./target/release/xenia-rs exec sylpheed.iso -n 6000000000 --quiet # Golden / determinism check: CARGO_BUILD_JOBS=4 cargo test -p xenia-app --release --test sylpheed_oracles -- --ignored sylpheed_n50m # Visual (watch the splash; ASK a human to watch — never self-screenshot): ./target/release/xenia-rs exec sylpheed.iso --ui ``` ⚠️ Probe/run discipline: kill background runs by pid or `pkill -x xenia-rs` (NEVER `pkill -f`, it self-matches the launcher). Runs are deterministic (instruction-count clock). 🤖 Generated with [Claude Code](https://claude.com/claude-code)