Skip to content

feat(accuracysnes): E9.09 echo wraps into page zero; fix the ares gate - #312

Merged
doublegate merged 2 commits into
mainfrom
feat/accuracysnes-e9-09-echo-wrap
Aug 1, 2026
Merged

feat(accuracysnes): E9.09 echo wraps into page zero; fix the ares gate#312
doublegate merged 2 commits into
mainfrom
feat/accuracysnes-e9-09-echo-wrap

Conversation

@doublegate

@doublegate doublegate commented Aug 1, 2026

Copy link
Copy Markdown
Owner

E9.09 — the echo write pointer wraps at the 16-bit boundary

ESA names a page, EDL a length, and the address is computed in sixteen bits with nothing clamping it at $FFFF. A driver that sets ESA too high does not get a short buffer or a dropped write — it gets the DSP writing four bytes per sample over the bottom of its own RAM. It is an errata because the failure is remote from its cause: the sound driver corrupts variables it never addressed.

The arrangement is the design, not the instrument

The obvious setup, ESA = $FF with a 2 KiB buffer, wraps after 256 bytes and then writes 1,792 bytes of low RAM — the sample directory, the stack, and the running program at $0200. The test destroys itself, and how much it destroys depends on how long it ran, which is a timing window the row has no reason to need.

ESA = $F9 with the same length ends the buffer exactly at $FFFF:

offset address
0 $F900
1788 $FFFC — the last entry below the boundary
1792 $0000 — the wrap
2044 $00FC — the last entry, then the offset resets

So the wrapped part is page zero and precisely page zero. Nothing else is in reach however long it runs.

No phase to get wrong

The echo offset free-runs whether or not FLG bit 5 has writes enabled — the flag gates the store, not the pointer — so the starting phase is unknowable. The row waits 768 samples against a 512-sample buffer, so every entry is written for every phase.

With EFB = 0 and a constant sample, every entry holds the identical four bytes, which is what makes the row a comparison: the wrapped entry must equal the unwrapped one at $F901, not merely differ from the paint. Page zero is painted $A5 first so "never written" is distinguishable from "written as zero", and the program repaints it with zeroes before releasing the APU.

Inject-verified at the site it names

(page << 8) | (offset & $FF) at dsp.rs's echo22code 2, the code that names a pointer masked inside the ESA page. The guard (code 1, "echo wrote the buffer at all") passes. dsp.rs restored byte-clean from a pre-edit snapshot afterwards.

Fixed: F1.10's ares verdict is not reproducible

Adding E9.09 — an APU row with nothing to do with controller ports — made ares' verdict on F1.10 flip run to run: 04 on eight runs of eight before, then 01 on two of five and 04 on the other three, same binary and same image. The row samples $4212 at the vblank edge, so anything added ahead of Group F moves the phase that decides it.

This is the second time on this exact row — Mesen2's F1.10 verdict flipped the other way when E3.06 was rewritten. Two of three references now demonstrate the row's cross-host verdict carries no information.

A count that includes it is a gate that flakes on unrelated work. ARES_KNOWN_FAILURES drops 4 → 3, and crossval.sh resolves the excluded row through SOURCE_CATALOG.tsv by ID at run time — the catalogue index moves whenever a test is added ahead of it, which is exactly what exposed the problem. Reproducible across runs again (5/5). RustySNES passes the row because of a deliberate fix and that stands.

Verification

cargo fmt --check                                          clean
cargo clippy --workspace --all-targets -- -D warnings       clean
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps  clean
cargo test -p rustysnes-test-harness --features test-roms --test accuracysnes
                                                            56 passed

Cross-validation: snes9x: OK (14 known), Mesen2: OK (1 known), ares: OK (3 known), 54/54 scenes on both scene hosts, 3 reference(s) agree with the cart.

Coverage 351 → 352 of 443 (298 on-cart + 54 scenes); battery 339 tests, 100% on-cart.

🤖 Generated with Claude Code

Summary

Adds AccuracySNES assertion E9.09. It claims that the DSP echo write pointer wraps at the 16-bit boundary into page zero when ESA = $F9 and the echo buffer is 2 KiB.

The claim is false if the pointer masks, clamps, or otherwise avoids page-zero writes. The test compares wrapped and unwrapped buffer entries across independent phases.

Updates the Ares cross-validation gate to exclude unstable assertion F1.10 by runtime ID lookup. The known-failure count changes from 4 to 3.

AccuracySNES coverage increases from 351 to 352 of 443 assertions. The coverage denominator remains 443.

…es gate

The echo write pointer is a 16-bit address with nothing clamping it at
$FFFF, so a buffer placed too high carries on into page zero. It is an
errata because the failure is remote from its cause: the sound driver
corrupts variables it never addressed.

The arrangement is the design, not the instrument. ESA = $FF with a
2 KiB buffer wraps after 256 bytes and then writes 1,792 bytes of low
RAM -- the directory, the stack, and the running program -- so the test
would destroy itself, by an amount a timing window decides. ESA = $F9
with the same length ends the buffer exactly at $FFFF, so the wrapped
part is page zero and precisely page zero, and nothing else is in reach
however long it runs.

The echo offset free-runs whether or not FLG bit 5 has writes enabled,
so the starting phase is unknowable; the row waits 768 samples against a
512-sample buffer, which covers every entry for every phase. With
EFB = 0 and a constant sample every entry holds the identical four
bytes, so the assertion is that the wrapped entry EQUALS the unwrapped
one rather than merely that something changed. The program repaints page
zero before releasing the APU.

Injecting (page << 8) | (offset & $FF) at echo22 fires code 2, the code
that names a pointer masked inside the ESA page; the guard passes.

Coverage 351 -> 352 of 443 (298 on-cart + 54 scenes), battery 339 tests
at 100% on-cart, three references agree.

Adding it also made ares' F1.10 verdict flip run to run on one image
(eight of eight failing before, then two of five passing). F1.10 samples
$4212 at the vblank edge, so anything ahead of Group F moves the phase
that decides it -- the same trap that flipped Mesen2's verdict on the
same row when E3.06 was rewritten. ARES_KNOWN_FAILURES drops 4 -> 3 and
crossval.sh excludes the row by ID resolved through SOURCE_CATALOG.tsv
at run time, because the catalogue index is exactly what moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 1, 2026 17:52
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a2dbc61d-1d1e-47c3-be37-09a983e653d7

📥 Commits

Reviewing files that changed from the base of the PR and between 396d22f and d73ad29.

⛔ Files ignored due to path filters (6)
  • docs/accuracysnes-coverage.md is excluded by !docs/accuracysnes-coverage.md and included by docs/**
  • tests/roms/AccuracySNES/ERROR_CODES.md is excluded by !tests/roms/AccuracySNES/ERROR_CODES.md and included by tests/**
  • tests/roms/AccuracySNES/SOURCE_CATALOG.tsv is excluded by !**/*.tsv, !tests/roms/AccuracySNES/SOURCE_CATALOG.tsv and included by tests/**
  • tests/roms/AccuracySNES/asm/tests_group_a.s is excluded by !tests/roms/AccuracySNES/asm/tests_group_a.s and included by tests/**
  • tests/roms/AccuracySNES/build/accuracysnes-pal.sfc is excluded by !tests/roms/AccuracySNES/build/** and included by tests/**
  • tests/roms/AccuracySNES/build/accuracysnes.sfc is excluded by !tests/roms/AccuracySNES/build/** and included by tests/**
📒 Files selected for processing (8)
  • CHANGELOG.md
  • crates/rustysnes-test-harness/tests/accuracysnes.rs
  • docs/STATUS.md
  • docs/accuracysnes-plan.md
  • scripts/accuracysnes/ares_host/README.md
  • scripts/accuracysnes/crossval.sh
  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
📜 Recent review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: lint
  • GitHub Check: accuracysnes
  • GitHub Check: test-light
  • GitHub Check: build demo + docs
🧰 Additional context used
📓 Path-based instructions (16)
crates/**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

crates/**/*.rs: Preserve the master-clock lockstep timing model.
rustysnes-core::Bus owns mutable machine state, and the CPU borrows &mut Bus.
Preserve determinism: seed, ROM, and input must produce bit-identical output.
Treat test ROMs as the behavioral specification; when documentation disagrees with passing ROM behavior, update the documentation.
Keep unsafe confined to existing allowed areas, namely frontend and FFI code, and document every unsafe block with a // SAFETY: comment.

Files:

  • crates/rustysnes-test-harness/tests/accuracysnes.rs
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use Rust edition 2024 and the toolchain pinned in rust-toolchain.toml (Rust 1.96).
Run cargo fmt --all --check; Rust code must remain rustfmt-compliant.
Run Clippy with cargo clippy --workspace --all-targets -- -D warnings; warnings must not remain.
New public Rust items must have rustdoc because missing_docs is a workspace lint.
Do not run cargo clippy --all-features; scripting and script-wasm are mutually exclusive. Use explicit per-feature jobs instead.

**/*.rs: Do not introduce .unwrap(), .expect(), or panic!() on untrusted external input—such as ROM/save-state bytes, netplay messages, Lua or scripting input, or user-supplied paths—outside #[cfg(test)] code. Use typed errors at those boundaries; locally constructed values or values immediately protected by a checked invariant are allowed.
Every new unsafe { ... } block or unsafe fn must have an adjacent // SAFETY: comment naming the relied-on invariant and its guarantor. Unsafe code outside the frontend and FFI shims should additionally be questioned because unsafe_code is a workspace lint.

**/*.rs: Use Rust edition 2024 with the pinned 1.96 toolchain; satisfy workspace pedantic, nursery, missing_docs, and unsafe_code warnings because CI runs with -D warnings. Document every public item.
Keep unsafe code restricted to the frontend and FFI, and include a // SAFETY: justification for each use.
Keep hot paths allocation-free.
Treat rustysnes_core::Bus as the owner of mutable emulator state; the CPU borrows &mut Bus.
Use the master clock at 21477270 Hz as the timing master; advance the scheduler in lockstep and run other chips on their divisors.
Maintain determinism: seed, ROM, and input must produce bit-identical audio/video; frontend rate control must not alter emulation results.
When implementing hardware behavior, pin and run the failing test ROM first; treat test ROMs as the specification.

Files:

  • crates/rustysnes-test-harness/tests/accuracysnes.rs
  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
**/*.{rs,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Chip-behavior changes must update both the chip implementation and the corresponding docs/<subsystem>.md documentation.

A chip change must update both the chip implementation and its corresponding docs/<chip>.md documentation in the same change.

Files:

  • crates/rustysnes-test-harness/tests/accuracysnes.rs
  • docs/STATUS.md
  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • docs/accuracysnes-plan.md
  • CHANGELOG.md
  • scripts/accuracysnes/ares_host/README.md
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
**/*

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*: Do not commit or vendor the generated snesdev_wiki/ mirror; it is gitignored and intended only as a local reference.
Keep commits focused and use Conventional Commits: <type>(<scope>): <subject>, with an imperative subject of at most 72 characters.
Do not use emojis in code, comments, or commit messages.
Before opening a PR, ensure formatting, Clippy, workspace tests, the core embedded build, rustdoc with warnings denied, documentation coverage, and changelog requirements pass.
Ticket completion must be reflected in the relevant to-dos/ sprint file.

**/*: Preserve the one-directional crate graph: chip crates must not depend on one another; rustysnes-core ties them together.
Never commit commercial ROMs; only commit derived screenshots and hashes.
Keep docs/STATUS.md as the authoritative per-subsystem status and update project documentation in the same PR as code changes.
Do not treat RustyNES v2.0 or engine-lineage anchors as project releases.

Files:

  • crates/rustysnes-test-harness/tests/accuracysnes.rs
  • docs/STATUS.md
  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • scripts/accuracysnes/crossval.sh
  • docs/accuracysnes-plan.md
  • CHANGELOG.md
  • scripts/accuracysnes/ares_host/README.md
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
crates/rustysnes-*/**/*

📄 CodeRabbit inference engine (Custom checks)

For the full pull request diff against its base branch, any observable behavior change under crates/rustysnes-<chip>/ must be accompanied by an edit to the matching docs/<chip>.md; a crate change passes without documentation only when it does not alter observable behavior, with the non-behavioral change stated explicitly.

Files:

  • crates/rustysnes-test-harness/tests/accuracysnes.rs
**/*.{rs,toml}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,toml}: Additive features must be default-off so shipped/native, no_std, and wasm builds remain byte-identical.
Never use or configure --all-features; validate opt-in feature combinations individually as required by the project recipe.

Files:

  • crates/rustysnes-test-harness/tests/accuracysnes.rs
  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
crates/**

⚙️ CodeRabbit configuration file

crates/**: Emulator core. Hot paths are allocation-free; unsafe requires a // SAFETY: comment
naming the invariant. Any change to save-stated fields needs a FORMAT_VERSION bump and a
docs/adr/0006 bump-log entry. Behavior changes must update the matching docs/<chip>.md
in the same change.

Files:

  • crates/rustysnes-test-harness/tests/accuracysnes.rs
docs/**/*.md

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Before changing a subsystem, consult docs/architecture.md, docs/STATUS.md, CONTRIBUTING.md, the relevant subsystem documentation, and applicable ADRs.

New subsystems must add documentation under docs/.

Files:

  • docs/STATUS.md
  • docs/accuracysnes-plan.md
docs/**/*

📄 CodeRabbit inference engine (docs/testing-strategy.md)

Chip crates should exceed 90% unit-test coverage, and each chip should be fuzzable in isolation.

Files:

  • docs/STATUS.md
  • docs/accuracysnes-plan.md
docs/**

⚙️ CodeRabbit configuration file

docs/**: Docs are the spec, not a history log. Flag claims that contradict the code, counts that
contradict the generated docs/accuracysnes-coverage.md, and any statement of coverage that
is broader than what the corresponding test actually asserts.

Files:

  • docs/STATUS.md
  • docs/accuracysnes-plan.md
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: Docs are the spec, not a changelog. Flag prose that has drifted from the code it describes
rather than style nits. The markdownlint gate is pinned to v0.39.0 via pre-commit —
do not report rules that version does not have (MD060 in particular).

Files:

  • docs/STATUS.md
  • docs/accuracysnes-plan.md
  • CHANGELOG.md
  • scripts/accuracysnes/ares_host/README.md
tests/roms/AccuracySNES/gen/src/**/*

📄 CodeRabbit inference engine (Custom checks)

For the full pull request diff against its base branch, when a test or scene is added or removed under tests/roms/AccuracySNES/gen/src/, verify its dossier.rs::MAP entry, all required regenerated artifacts, and matching count changes in docs/accuracysnes-plan.md. Artifact presence must be judged from the path-filter exclusion list, not from unreadable contents.

Files:

  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
tests/roms/AccuracySNES/gen/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Rebuild AccuracySNES after any change to gen/ or asm/; never hand-edit generated asm/tests_group_a.s or asm/scenes.s.

Files:

  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
tests/roms/AccuracySNES/gen/src/**

⚙️ CodeRabbit configuration file

tests/roms/AccuracySNES/gen/src/**: This generates a hardware-accuracy test cartridge. Judge each test by whether it can
distinguish the behavior it names from the alternatives, not by whether it passes.

Flag, specifically:

  • Vacuity. An assertion whose expected value is also what a broken or absent
    implementation produces (zero, "unchanged", "not $FF") needs a paired control assertion
    that would fail on that implementation. Say which alternative goes uncaught.
  • Overstated doc comments. The prose above a test is a claim about what it validates.
    If it names behaviors the emitted program does not exercise, or asserts a rationale that
    is not true of the code, that is a defect even though the test passes.
  • Shared state. OAM, CGRAM, VRAM and the S-DSP registers are not reset between tests. A
    test that does not establish its own starting conditions may be measuring the previous
    one; look for an earlier test that leaves the relevant state dirty.
  • Timing-marginal reads. Reading a register a few cycles after disturbing it, or
    asserting on a value that is still moving, produces a verdict that flips when unrelated
    code shifts. Prefer a settle, a disarm, or a provably stationary value.
  • Scanline geometry. Line 0 is a blanking line; the V counter's low byte aliases on a
    312-line PAL frame; the visible height is 224 or 239 depending on overscan. Constants
    derived from any of these deserve a second look.
  • Duplicate coverage. dossier.rs::MAP must not claim an assertion another test already
    implements. There is a build gate for this, but flag it in review too.

Files:

  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
scripts/accuracysnes/**

⚙️ CodeRabbit configuration file

scripts/accuracysnes/**: The cross-validation harness: the same AccuracySNES image is run on snes9x (through a
libretro host in C) and on Mesen2 (through its test runner and a Lua script), and their
verdicts are compared with the cart's. Its integrity is the whole argument for the
battery, so flag anything that could make a reference appear to agree — a verdict parsed
loosely, a missing-file path that degrades to success, a scene comparison that skips
rather than fails when the golden is absent. A known reference divergence belongs in
SNES9X_KNOWN_FAILURES with a source citation, never in a widened match.

Files:

  • scripts/accuracysnes/crossval.sh
  • scripts/accuracysnes/ares_host/README.md
CHANGELOG.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

User-visible changes must be recorded under the [Unreleased] section.

For the full pull request diff against its base branch, modify CHANGELOG.md when user-visible behavior changes, including emulator output, frontend features, CLI flags, public APIs, or AccuracySNES cartridge contents. Do not require it for purely internal changes, tests, comments, or CI configuration.

Files:

  • CHANGELOG.md
🧠 Learnings (5)
📚 Learning: 2026-07-21T02:10:49.581Z
Learnt from: doublegate
Repo: doublegate/RustySNES PR: 190
File: tests/roms/AccuracySNES/gen/src/tests/ppu.rs:0-0
Timestamp: 2026-07-21T02:10:49.581Z
Learning: For any AccuracySNES tests that perform a runtime measurement investigation using writes to $2137 and $4201, do not reuse measurement/slot indices that may already be owned by another test. Before using a slot, verify it is unused (e.g., via an on-cart probe/readback that confirms the slot contains no prior test result). Then independently record $213F immediately before and immediately after each $2137/$4201 operation, so the test can attribute changes to its own operation and avoid cross-test interference.

Applied to files:

  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
📚 Learning: 2026-07-21T01:34:22.909Z
Learnt from: doublegate
Repo: doublegate/RustySNES PR: 189
File: docs/accuracysnes-plan.md:0-0
Timestamp: 2026-07-21T01:34:22.909Z
Learning: When reviewing the AccuracySNES documentation in docs/accuracysnes-*.md (notably docs/accuracysnes-plan.md vs the generated docs/accuracysnes-coverage.md), treat the reported metrics as intentionally non-equivalent: the battery test count and dossier assertion coverage are not interchangeable. Do not infer one count/coverage from the other during review (e.g., one test may contain multiple assertions, and multiple tests may contribute to a single assertion/row such as E6.02).

Applied to files:

  • docs/accuracysnes-plan.md
📚 Learning: 2026-07-21T05:22:58.848Z
Learnt from: doublegate
Repo: doublegate/RustySNES PR: 197
File: docs/accuracysnes-plan.md:598-600
Timestamp: 2026-07-21T05:22:58.848Z
Learning: In the AccuracySNES documentation under `docs/`, when an assertion exists in the research dossier but cannot be measured/verified by the current cartridge timing test, distinguish the dossier assertion from test measurability: keep the original hardware assertion (and any contribution to the coverage denominator) intact, withdraw/stop using the specific test coverage only if the sources cannot decompose the required CPU-cycle timing into bus vs internal components, and mark the row as not measurable using the `[NOT CART-MEASURABLE ...]` annotation with links to the corresponding plan section (e.g., `docs/accuracysnes-plan.md` §A5.20) and the related roadmap/ticket (e.g., `to-dos/ROADMAP.md` ticket `T-06-A`). Ensure the documentation/coverage reporting treats the row as uncovered rather than removing or redefining the assertion.

Applied to files:

  • docs/accuracysnes-plan.md
📚 Learning: 2026-07-21T06:21:34.629Z
Learnt from: doublegate
Repo: doublegate/RustySNES PR: 198
File: docs/accuracysnes-plan.md:0-0
Timestamp: 2026-07-21T06:21:34.629Z
Learning: When reviewing slot allocation for SNES/ROM measurement channels in generator-driven Rust code, account for slots assigned via generation-time computed writers (e.g., slots derived from formulas like `slot_base = 8 + index * 2`) rather than only literal `record(...)` calls. Trace the computed writer’s full emitted slot range and verify there are no collisions with other opcodes/channels that may use different slot ranges after earlier conflict resolution.

Applied to files:

  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
📚 Learning: 2026-07-22T06:12:00.703Z
Learnt from: doublegate
Repo: doublegate/RustySNES PR: 201
File: tests/roms/AccuracySNES/gen/src/tests/apu.rs:1508-1538
Timestamp: 2026-07-22T06:12:00.703Z
Learning: When working on APU/voice timing in these AccuracySNES test sources, treat NTSC vs PAL differences as a first-class constraint. In particular, do not change `Voice::settle` (or any settling/code-size/timing logic it affects) unless you cross-validate against both regions (NTSC and PAL), because timing/polling phase shifts can cause regressions that may appear as PAL-only test failures. For ROM-specific expectations like `E7.13`, keep the intentionally chosen ENVX range (e.g., `0x68..=0x7C`) unless you re-validate that the absorbed post-KON timing variation still matches across both regions.

Applied to files:

  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
🪛 LanguageTool
docs/STATUS.md

[style] ~244-~244: Try using a descriptive adverb here.
Context: ...cs/adr/0013), kept as separate columns on purpose (docs/accuracysnes-coverage.md`, regen...

(ON_PURPOSE_DELIBERATELY)

scripts/accuracysnes/ares_host/README.md

[style] ~111-~111: Consider an alternative for the overused word “exactly”.
Context: ...r a test is added ahead of it, which is exactly the circumstance that exposed the probl...

(EXACTLY_PRECISELY)

🔇 Additional comments (13)
tests/roms/AccuracySNES/gen/src/tests/apu.rs (4)

6823-6829: 🎯 Functional Correctness | ⚡ Quick win

e9_09 never writes the sample's loop-address entry; it depends on a leftover value from e9_05.

e9_09 writes only DIR_ADDR and DIR_ADDR + 1 (the sample start address). It never writes DIR_ADDR + 2 and DIR_ADDR + 3 (the loop address). The sample is built with brr_block(shift, filter, 0b11, nibble, nibble): bit 0 is END and bit 1 is LOOP, so 0b11 sets both. A looping BRR block reads its loop address from the source directory entry at load time. Without that entry, the DSP loops into whatever loop address is still in ARAM from the previous test.

This currently works only because e9_05 — the test that runs immediately before e9_09 in all() — writes the same 4-byte directory entry with loop address equal to its own addr, and both tests upload their sample to the same IMAGE_BASE. If a future change reorders all(), drops e9_05, or changes e9_05 to use a different DIR_ADDR or upload location, e9_09's voice will loop into stale or wrong data instead of its own sample. The wrapped-entry equality assertion (E9.09's core claim) depends on the voice reproducing an unchanging constant output across the full 768-sample window; a wrong loop address breaks that silently rather than failing loudly.

Write DIR_ADDR + 2 and DIR_ADDR + 3 explicitly in e9_09, the same way e9_05 does.

🐛 Proposed fix to make the loop address self-contained
     let mut prog = Spc::new();
     let addr = prog.data_first(IMAGE_BASE, &constant_sample(0xC, 0x7));
     prog.mov_x_imm(0xEF).mov_sp_x();
     let [lo, hi] = addr.to_le_bytes();
     prog.mov_a_imm(lo).mov_abs_a(DIR_ADDR);
     prog.mov_a_imm(hi).mov_abs_a(DIR_ADDR + 1);
+    prog.mov_a_imm(lo).mov_abs_a(DIR_ADDR + 2);
+    prog.mov_a_imm(hi).mov_abs_a(DIR_ADDR + 3);

As per path instructions, "A test that does not establish its own starting conditions may be measuring the previous one; look for an earlier test that leaves the relevant state dirty."

Source: Path instructions


6804-6906: LGTM!

Also applies to: 6908-6936, 6938-6953


114-114: LGTM!


6805-6819: 📐 Maintainability & Code Quality

The rename landed in commit d73ad29f: CYCLES_WAITED was renamed to DELAY_BLOCKS.

			> Likely an incorrect or invalid review comment.
tests/roms/AccuracySNES/gen/src/dossier.rs (1)

233-233: LGTM!

docs/STATUS.md (1)

26-27: LGTM!

Also applies to: 244-244

docs/accuracysnes-plan.md (2)

16-20: LGTM!

Also applies to: 754-768


747-753: 🗄️ Data Integrity & Integration

No change needed. crossval.sh sets MESEN2_KNOWN_FAILURES=1 and excludes F1.10 from the retained known-failure entry.

scripts/accuracysnes/crossval.sh (2)

347-356: LGTM!

The lookup fails loudly (exit 1) rather than silently degrading to success when ARES_UNSTABLE_ROW is missing from SOURCE_CATALOG.tsv, and the exclusion compares numerically ($2 + 0 != skip + 0) rather than as strings. This matches the path instruction to flag anything that would let a reference verdict "appear to agree" through a loosely-parsed or silently-skipped comparison — here it does the opposite correctly.


241-255: LGTM!

Also applies to: 280-287

scripts/accuracysnes/ares_host/README.md (1)

98-119: LGTM!

CHANGELOG.md (1)

12-38: LGTM!

Also applies to: 41-57

crates/rustysnes-test-harness/tests/accuracysnes.rs (1)

599-624: 🗄️ Data Integrity & Integration

No slot conflict exists.

Slots 263–265 are recorded only by the E9.09 test, so GROUP_E_BATCH_SLOTS does not reuse them.

			> Likely an incorrect or invalid review comment.

Walkthrough

The PR adds the AccuracySNES E9.09 echo-buffer wrapping test and updates its coverage records. It also excludes nondeterministic ares result F1.10 by test ID, with runtime catalogue lookup and updated documentation.

Changes

AccuracySNES validation updates

Layer / File(s) Summary
E9.09 echo-pointer-wrap coverage
tests/roms/AccuracySNES/gen/src/tests/apu.rs, tests/roms/AccuracySNES/gen/src/dossier.rs, crates/rustysnes-test-harness/tests/accuracysnes.rs, docs/STATUS.md, docs/accuracysnes-plan.md
The APU suite registers E9.09, configures a $F900 echo buffer, checks pointer wrapping into page zero, restores page-zero RAM, and records the added assertion and coverage counts.
F1.10 ares exclusion handling
scripts/accuracysnes/crossval.sh, scripts/accuracysnes/ares_host/README.md, CHANGELOG.md
crossval.sh resolves F1.10 from SOURCE_CATALOG.tsv, excludes its result from failure counting, and reduces ARES_KNOWN_FAILURES from 4 to 3. Documentation records the nondeterministic verdict.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant e909_test
  participant echo_buffer
  participant page_zero
  e909_test->>page_zero: Paint sentinel bytes
  e909_test->>echo_buffer: Configure and run echo cycle
  echo_buffer->>page_zero: Write after 16-bit pointer wrap
  e909_test->>page_zero: Compare wrapped and in-buffer bytes
  e909_test->>page_zero: Restore RAM
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 10
✅ Passed checks (10 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title uses valid Conventional Commit syntax and accurately summarizes the E9.09 test and Ares gate changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Changelog Entry ✅ Passed The full PR diff against origin/main modifies CHANGELOG.md with Added E9.09 and Fixed F1.10 entries, while the AccuracySNES cartridge also changes.
Docs-As-Spec ✅ Passed The only crates/** edit is an AccuracySNES integration-test slot list; it adds three diagnostic measurement lines and does not change rustysnes-* emulator behavior.
Accuracysnes Bookkeeping ✅ Passed Against origin/main, E9.09 is registered and mapped to E9.09; all required generated paths are in the diff, no scene changed, and coverage plus plan counts increase by one.
No Panic On Untrusted Input ✅ Passed The PR adds no .unwrap(), .expect(), or panic!() calls. E9.09 builds inputs locally with Spc and Asm; existing panic-like calls are unchanged.
Safety Comment On New Unsafe ✅ Passed The full origin/main..HEAD Rust diff adds no unsafe {} blocks or unsafe fn declarations, and no changed Rust file contains unsafe syntax.

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new AccuracySNES APU/DSP assertion (E9.09) that verifies the S-DSP echo write pointer is 16-bit and wraps past $FFFF into page zero, and hardens the ares cross-validation gate by excluding the now-demonstrated-to-be-phase-fragile F1.10 row by ID (resolved via SOURCE_CATALOG.tsv) rather than by catalogue index.

Changes:

  • Add E9.09 test implementation to the AccuracySNES generator sources and wire it into the dossier mapping and measurement reporting.
  • Regenerate AccuracySNES committed artifacts (assembly, catalog, error-code docs, coverage counts) to include the new row and updated totals.
  • Stabilize ares gate in crossval.sh by excluding the unstable row by name/ID at runtime and adjusting ARES_KNOWN_FAILURES.

Reviewed changes

Copilot reviewed 12 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/roms/AccuracySNES/SOURCE_CATALOG.tsv Regenerated catalog entry list to include E9.09 and shifted indices/addresses accordingly.
tests/roms/AccuracySNES/gen/src/tests/apu.rs Adds the E9.09 generator-side test logic and helpers, plus measurement-slot records.
tests/roms/AccuracySNES/gen/src/dossier.rs Maps dossier assertion E9.09 to the new cart row.
tests/roms/AccuracySNES/ERROR_CODES.md Documents new E9.09 failure codes (generated output).
tests/roms/AccuracySNES/asm/tests_group_a.s Regenerated assembled test bodies/metadata to include test_e9_09 and updated program indices.
scripts/accuracysnes/crossval.sh Excludes F1.10 from ares gate by resolving its catalog index from SOURCE_CATALOG.tsv at runtime; updates expected known-failure count.
scripts/accuracysnes/ares_host/README.md Documents the rationale for excluding F1.10 from the ares gate due to non-reproducibility.
docs/STATUS.md Updates project status metrics to reflect 352/443 covered assertions and 339-test battery.
docs/accuracysnes-plan.md Updates plan/metrics and notes around the new assertion and the ares gating change.
docs/accuracysnes-coverage.md Regenerated coverage totals and uncovered list to account for E9.09 now being covered.
crates/rustysnes-test-harness/tests/accuracysnes.rs Extends Group E batch measurement slot reporting to include the new E9.09 slots.
CHANGELOG.md Documents the new E9.09 addition and the ares gate stabilization for F1.10.

Comment on lines +6813 to +6814
/// Whole buffer cycles to wait, over the one the wrap needs. 512 samples each.
const CYCLES_WAITED: u8 = 16;
Bot review, three of four accepted.

CYCLES_WAITED was a wrong name attached to a right number: 16 is delay
BLOCKS, not buffer cycles, and 16 * 48 = 768 samples against a
512-sample buffer. The constant now says so, which is the fact that
makes the coverage phase-free.

The ares exclusion compares $2 + 0 against skip + 0. Both sides are
already strnum in practice, so this changes nothing measurable (3 across
three runs before and after) -- it removes the need to know that.

The ROM is byte-identical: checksum stays $9AB2.

Declined: deriving SOURCE_CATALOG.tsv's path from BASH_SOURCE. The
script cd's to the repo root at line 36 and every path in it is
root-relative by design, ROM= included; the catalogue is the same shape
as everything around it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@doublegate doublegate changed the title feat(accuracysnes): E9.09 echo wraps into page zero; stabilise the ares gate feat(accuracysnes): E9.09 echo wraps into page zero; fix the ares gate Aug 1, 2026
@doublegate

Copy link
Copy Markdown
Owner Author

Bot review adjudicated — three accepted, one declined.

Accepted.

  • CYCLES_WAITED was a wrong name on a right number. 16 is delay blocks, not buffer cycles: one block is 1,536 SPC cycles = 48 output samples, and 16 * 48 = 768 against a 512-sample buffer. Renamed to DELAY_BLOCKS with the arithmetic stated, since that ratio is exactly what makes the coverage phase-free.
  • The awk exclusion now compares $2 + 0 against skip + 0. Both sides are already strnum in practice — the count is 3 across three runs before and after — so this changes nothing measurable; it removes the need to know that.
  • PR title shortened to 70 characters.

The ROM is byte-identical across the rename: checksum stays $9AB2.

Declined: deriving SOURCE_CATALOG.tsv's path from ${BASH_SOURCE[0]}. The script already normalizes its working directory — crossval.sh:36 is cd "$(dirname "$0")/../..", and every path in it is repo-root-relative by design, including ROM=tests/roms/AccuracySNES/build/accuracysnes.sfc on line 38. The catalogue read is the same shape as everything around it and works from any working directory today.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

This PR adds the E9.09 DSP echo page zero wrapping test to the AccuracySNES test suite and dynamically excludes the host-timing-sensitive F1.10 test row from the ares cross-validation gate in crossval.sh.

Blocking issues

None found.

Suggestions

  • scripts/accuracysnes/crossval.sh#L348: The awk -F'\t' command relies strictly on tab separation in SOURCE_CATALOG.tsv. Consider trimming leading/trailing whitespace on $1 to ensure strict integer conversion when comparing $2 + 0 != skip + 0 in line 355.
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs#L6811: In e9_09(), DELAY_BLOCKS is set to 16 (768 samples). Consider adding an explicit comment detailing the SPC clock math (1536 SPC cycles per block / 32 cycles per sample = 48 samples/block) directly in tests_group_a.s for assembly reviewers who do not inspect apu.rs.

Nitpicks

  • tests/roms/AccuracySNES/asm/tests_group_a.s#L18589: Hardcoded wait loop bound cpx #$8000 lacks a comment indicating equivalent CPU cycle duration.

Automated first-pass review by agy on a self-hosted runner -- not a human review.

@doublegate
doublegate merged commit fe3ea97 into main Aug 1, 2026
16 checks passed
@doublegate
doublegate deleted the feat/accuracysnes-e9-09-echo-wrap branch August 1, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants