Skip to content

feat(accuracysnes): B2.07/B2.08 — the frame rate, measured against the APU - #317

Merged
doublegate merged 2 commits into
mainfrom
feat/accuracysnes-b2-07-frame-rate
Aug 2, 2026
Merged

feat(accuracysnes): B2.07/B2.08 — the frame rate, measured against the APU#317
doublegate merged 2 commits into
mainfrom
feat/accuracysnes-b2-07-frame-rate

Conversation

@doublegate

@doublegate doublegate commented Aug 2, 2026

Copy link
Copy Markdown
Owner

"60.0988 Hz" is a claim about wall-clock time, and a cart has no wall clock. The one independent timebase it can reach is the APU's: the SPC700 runs from its own 24.576 MHz crystal at the same rate in both regions, so counting APU timer ticks over a known number of frames measures the frame rate in units of that crystal.

Timer 0 at T0DIV = 1 steps T0OUT every 256 SMP base clocks — 8,010 ticks a second on either console.

image frame rate ticks/frame over 48 frames
NTSC 60.0988 Hz 133.3 ~6,398
PAL 50.0070 Hz 160.2 ~7,689

1,291 apart, keyed on the measured frame height rather than the region bit — whose position B2.10 had to settle, and which a frame-rate test must not depend on.

It only became a real measurement last PR

The SPC divisor used to be pinned to the NTSC master clock, which made the APU scale with the video clock. An APU that speeds up exactly when the frames get shorter cannot measure how long a frame is — this row would have read the same number on both images. #316 is what made it meaningful.

What it pins, stated honestly

The band is ±2%, which pins the rate to about a part in fifty. That is not the precision the quoted figures have, and the doc comment says so: distinguishing 60.0988 from a flat 60.0 is one part in 600 and needs a count ten times longer than the battery should spend. What this row excludes is a core whose frame rate is wrong by a scanline count or whose APU is region-scaled — both percent-level errors, and one of which this emulator actually had.

The handshake

The cart cannot sit in upload_and_run's wait loop while it counts frames, so the row drives the program itself through a new upload_only: the SPC announces it is accumulating, the cart counts vblanks, then writes a stop byte on port 3. Every wait on both sides is bounded — an APU that never answers reports SKIP rather than hanging the battery.

And it flipped F1.10 again, on a third host/image

Adding it made Mesen2's PAL verdict on F1.10 flip between runs of a single build: two runs of the same image gave F1.03 alone, then F1.03 + F1.10. That row samples $4212 right at the vblank edge, so any test added ahead of Group F moves the phase that decides it.

mesen_crossval.lua now skips one index supplied through ACCURACYSNES_SKIP_INDEX, which crossval.sh resolves by ID from SOURCE_CATALOG.tsv — the index moves whenever a test is added ahead of it, which is exactly the circumstance that exposed the problem. The row is still logged, just not counted, and the ares gate now shares the same resolved index instead of computing its own.

Three consecutive full cross-validation runs are stable, which is the check that matters for a gate that was flaking.

Verification

cargo fmt --check, workspace clippy at -D warnings, and the 56-test AccuracySNES harness suite pass. snes9x: OK (15 known), Mesen2: OK (1 known), ares: OK (3 known), snes9x PAL: OK (15 known), Mesen2 PAL: OK (1 known), 54/54 scenes on both scene hosts, 3 reference(s) agree with the cart.

Coverage 355 → 356 of 443 (302 on-cart + 54 scenes); battery 343 tests, 100% on-cart.

🤖 Generated with Claude Code

Summary

Adds AccuracySNES dossier assertions B2.07 and B2.08. The tests measure APU timer ticks over 48 frames and use measured frame height to distinguish NTSC and PAL timing.

The claim is false if the tests use wall-clock time or the region bit, misclassify frame timing, or report success when the APU does not respond. Bounded waits and SKIP reporting prevent an unresponsive APU from producing a verdict.

Cross-validation excludes F1.10 by catalogue ID on both Mesen2 and ares. The row remains logged. The stability claim is false if the three full runs disagree or if the excluded row affects verdict counts.

AccuracySNES coverage increases from 355/443 to 356/443. The battery increases from 342 to 343 tests. On-cart coverage increases from 301 to 302. Rendered-scene coverage remains 54.

…e APU

"60.0988 Hz" is a claim about wall-clock time and a cart has no wall
clock. The one independent timebase it can reach is the APU's, whose
24.576 MHz crystal runs at the same rate in both regions: timer 0 at
T0DIV = 1 steps T0OUT every 256 SMP base clocks -- 8,010 ticks a second
on either console -- so counting ticks over a known number of frames
measures the frame rate in units of that crystal.

This only became a real measurement once the APU stopped scaling with the
video clock (the region-independence fix). An APU that speeds up exactly
when the frames get shorter cannot measure how long a frame is; the row
would have read the same number on both images.

Over 48 frames: ~6,398 NTSC against ~7,689 PAL, 1,291 apart, keyed on the
MEASURED frame height rather than the region bit. The band is +/-2% and
the doc says plainly that this pins the rate to about a part in fifty --
NOT to the precision the quoted figures have. What it excludes is a wrong
scanline count or a region-scaled APU clock, both percent-level errors
and one of which this emulator had.

The cart cannot sit in upload_and_run's wait loop while it counts frames,
so the row drives the program itself through a new upload_only: the SPC
announces it is accumulating, the cart counts vblanks, then writes a stop
byte. Every wait on both sides is bounded, so an APU that never answers
reports SKIP rather than hanging the battery.

Adding it also made Mesen2's PAL verdict on F1.10 flip between runs of a
single build -- the third host/image combination to show that row is
decided by the cart's execution phase. mesen_crossval.lua now skips one
index supplied through ACCURACYSNES_SKIP_INDEX, which crossval.sh
resolves BY ID from SOURCE_CATALOG.tsv; the ares gate shares the same
resolved index instead of computing its own. Three consecutive full
cross-validation runs are stable.

Coverage 355 -> 356 of 443 (302 on-cart + 54 scenes), battery 343 tests
at 100% on-cart, three references agree on both images.

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

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The AccuracySNES suite adds B2.07/B2.08 APU timing measurements over 48 frames. Cross-validation now resolves and excludes the unstable F1.10 catalogue row for both Mesen2 and ares.

Changes

AccuracySNES APU timing coverage

Layer / File(s) Summary
APU frame timing measurement
tests/roms/AccuracySNES/gen/src/tests/apu.rs, CHANGELOG.md
Adds upload-only APU execution, bounded waits, vblank counting, measured frame height, and NTSC/PAL tick validation for B2.07/B2.08.
APU test registration and coverage records
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
Registers the test, maps its assertions, adds batch measurements, and updates AccuracySNES coverage totals.

F1.10 cross-validation exclusion

Layer / File(s) Summary
Shared F1.10 exclusion handling
scripts/accuracysnes/crossval.sh, scripts/accuracysnes/mesen_crossval.lua, CHANGELOG.md
Resolves F1.10 by catalogue ID once and shares its index with Mesen2 and ares filtering. Mesen2 logs the excluded row and omits it from result counters.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AccuracySNESCart
  participant SPC700
  participant APUTest
  AccuracySNESCart->>SPC700: upload timer program
  APUTest->>AccuracySNESCart: count 48 vblank intervals
  AccuracySNESCart->>SPC700: send stop command
  SPC700-->>APUTest: return timer ticks
  APUTest->>APUTest: validate NTSC or PAL range
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 9 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title uses a valid Conventional Commits prefix and describes the change, but its subject is not written in imperative mood. Rewrite the subject in imperative mood, for example: feat(accuracysnes): add B2.07/B2.08 frame-rate measurements against the APU.
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 from merge-base f195314 to HEAD modifies CHANGELOG.md with entries for F1.10 handling and B2.07/B2.08 cartridge behavior.
Docs-As-Spec ✅ Passed The only crate change extends the AccuracySNES integration test's host-side diagnostic report from 17 to 19 measurement rows; it does not change emulator or chip behavior.
Accuracysnes Bookkeeping ✅ Passed Against origin/main, B2.07 maps to B2.07/B2.08; all six required artifacts are in the diff, no scene changed, and plan and coverage counts each increase by one.
No Panic On Untrusted Input ✅ Passed The PR diff adds no .unwrap(), .expect(), or panic!() calls. New B2.07 code uses bounded assembly waits and typed local construction only.
Safety Comment On New Unsafe ✅ Passed The feature diff adds no unsafe blocks or unsafe fn declarations; repository searches also find no unsafe syntax in the changed AccuracySNES Rust sources.

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 on-cart timing check that measures the console frame rate against the APU’s region-independent crystal (via SPC700 timer ticks), and stabilizes cross-validation gating by excluding a phase-fragile controller-port row via an ID-resolved catalog index.

Changes:

  • Add B2.07/B2.08 “Frame rate vs APU clock” test, including a new CPU↔APU handshake path (upload_only) and measurement-slot reporting.
  • Update AccuracySNES metadata/docs (catalog, dossier map, error codes, coverage/plan/status) to account for the added row and coverage increments.
  • Make Mesen2 + ares cross-validation consistently exclude F1.10 by passing a shared, ID-resolved ACCURACYSNES_SKIP_INDEX.

Reviewed changes

Copilot reviewed 12 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/roms/AccuracySNES/SOURCE_CATALOG.tsv Registers the new B2.07 row and shifts subsequent catalog indices.
tests/roms/AccuracySNES/gen/src/tests/apu.rs Implements the new APU-timed frame-rate test and adds upload_only/tagged timeout label support.
tests/roms/AccuracySNES/gen/src/dossier.rs Maps dossier ID B2.07 to assertion rows B2.07 + B2.08.
tests/roms/AccuracySNES/ERROR_CODES.md Documents new failure codes for B2.07.
tests/roms/AccuracySNES/asm/tests_group_a.s Generated ROM test bodies updated to include test_b2_07 and new SPC payload bytes.
scripts/accuracysnes/mesen_crossval.lua Adds env-driven skip index to exclude a phase-fragile row from the failing count.
scripts/accuracysnes/crossval.sh Resolves unstable row index by ID from SOURCE_CATALOG.tsv and exports it for all hosts.
docs/STATUS.md Updates reported AccuracySNES test/coverage counts (355→356; 342→343; on-cart 301→302).
docs/accuracysnes-plan.md Updates headline test/coverage counts to match the new row.
docs/accuracysnes-coverage.md Updates B2 coverage and overall on-cart/total coverage counts.
crates/rustysnes-test-harness/tests/accuracysnes.rs Extends the measurement-slot report list to include the new B2.07 slots.
CHANGELOG.md Documents the new B2.07/B2.08 measurement row and the expanded crossval exclusion for F1.10.

Comment on lines +8898 to +8902
/// The cart cannot sit in `upload_and_run`'s wait loop while it counts frames, so this uses
/// [`upload_only`] and drives the program itself: the SPC announces it is accumulating, the cart
/// counts vblanks, then writes a stop byte. **Every wait on both sides is bounded** — an APU that
/// never answers reports SKIP rather than hanging the battery, and the SPC's own poll loop exits on
/// a counter as well as on the stop byte, so a cart that dies mid-count cannot strand it.
@@ -596,7 +596,7 @@ const A5_08_SLOTS: [(u8, &str); 7] = [
];

/// The measurement slots the `v1.29.0` Group E batch records.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 72-75: Correct the changelog statement that claims every wait is
bounded, or update the SPC poll loop emitted by b2_07 in the APU test
implementation so it has a bounded timeout rather than waiting indefinitely for
the port 3 stop byte. Ensure the changelog accurately reflects the shipped
behavior and the row reports SKIP when the SPC does not respond.

In `@crates/rustysnes-test-harness/tests/accuracysnes.rs`:
- Around line 624-628: Update the accuracy assertions for slot 276 in the test
around the “B2.07 measured frame height” entry so the measured value is accepted
only when it equals 261 (NTSC) or 311 (PAL). Preserve the existing slot 257
validation and ensure any other value, including an unwritten zero, fails the
test.

In `@scripts/accuracysnes/crossval.sh`:
- Around line 303-304: Update the UNSTABLE_INDEX lookup in the cross-validation
flow to require exactly one matching UNSTABLE_ROW entry, then validate that the
catalogue index is a decimal integer within 0 <= index < test_count. Reject
invalid, ambiguous, one-based, or values such as “293x” before exporting
ACCURACYSNES_SKIP_INDEX or applying either host’s filtering.

In `@tests/roms/AccuracySNES/gen/src/tests/apu.rs`:
- Around line 8923-8937: Bound the SPC polling loop around the visible poll
label and T0OUT accumulation by adding the iteration counter described in the
nearby documentation, while preserving exit on STOP and allowing fall-through to
publish and release_to_ipl. In CHANGELOG.md lines 72-75, retain the statement
that every wait on both sides is bounded after this code change.
- Around line 8821-8832: Align the measurement start with a vblank boundary
instead of beginning accumulation at the arbitrary `@b2go` phase. In the
`@b2go/`@b2frame flow, call wait_vblank_far once before writing the start byte on
port 3, and update the SPC-side startup logic to drain $FD and begin
accumulation only after receiving that byte. Preserve the intended B2_FRAMES
frame interval and ±2% assertion bounds.
- Around line 180-215: Refactor upload_and_run_tagged to reuse upload_only for
the shared program emission, register setup, upload, and APUIO0 clearing instead
of duplicating that upload half. Keep only the wait-for-done loop and its
surrounding behavior in upload_and_run_tagged, while preserving upload_only’s
existing semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4acdc5a6-965e-4cb7-bc47-b692a15d0f4c

📥 Commits

Reviewing files that changed from the base of the PR and between f195314 and 7732bd1.

⛔ 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/crossval.sh
  • scripts/accuracysnes/mesen_crossval.lua
  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: lint
  • GitHub Check: test-light
  • GitHub Check: accuracysnes
  • GitHub Check: copilot-pull-request-reviewer
  • GitHub Check: review
  • GitHub Check: build demo + docs
🧰 Additional context used
📓 Path-based instructions (16)
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
**/*.{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:

  • docs/STATUS.md
  • docs/accuracysnes-plan.md
  • crates/rustysnes-test-harness/tests/accuracysnes.rs
  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • CHANGELOG.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:

  • docs/STATUS.md
  • scripts/accuracysnes/mesen_crossval.lua
  • scripts/accuracysnes/crossval.sh
  • docs/accuracysnes-plan.md
  • crates/rustysnes-test-harness/tests/accuracysnes.rs
  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • CHANGELOG.md
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
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/**

⚙️ 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/mesen_crossval.lua
  • scripts/accuracysnes/crossval.sh
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
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
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
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-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-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-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)

🔇 Additional comments (10)
scripts/accuracysnes/crossval.sh (2)

293-301: LGTM!


305-309: LGTM!

Also applies to: 369-371

scripts/accuracysnes/mesen_crossval.lua (1)

24-28: LGTM!

Also applies to: 70-79, 97-97

tests/roms/AccuracySNES/gen/src/tests/apu.rs (2)

28-28: LGTM!

Also applies to: 152-152, 398-411, 8947-8965


8805-8819: 🎯 Functional Correctness

Retain the current STARTED handshake. apu_upload writes zero to APUIO1 at the final transfer close before upload_only returns, so an earlier program or the IPL cannot leave $3C there. The wait accepts $3C only after this program writes it.

			> Likely an incorrect or invalid review comment.
CHANGELOG.md (1)

14-26: LGTM!

Also applies to: 56-71, 76-76

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

599-599: LGTM!

docs/STATUS.md (1)

26-27: LGTM!

Also applies to: 244-244

docs/accuracysnes-plan.md (1)

16-17: LGTM!

tests/roms/AccuracySNES/gen/src/dossier.rs (1)

275-275: 🗄️ Data Integrity & Integration

Keep the coverage totals unchanged. B2.07 and B2.08 form one enumerated dossier row, so the generated report correctly increases on-cart coverage from 301 to 302.

			> Likely an incorrect or invalid review comment.

Comment thread CHANGELOG.md
Comment on lines +72 to +75
The cart cannot sit in `upload_and_run`'s wait loop while it counts frames, so the row drives the
program itself through a new `upload_only`: the SPC announces it is accumulating, the cart counts
vblanks, then writes a stop byte. Every wait on both sides is bounded, so an APU that never answers
reports SKIP rather than hanging the battery. Coverage `355 → 356 of 443`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

"Every wait on both sides is bounded" is not true of the shipped SPC program.

The cart side is bounded. The SPC side is not: the emitted poll loop in b2_07 exits only on the stop byte from port 3. See the finding on tests/roms/AccuracySNES/gen/src/tests/apu.rs. Correct the code or this sentence; the changelog is the user-visible statement of what the row guarantees.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 72 - 75, Correct the changelog statement that
claims every wait is bounded, or update the SPC poll loop emitted by b2_07 in
the APU test implementation so it has a bounded timeout rather than waiting
indefinitely for the port 3 stop byte. Ensure the changelog accurately reflects
the shipped behavior and the row reports SKIP when the SPC does not respond.

Comment on lines +624 to +628
(
275,
"B2.07 APU ticks over 48 frames (~6398 NTSC, ~7689 PAL)",
),
(276, "B2.07 measured frame height (261 NTSC, 311 PAL)"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Slot 276 has exactly two legal values; assert them.

The measured frame height is 261 or 311. Nothing else is a valid reading. The existing crude check on slot 257 exists because an unwritten slot reads zero and prints as a plausible-looking row. Slot 276 admits the same failure and is cheaper to bound than slot 257.

Proposed additional check
     let voices = report.meas[257];
     assert!(
         (1..=8).contains(&voices),
         "E8.01 recorded {voices} started voices, which is not a count of eight voices — the slot \
          was never written, or the mask reduction overflowed"
     );
+
+    let height = report.meas[276];
+    assert!(
+        height == 261 || height == 311,
+        "B2.07 recorded a frame height of {height}, which is neither NTSC's 261 nor PAL's 311 — \
+         the slot was never written, or the measurement is wrong"
+    );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/rustysnes-test-harness/tests/accuracysnes.rs` around lines 624 - 628,
Update the accuracy assertions for slot 276 in the test around the “B2.07
measured frame height” entry so the measured value is accepted only when it
equals 261 (NTSC) or 311 (PAL). Preserve the existing slot 257 validation and
ensure any other value, including an unwritten zero, fails the test.

Comment on lines +303 to +304
UNSTABLE_INDEX=$(awk -F'\t' -v id="$UNSTABLE_ROW" '$2 == id { print $1; exit }' \
tests/roms/AccuracySNES/SOURCE_CATALOG.tsv)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

catalog=tests/roms/AccuracySNES/SOURCE_CATALOG.tsv

count=$(awk -F'\t' '$2 == "F1.10" { n++ } END { print n + 0 }' "$catalog")
[[ "$count" -eq 1 ]]

index=$(awk -F'\t' '$2 == "F1.10" { print $1; exit }' "$catalog")
[[ "$index" =~ ^[0-9]+$ ]]

first_index=$(awk -F'\t' '$1 ~ /^[0-9]+$/ { print $1; exit }' "$catalog")
[[ "$first_index" == "0" ]]

printf 'F1.10 index: %s\n' "$index"

rg -n \
  'ACCURACYSNES_SKIP_INDEX|SOURCE_CATALOG.tsv|STATUS \+|skip.*\+ 0|for i = 0' \
  scripts/accuracysnes

Repository: doublegate/RustySNES

Length of output: 2489


🏁 Script executed:

set -euo pipefail

catalog=tests/roms/AccuracySNES/SOURCE_CATALOG.tsv

printf '%s\n' '--- catalog header and F1.10 row ---'
awk -F'\t' '
  NR <= 5 || $2 == "F1.10" { printf "%d:%s\n", NR, $0 }
' "$catalog"

printf '%s\n' '--- catalog index diagnostics ---'
python3 - "$catalog" <<'PY'
import csv, sys
path = sys.argv[1]
with open(path, newline="") as f:
    rows = list(csv.reader(f, delimiter="\t"))
print("rows:", len(rows))
for i, row in enumerate(rows, 1):
    if len(row) < 2:
        print("short_row", i, len(row), row)
indices = [row[0] for row in rows if len(row) >= 1]
numeric = [(i, int(v)) for i, v in enumerate(indices, 1) if v.isdigit()]
print("numeric_rows:", len(numeric))
print("first_numeric:", numeric[:3])
print("last_numeric:", numeric[-3:])
from collections import Counter
counts = Counter(v for _, v in numeric)
print("duplicate_numeric_indices:", sorted((v, n) for v, n in counts.items() if n > 1)[:20])
if numeric:
    values = [v for _, v in numeric]
    expected = list(range(min(values), max(values) + 1))
    print("min_index:", min(values), "max_index:", max(values))
    print("missing_indices:", sorted(set(expected) - set(values))[:20])
    print("index_count:", len(values), "range_count:", len(expected))
PY

printf '%s\n' '--- crossval.sh mapping and filters ---'
sed -n '170,220p;245,325p;350,390p' scripts/accuracysnes/crossval.sh

printf '%s\n' '--- Mesen skip logic ---'
cat -n scripts/accuracysnes/mesen_crossval.lua | sed -n '1,110p'

printf '%s\n' '--- Ares/status filter implementation ---'
cat -n scripts/accuracysnes/crossval.sh | sed -n '325,385p'
rg -n -C 4 'UNSTABLE_INDEX|ACCURACYSNES_SKIP_INDEX|skip|status' scripts/accuracysnes/ares_host scripts/accuracysnes/crossval.sh

Repository: doublegate/RustySNES

Length of output: 30245


🏁 Script executed:

set -euo pipefail

catalog=tests/roms/AccuracySNES/SOURCE_CATALOG.tsv

printf '%s\n' '--- catalog header and F1.10 row ---'
awk -F'\t' '
  NR <= 5 || $2 == "F1.10" { printf "%d:%s\n", NR, $0 }
' "$catalog"

printf '%s\n' '--- catalog index diagnostics ---'
python3 - "$catalog" <<'PY'
import csv, sys
from collections import Counter

with open(sys.argv[1], newline="") as f:
    rows = list(csv.reader(f, delimiter="\t"))

print("rows:", len(rows))
for line, row in enumerate(rows, 1):
    if len(row) < 2:
        print("short_row", line, len(row), row)

numeric = [(line, int(row[0])) for line, row in enumerate(rows, 1)
           if row and row[0].isdigit()]
print("numeric_rows:", len(numeric))
print("first_numeric:", numeric[:3])
print("last_numeric:", numeric[-3:])

counts = Counter(value for _, value in numeric)
print("duplicate_numeric_indices:",
      sorted((value, count) for value, count in counts.items() if count > 1)[:20])

if numeric:
    values = [value for _, value in numeric]
    expected = set(range(min(values), max(values) + 1))
    print("min_index:", min(values), "max_index:", max(values))
    print("missing_indices:", sorted(expected - set(values))[:20])
    print("index_count:", len(values), "range_count:", len(expected))
PY

printf '%s\n' '--- crossval.sh mapping and filters ---'
sed -n '170,220p;245,325p;350,390p' scripts/accuracysnes/crossval.sh

printf '%s\n' '--- Mesen skip logic ---'
cat -n scripts/accuracysnes/mesen_crossval.lua | sed -n '1,110p'

printf '%s\n' '--- Ares/status filter implementation ---'
cat -n scripts/accuracysnes/crossval.sh | sed -n '325,385p'
rg -n -C 4 'UNSTABLE_INDEX|ACCURACYSNES_SKIP_INDEX|skip|status' \
  scripts/accuracysnes/ares_host scripts/accuracysnes/crossval.sh

Repository: doublegate/RustySNES

Length of output: 30245


🏁 Script executed:

set -euo pipefail

printf '%s\n' '--- catalog generation and index consumers ---'
rg -n -C 5 \
  'SOURCE_CATALOG|catalog|enumerate|index|status' \
  --glob '*.rs' --glob '*.py' --glob '*.sh' --glob '*.lua' --glob '*.cpp' \
  . | sed -n '1,260p'

printf '%s\n' '--- exact lookup and host-index contracts ---'
rg -n -C 8 \
  'UNSTABLE_ROW|UNSTABLE_INDEX|ACCURACYSNES_SKIP_INDEX|for i = 0|status %|STATUS \+|SOURCE_CATALOG.tsv' \
  scripts tests | sed -n '1,260p'

printf '%s\n' '--- malformed lookup behavior model ---'
python3 - <<'PY'
import re

def lua_tonumber(value):
    # Lua tonumber accepts ordinary decimal forms; malformed strings become nil.
    try:
        value = value.strip()
        if not value or not re.fullmatch(r'[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?', value):
            return None
        return float(value)
    except ValueError:
        return None

def awk_number(value):
    # Model the numeric prefix used by awk's string-to-number conversion.
    m = re.match(r'^[ \t]*[+-]?(?:\d+(?:\.\d*)?|\.\d+)', value)
    return float(m.group(0)) if m else 0.0

for value in ["293", "294", "293x", "x293", "", "-1", "+293"]:
    lua = lua_tonumber(value)
    mesen = -1 if lua is None else lua
    ares = awk_number(value)
    print(f"{value!r}: Mesen={mesen:g}, ares={ares:g}")
PY

Repository: doublegate/RustySNES

Length of output: 45951


🏁 Script executed:

set -euo pipefail

printf '%s\n' '--- catalog generation and index consumers ---'
rg -n -C 5 \
  'SOURCE_CATALOG|catalog|enumerate|index|status' \
  --glob '*.rs' --glob '*.py' --glob '*.sh' --glob '*.lua' --glob '*.cpp' \
  . | sed -n '1,260p'

printf '%s\n' '--- exact lookup and host-index contracts ---'
rg -n -C 8 \
  'UNSTABLE_ROW|UNSTABLE_INDEX|ACCURACYSNES_SKIP_INDEX|for i = 0|status %|STATUS \+|SOURCE_CATALOG.tsv' \
  scripts tests | sed -n '1,260p'

printf '%s\n' '--- malformed lookup behavior model ---'
python3 - <<'PY'
import re

def lua_tonumber(value):
    value = value.strip()
    if not re.fullmatch(r'[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?', value):
        return None
    return float(value)

def awk_number(value):
    match = re.match(r'^[ \t]*[+-]?(?:\d+(?:\.\d*)?|\.\d+)', value)
    return float(match.group(0)) if match else 0.0

for value in ["293", "294", "293x", "x293", "", "-1", "+293"]:
    lua = lua_tonumber(value)
    mesen = -1 if lua is None else lua
    ares = awk_number(value)
    print(f"{value!r}: Mesen={mesen:g}, ares={ares:g}")
PY

Repository: doublegate/RustySNES

Length of output: 48424


🏁 Script executed:

set -euo pipefail

printf '%s\n' '--- catalog emitter ---'
cat -n tests/roms/AccuracySNES/gen/src/emit.rs | sed -n '288,345p'

printf '%s\n' '--- test ordering and duplicate-ID checks ---'
rg -n -C 6 \
  'tests\.iter\(\)\.enumerate|enumerate\(\)|assert.*id|duplicate|sort_by|sort_unstable' \
  tests/roms/AccuracySNES/gen/src

printf '%s\n' '--- exact ares awk behavior for malformed skip values ---'
for skip in 293 294 293x x293 -1 +293; do
    result=$(
        printf 'status 0 01\nstatus 293 02\nstatus 294 01\n' |
        awk -v skip="$skip" '
            $1 == "status" && $2 + 0 != skip + 0 &&
            $3 != "ff" && $3 != "00" &&
            $3 ~ /[02468aceACE]$/ { c++ }
            END { print c + 0 }
        '
    )
    printf 'skip=%q ares_count=%s\n' "$skip" "$result"
done

printf '%s\n' '--- runtime guard presence ---'
python3 - <<'PY'
from pathlib import Path
text = Path("scripts/accuracysnes/crossval.sh").read_text()
start = text.index("UNSTABLE_INDEX=")
end = text.index("ARES_HOST=", start)
block = text[start:end]
print(block)
print("has_numeric_guard:", "[[ \"$UNSTABLE_INDEX\" =~" in block)
print("has_duplicate_guard:", "count" in block and "== 1" in block)
print("has_range_guard:", "343" in block or "COUNT" in block)
PY

Repository: doublegate/RustySNES

Length of output: 1929


🏁 Script executed:

set -euo pipefail

printf '%s\n' '--- catalog emitter ---'
cat -n tests/roms/AccuracySNES/gen/src/emit.rs | sed -n '288,345p'

printf '%s\n' '--- test ordering and duplicate-ID checks ---'
rg -n -C 6 \
  'tests\.iter\(\)\.enumerate|enumerate\(\)|assert.*id|duplicate|sort_by|sort_unstable' \
  tests/roms/AccuracySNES/gen/src

printf '%s\n' '--- exact ares awk behavior for malformed skip values ---'
for skip in 293 294 293x x293 -1 +293; do
    result=$(
        printf 'status 0 01\nstatus 293 02\nstatus 294 01\n' |
        awk -v skip="$skip" '
            $1 == "status" && $2 + 0 != skip + 0 &&
            $3 != "ff" && $3 != "00" &&
            $3 ~ /[02468aceACE]$/ { c++ }
            END { print c + 0 }
        '
    )
    printf 'skip=%q ares_count=%s\n' "$skip" "$result"
done

printf '%s\n' '--- runtime guard presence ---'
python3 - <<'PY'
from pathlib import Path

text = Path("scripts/accuracysnes/crossval.sh").read_text()
start = text.index("UNSTABLE_INDEX=")
end = text.index("ARES_HOST=", start)
block = text[start:end]
print(block)
print("has_numeric_guard:", "[[ \"$UNSTABLE_INDEX\" =~" in block)
print("has_duplicate_guard:", "count" in block and "== 1" in block)
print("has_range_guard:", "343" in block or "COUNT" in block)
PY

Repository: doublegate/RustySNES

Length of output: 1929


Reject invalid catalogue indices before exporting ACCURACYSNES_SKIP_INDEX.

The current catalogue is zero-based, contiguous, and unique, but line 303 accepts any non-empty first match. A one-based index skips the wrong status in both hosts. A value such as 293x makes Mesen skip nothing while ares treats it as 293. Require exactly one UNSTABLE_ROW match, a decimal index, and 0 <= index < test_count before filtering either host.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/accuracysnes/crossval.sh` around lines 303 - 304, Update the
UNSTABLE_INDEX lookup in the cross-validation flow to require exactly one
matching UNSTABLE_ROW entry, then validate that the catalogue index is a decimal
integer within 0 <= index < test_count. Reject invalid, ambiguous, one-based, or
values such as “293x” before exporting ACCURACYSNES_SKIP_INDEX or applying
either host’s filtering.

Source: Path instructions

Comment on lines +180 to +215
/// Emit only the UPLOAD half of [`upload_and_run_tagged`]: the transfer and the port-0 clear,
/// stopping short of the wait-for-done loop.
///
/// Split out for the one row that has to do something on the CPU side **while** the SPC program
/// runs — `B2.07`/`B2.08` count frames against the APU's timer, so the cart cannot be sitting in a
/// busy-wait. Everything else should keep calling [`upload_and_run`], which is this plus the wait.
fn upload_only(a: &mut Asm, prog: &Spc) {
let label = format!("apu_prog_{}", next_prog_id());
a.d(&format!("{label}:"));
for line in prog.as_ca65(" ").lines() {
a.d(line);
}
a.l("rep #$30");
a.l("phk");
a.l("plb");
a.c("Point apu_upload at this test's own program image, which lives in another bank.");
a.l(&format!("lda #.loword({label})"));
a.l("sta f:V_APU_SRC");
a.l("sep #$20");
a.l(&format!("lda #^{label}"));
a.l("sta f:V_APU_BANK");
a.l("rep #$30");
a.l(&format!("lda #{}", prog.bytes().len()));
a.l("sta f:V_APU_LEN");
a.l("lda #$0200");
a.l("sta f:V_APU_DEST ; APU RAM $0200: clear of the zero page and the stack");
a.l("lda #$0200");
a.l("sta f:V_APU_ENTRY");
a.l("jsl apu_upload_far");
a.c("Clear the CPU-side port 0 before the program can look at it. The previous test left the");
a.c("release byte there, and a program whose release loop sees it immediately jumps back to");
a.c("the IPL before the cart has read a thing — which reads as a wrong answer, not a race.");
a.l("sep #$20");
a.l("lda #$00");
a.l("sta APUIO0");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

upload_only duplicates upload_and_run_tagged's upload half verbatim.

Lines 186-215 and lines 221-248 are identical, comments included. The two will drift. Express the wait-carrying variant in terms of the upload-only one.

Proposed deduplication
 fn upload_and_run_tagged(a: &mut Asm, prog: &Spc, tag: &str) {
-    // The image goes in the out-of-bank data segment, not inline in the test body: these are
-    // several hundred bytes each and bank $00 is finite. `apu_upload` takes a 24-bit pointer
-    // anyway, so nothing about the upload cares where it lives.
-    let label = format!("apu_prog_{}", next_prog_id());
-    a.d(&format!("{label}:"));
-    for line in prog.as_ca65("    ").lines() {
-        a.d(line);
-    }
-    a.l("rep #$30");
-    ...
-    a.l("sta APUIO0");
+    upload_only(a, prog);
     a.c("Wait for the program's done marker, but not forever: an APU that never boots would");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/roms/AccuracySNES/gen/src/tests/apu.rs` around lines 180 - 215,
Refactor upload_and_run_tagged to reuse upload_only for the shared program
emission, register setup, upload, and APUIO0 clearing instead of duplicating
that upload half. Keep only the wait-for-done loop and its surrounding behavior
in upload_and_run_tagged, while preserving upload_only’s existing semantics.

Comment on lines +8821 to +8832
a.c(
"Count frames. wait_vblank_far returns at vblank start, so this is exactly N frame periods",
);
a.c("of CPU time -- the interval the APU's tick count is being measured against.");
a.l("rep #$30");
a.l(&format!("ldx #{frames}"));
a.label("b2frame");
a.l("phx");
a.l("jsl wait_vblank_far");
a.l("plx");
a.l("dex");
a.l("bne @b2frame");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

The counted interval is 47 to 48 frames, not 48, and the NTSC lower bound sits inside that jitter.

wait_vblank_far returns at the next vblank start. The loop runs it B2_FRAMES times, so the elapsed CPU time is the partial frame remaining when the loop is entered plus 47 whole frames. The APU, however, started accumulating at STARTED, which the cart observes at an arbitrary point inside a frame.

Let f be the fraction of a frame still remaining when @b2go is reached. The measured interval is 48 - f frames. On NTSC the tick total is therefore in (6265, 6398], not ~6398. The asserted lower bound is 6270. A handshake that completes in the last 4% of a frame produces a reading below 6270 and the row fails with nothing wrong in the core. The PAL side has the same shape: (7529, 7689] against a 7535 bound.

The phase of that handshake depends on upload duration and on how much code runs ahead of this test, which is exactly the sensitivity that made F1.10 unstable in this same PR.

Widening the band is the wrong fix; it weakens the ±2% claim the doc comment is built on. Align to a frame boundary first, then start the APU accumulating:

  • call wait_vblank_far once after @b2go,
  • then write a start byte on port 3,
  • and have the SPC drain $FD and begin only on that byte.

The residual error is then one SPC poll-loop period instead of one frame.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/roms/AccuracySNES/gen/src/tests/apu.rs` around lines 8821 - 8832, Align
the measurement start with a vblank boundary instead of beginning accumulation
at the arbitrary `@b2go` phase. In the `@b2go/`@b2frame flow, call wait_vblank_far
once before writing the start byte on port 3, and update the SPC-side startup
logic to drain $FD and begin accumulation only after receiving that byte.
Preserve the intended B2_FRAMES frame interval and ±2% assertion bounds.

Source: Path instructions

Comment on lines +8923 to +8937
// Accumulate T0OUT into a 16-bit sum until the cart says stop. MOV never touches carry, so the
// high byte's `adc #$00` picks up exactly the carry the low byte's add produced.
let poll = prog.here();
prog.mov_a_dp(0xFD) // T0OUT, read-and-clear
.mov_dp_a(0x12)
.mov_a_dp(0x10)
.clrc()
.adc_a_dp(0x12)
.mov_dp_a(0x10)
.mov_a_dp(0x11)
.adc_a_imm(0x00)
.mov_dp_a(0x11)
.mov_a_dp(PORT3)
.cmp_a_imm(STOP);
prog.bne_back(poll);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

The SPC poll loop is unbounded, and two documents state that it is bounded. The emitted program exits its accumulate loop only on the stop byte from port 3. There is no iteration counter. A cart that reaches @b2timeout after the upload leaves the SPC outside the IPL and un-released, which breaks every later APU upload.

  • tests/roms/AccuracySNES/gen/src/tests/apu.rs#L8923-L8937: add the iteration counter the doc comment at lines 8896-8902 describes, so the program falls through to publish and release_to_ipl on its own; alternatively write RELEASE on the @b2timeout path and correct the doc comment.
  • CHANGELOG.md#L72-L75: after the code change, keep the "every wait on both sides is bounded" sentence; if the code is not changed, restrict the sentence to the cart side.
📍 Affects 2 files
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs#L8923-L8937 (this comment)
  • CHANGELOG.md#L72-L75
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/roms/AccuracySNES/gen/src/tests/apu.rs` around lines 8923 - 8937, Bound
the SPC polling loop around the visible poll label and T0OUT accumulation by
adding the iteration counter described in the nearby documentation, while
preserving exit on STOP and allowing fall-through to publish and release_to_ipl.
In CHANGELOG.md lines 72-75, retain the statement that every wait on both sides
is bounded after this code change.

Source: Path instructions

…caught it

`[`B2_FRAMES`]` is a function-local constant, so rustdoc cannot resolve
it -- a plain code span instead. Local `cargo doc --workspace` passed
because CI runs it with `--exclude rustysnes-android`, and my earlier
local run predated this row; running the gate's exact command reproduces
it. ROM byte-identical, checksum still $5B04.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

Adds AccuracySNES test B2.07/B2.08 to measure SNES frame rate against the APU crystal clock over 48 frames, updates test metrics and status documentation, and excludes phase-fragile test F1.10 from Mesen2 cross-validation counts.

Blocking issues

  • tests/roms/AccuracySNES/gen/src/tests/apu.rs:8815-8830 / tests/roms/AccuracySNES/asm/tests_group_a.s:7610-7622: b2_07_drive does not measure 48 full frame periods. When @b2wait detects APUIO1 == STARTED, the CPU is at an arbitrary scanline $L$ mid-frame. The subsequent 48-iteration @b2frame loop invokes jsl wait_vblank_far on its first iteration, which returns at the very next vblank start (only $225 - L$ scanlines elapsed). The CPU measures $47 + \frac{225 - L}{\text{scanlines_per_frame}}$ frames rather than 48 full frames, introducing up to a 1-frame (~133 NTSC / ~160 PAL APU ticks) phase error into the measurement baseline despite the code comment claiming it measures "exactly N frame periods of CPU time".

Suggestions

  • tests/roms/AccuracySNES/gen/src/tests/apu.rs:8815: Synchronize the CPU to vblank (jsl wait_vblank_far) right before entering @b2frame so that the 48-frame count begins at a clean vblank edge instead of truncating the first frame period.
  • scripts/accuracysnes/crossval.sh:302-309 & scripts/accuracysnes/mesen_crossval.lua:76-81: Dynamically masking F1.10 from cross-validation because preceding tests shift CPU/PPU phase alignment hides test suite phase drift; phase-sensitive test groups should re-synchronize PPU phase prior to execution rather than masking cross-validation failure counters.

Nitpicks

  • PR title subject line feat(accuracysnes): B2.07/B2.08 — the frame rate, measured against the APU is 74 characters long, exceeding the 72-character limit defined in the style guide.

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

@coderabbitai coderabbitai Bot 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.

♻️ Duplicate comments (2)
tests/roms/AccuracySNES/gen/src/tests/apu.rs (2)

8792-8858: 🩺 Stability & Availability | 🟠 Major

Release the APU before reporting SKIP.

b2_07_drive uploads an SPC program before its bounded waits complete. If a timeout reaches the tagged SKIP handler while the SPC remains outside the IPL, the next APU test cannot upload reliably. Ensure every timeout path invokes release_to_ipl or sends the release command before reporting SKIP.

This repeats the previous review finding; verify the shared SKIP handler and every timeout branch.

#!/usr/bin/env bash
set -euo pipefail
file='tests/roms/AccuracySNES/gen/src/tests/apu.rs'
sed -n '8792,9010p' "$file"
rg -n -C 5 'apu_timeout_arm_tagged|release_to_ipl|RELEASE|SKIP|timeout|b2_07_drive' "$file"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/roms/AccuracySNES/gen/src/tests/apu.rs` around lines 8792 - 8858,
Update b2_07_drive and the shared tagged SKIP timeout flow so every timeout path
releases the APU to the IPL before reporting SKIP. Ensure both `@b2timeout`
branches invoke release_to_ipl or send the RELEASE command, and verify
apu_timeout_arm_tagged and related timeout handlers do not bypass that release.

8792-8858: 🎯 Functional Correctness | 🟠 Major

Start APU accumulation on a frame boundary.

The changed flow waits for the start marker and then counts B2_FRAMES vblanks. If the SPC starts accumulating before the first counted vblank, the interval is 47–48 frames instead of a fixed 48-frame interval. The NTSC and PAL lower bounds can reject a correct implementation when the start occurs late in a frame. Wait for one vblank before sending the start byte, then start the SPC timer on that byte.

As per path instructions, timing-marginal measurements must use a settled or provably stationary value.

#!/usr/bin/env bash
set -euo pipefail
file='tests/roms/AccuracySNES/gen/src/tests/apu.rs'
sed -n '8792,9010p' "$file"
rg -n -C 4 'B2_FRAMES|wait_vblank_far|APUIO3|START|STOP|T0OUT' "$file"

Also applies to: 8877-8877

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/roms/AccuracySNES/gen/src/tests/apu.rs` around lines 8792 - 8858, Align
the APU accumulation start with a frame boundary in b2_07_drive. After the
readiness handshake, wait for one vblank, then send the start command/byte so
the SPC begins timing at that boundary; count B2_FRAMES vblanks only after that
start. Preserve the existing bounded timeout behavior and stop/result collection
flow.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@tests/roms/AccuracySNES/gen/src/tests/apu.rs`:
- Around line 8792-8858: Update b2_07_drive and the shared tagged SKIP timeout
flow so every timeout path releases the APU to the IPL before reporting SKIP.
Ensure both `@b2timeout` branches invoke release_to_ipl or send the RELEASE
command, and verify apu_timeout_arm_tagged and related timeout handlers do not
bypass that release.
- Around line 8792-8858: Align the APU accumulation start with a frame boundary
in b2_07_drive. After the readiness handshake, wait for one vblank, then send
the start command/byte so the SPC begins timing at that boundary; count
B2_FRAMES vblanks only after that start. Preserve the existing bounded timeout
behavior and stop/result collection flow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d4788ed3-48e1-4e07-a349-b1077fea204d

📥 Commits

Reviewing files that changed from the base of the PR and between 7732bd1 and cb32037.

📒 Files selected for processing (1)
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: accuracysnes
  • GitHub Check: test-light
  • GitHub Check: lint
  • GitHub Check: review
  • GitHub Check: build demo + docs
🧰 Additional context used
📓 Path-based instructions (7)
**/*.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:

  • 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:

  • 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:

  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
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/tests/apu.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:

  • 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/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/tests/apu.rs
🧠 Learnings (3)
📚 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/tests/apu.rs
📚 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
🔇 Additional comments (3)
tests/roms/AccuracySNES/gen/src/tests/apu.rs (3)

180-216: 📐 Maintainability & Code Quality

Remove the duplicated APU upload sequence.

upload_and_run_tagged still emits the upload and APUIO0-clear sequence at Line 221-248. Verify that upload_only does not duplicate this logic. If both bodies contain it, make upload_and_run_tagged call upload_only and retain only its wait, result-copy, and release logic.

This repeats the previous review finding; verify the current function bodies before merging.

#!/usr/bin/env bash
set -euo pipefail
sed -n '180,280p' tests/roms/AccuracySNES/gen/src/tests/apu.rs

28-28: LGTM!

Also applies to: 152-152, 399-405


8860-9009: 🗄️ Data Integrity & Integration

Keep the measurement-slot and $213F logic unchanged. The 35-entry sweep ends at slots 231 and 232, so slots 275 and 276 are unclaimed. measure_frame_height reads $213F only to reset flip-flops before $2137; $4201 is initialized and restored.

			> Likely an incorrect or invalid review comment.

@doublegate
doublegate merged commit ddee9b0 into main Aug 2, 2026
16 checks passed
@doublegate
doublegate deleted the feat/accuracysnes-b2-07-frame-rate branch August 2, 2026 01:11
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