Skip to content

fix(accuracysnes): E3.06 records the counts it compares — ares' failure is a wrap - #307

Merged
doublegate merged 1 commit into
mainfrom
fix/e3-06-records-its-counts
Aug 1, 2026
Merged

fix(accuracysnes): E3.06 records the counts it compares — ares' failure is a wrap#307
doublegate merged 1 commit into
mainfrom
fix/e3-06-records-its-counts

Conversation

@doublegate

@doublegate doublegate commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Continues #305/#306'''s adjudication. E3.06 was one of two genuinely ares-only divergences; it is now measured rather than assumed.

The row had no headroom, structurally

E3.06 asserts timer 2 ticks ~8x timer 0 over one interval, band 8..15. TnOUT is four bits, so the band ends one tick short of its own wrap.

That tightness is not a choice. Timer 0 must tick at least once for the ratio to mean anything; one timer-0 period is eight timer-2 periods; so timer 2 can never read below 8, and the wrap sits only a factor of two above.

Nothing recorded the counts, which is why ares''' failure looked like a rate bug

timer 0 timer 2
RustySNES 1 10
ares 1 0

0 is what 16 reads as. And ares''' own declarations — Timer<128>, Timer<128>, Timer<16> — are an exactly correct 8:1 ratio, which makes the wrap the likelier reading by some margin.

So E3.06 is a weak row, not a settled divergence

It cannot distinguish "timer 2 wrapped" from "timer 2 is not running at 64 kHz". The assertion message now says exactly that and points at the recording slot, instead of asserting a conclusion the instrument cannot support.

I have not widened the band or changed the interval — both would trade one blind spot for another, and the right fix (poll timer 2 and accumulate, so the 4-bit counter stops being the ceiling) is real work that deserves its own change.

Also

The ares host dumps the whole measurement channel rather than a curated list. It exists to arbitrate disagreements, and which slot matters is decided by whichever row turns out to disagree — a curated list would have to be edited every time that changes.

Verification

  • cargo fmt --check, cargo clippy --workspace --exclude rustysnes-android --all-targets -- -D warnings — clean.
  • cargo test -p rustysnes-test-harness --test accuracysnes --test accuracysnes_scenes --features test-roms58 passed, 0 failed.
  • REF_PROJ=$PWD/ref-proj bash scripts/accuracysnes/crossval.shsnes9x: OK (14 known), Mesen2: OK (2 known), 54 scenes match on both, 2 reference(s) agree with the cart.

🤖 Generated with Claude Code

Summary

AccuracySNES E3.06 now records timer 0 and timer 2 counts used by the ratio check. The dossier assertion identifies timer 2 wrapping as unresolved when ares reports 0; a four-bit TnOUT counter can represent 0 after wrapping from 16.

The claim is false if timer 2 does not wrap and ares instead reports 0 because timer 2 is not running at 64 kHz. RustySNES reports counts of 1 and 10; ares reports 1 and 0, so the test cannot distinguish these cases.

The altered dossier assertion is E3.06. The test adds measurement slots 266 and 267 for timer 0 and timer 2. The coverage denominator does not move because these slots record diagnostic values and do not add dossier assertions.

The ares host now reports the complete measurement channel. The timer interval and accepted comparison band remain unchanged.

…e is a wrap

The row asserts timer 2 ticks ~8x timer 0 over one interval, with a band of
8..15 -- and TnOUT is FOUR BITS, so the band ends one tick short of its own
wrap. That tightness is structural rather than a choice: timer 0 must tick at
least once for the ratio to mean anything, one timer-0 period IS eight timer-2
periods, so timer 2 can never read below 8 and the wrap is only a factor of
two above.

Nothing recorded the counts, so a failure said only "not 8x" -- which is why
ares' failure looked like a rate divergence. With the two slots added:
RustySNES reads timer 2 = 10, ares reads 0, and 0 is what 16 reads as. ares'
own Timer<128>/Timer<128>/Timer<16> declarations are an exactly correct 8:1
ratio, which makes the wrap the likelier reading.

So E3.06 is a WEAK ROW, not a settled divergence: it cannot distinguish
"timer 2 wrapped" from "timer 2 is not at 64 kHz". The assertion message now
says so and points at the slot, rather than asserting a conclusion the
instrument cannot support.

The ares host also dumps the whole measurement channel now, not a curated
list: it exists to arbitrate disagreements, and which slot matters is decided
by whichever row turns out to disagree.

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

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

E3.06 now records timer 0 and timer 2 measurements, accounts for four-bit timer-counter wrap, and reports measurement slots. The ares host dumps nonzero measurement-channel values. Documentation records the differing RustySNES and ares readings.

Changes

E3.06 timer measurement diagnostics

Layer / File(s) Summary
Timer measurement recording and assertions
tests/roms/AccuracySNES/gen/src/tests/apu.rs, crates/rustysnes-test-harness/tests/accuracysnes.rs
E3.06 records timer counts, validates timer 0, documents timer 2 wraparound, and reports measurements in slots 266 and 267.
ares measurement output and documentation
scripts/accuracysnes/ares_host/ares_host.cpp, scripts/accuracysnes/ares_host/README.md, CHANGELOG.md
The ares host prints nonzero measurement-channel values. The documentation records the observed counts and the unresolved wrap interpretation.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: copilot

🚥 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 E3.06, but it is not imperative and incorrectly states that ares failure is confirmed as a wrap. Use an imperative, unresolved subject such as fix(accuracysnes): record E3.06 timer counts and diagnose possible ares wrap.
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 diff against origin/main adds a CHANGELOG.md Unreleased entry and changes AccuracySNES assembly plus both cartridge binaries.
Docs-As-Spec ✅ Passed Against origin/main, no chip implementation file changed; the sole crate diff only adds AccuracySNES measurement slots to test-report output, so emulator behavior and chip specifications are unchan...
Accuracysnes Bookkeeping ✅ Passed The PR edits existing test e3_06 only; no test or scene is added or removed, and E3.06 is already mapped in dossier.rs::MAP.
No Panic On Untrusted Input ✅ Passed The diff adds no new .unwrap(), .expect(), or panic!() calls. The new ares input read uses a fixed 512-slot bounded loop; existing test-only expects are unchanged.
Safety Comment On New Unsafe ✅ Passed The parent-to-HEAD diff adds no unsafe blocks or unsafe fn declarations; changed Rust files contain no unsafe declarations, so no new // SAFETY: comment is required.

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

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

This PR updates the E3.06 test in AccuracySNES to record raw timer 0 and timer 2 tick counts into WRAM measurement slots 266 and 267, updating the test generator, assembly, prebuilt SFC ROMs, harness slot definitions, and the ares_host runner.

Blocking issues

  • docs/accuracysnes-coverage.md:L35: Erroneous coverage table edit. The diff modifies row C11 (changing rendered scene coverage from 7 to 8 and removing C11.03) and increments the overall rendered scene tally from 53 to 54. This PR modifies APU test E3.06 (Group E); updating Group C metrics in the coverage spec is incorrect and corrupts the coverage audit trail.

Suggestions

  • tests/roms/AccuracySNES/gen/src/tests/apu.rs:L8090-L8108 (and tests_group_a.s:L22976-L22996): Redundant WRAM reads. lda f:$7E0101 / and #$00FF is executed to record slot 266 and then re-executed immediately below for assert_a16_range. The generator should reuse the value in the register rather than emitting duplicate loads.
  • scripts/accuracysnes/ares_host/ares_host.cpp:L178-L182: Unfiltered measurement dumping. Unconditionally printing any non-zero 16-bit word in 0xE200..0xE5FF will emit false positive measurement channels if WRAM is not strictly zeroed prior to execution. Consider checking a explicit valid count or bounds mask.

Nitpicks

  • tests/roms/AccuracySNES/ERROR_CODES.md:L2644: Markdown table row string is excessively long due to the inline note. Break the note into a separate paragraph below the error code table.

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

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

This PR improves AccuracySNES’s ability to adjudicate emulator divergences by turning E3.06 into an explicitly measured row: it records the raw timer counts it compares, and updates the ares host/harness/docs to surface those measurements for cross-emulator analysis.

Changes:

  • Record E3.06 timer-0 and timer-2 tick counts into measurement slots 266/267 before asserting.
  • Extend the ares host and RustySNES test harness reporting to include the new measurement slots.
  • Update generated error-code text and documentation/changelog to reflect the new “measured, not assumed” interpretation.

Reviewed changes

Copilot reviewed 8 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/roms/AccuracySNES/gen/src/tests/apu.rs Adds measurement recording for E3.06 before assertions and updates the failure message to point at the recorded slots.
tests/roms/AccuracySNES/ERROR_CODES.md Updates the generated failure description for E3.06 to note the wrap ambiguity and reference slot 267.
tests/roms/AccuracySNES/asm/tests_group_a.s Regenerated ASM reflecting the new measurement writes for slots 266/267 and updated comments/messages.
scripts/accuracysnes/ares_host/README.md Documents that E3.06 is likely a wrap-related weakness and that the row now records raw counts.
scripts/accuracysnes/ares_host/ares_host.cpp Adds emission of measurement-channel values from WRAM for cross-validation.
docs/accuracysnes-coverage.md Updates generated coverage totals (including rendered-scene coverage count).
crates/rustysnes-test-harness/tests/accuracysnes.rs Adds labels for the new Group E batch measurement slots (266/267) to the harness report.
CHANGELOG.md Adds an Unreleased entry describing the new E3.06 measurements and the ares-host measurement dump.

Comment on lines +175 to +182
// The measurement channel, which is where a row puts the numbers a one-byte verdict cannot carry.
// Emitted for every slot rather than a curated list: this host exists to arbitrate disagreements,
// and which slot matters is decided by whichever row turns out to disagree.
const u32 MEAS = 0xE200;
for(u32 i = 0; i < 512; i++) {
u32 v = wram[MEAS + i * 2] | (wram[MEAS + i * 2 + 1] << 8);
if(v) printf("meas %u %u\n", i, v);
}

@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: 2

🤖 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 `@scripts/accuracysnes/ares_host/ares_host.cpp`:
- Around line 175-182: Update the measurement loop in the ares host output path
to print a record for every slot, including measurements whose value is zero.
Remove the conditional suppression around the printf in the loop iterating over
MEAS so wrapped timer values remain distinguishable from unwritten slots.

In `@tests/roms/AccuracySNES/gen/src/tests/apu.rs`:
- Around line 8106-8130: Update the APU timer test around the timer 0 and timer
2 assertions to enforce the intended 8:1 relationship under modulo-16 reads,
either by requiring timer 0 to equal 1 before applying the 8–15 timer 2 range or
by comparing timer 2 against timer 0 modulo 16; ensure the test distinguishes
the 64 kHz behavior from the 32 kHz alternative. In
scripts/accuracysnes/ares_host/README.md lines 56-63, remove the claim that
timer 2 cannot read below 8 and document the modulo-16 ambiguity. In
CHANGELOG.md lines 14-25, revise the Unreleased entry to describe the corrected
assertion 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: f4253512-f4ad-493d-8acf-f5e61632c284

📥 Commits

Reviewing files that changed from the base of the PR and between 1480f23 and 836bd19.

⛔ Files ignored due to path filters (5)
  • 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/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 (5)
  • CHANGELOG.md
  • crates/rustysnes-test-harness/tests/accuracysnes.rs
  • scripts/accuracysnes/ares_host/README.md
  • scripts/accuracysnes/ares_host/ares_host.cpp
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: lint
  • GitHub Check: accuracysnes
  • GitHub Check: test-light
  • GitHub Check: copilot-pull-request-reviewer
  • GitHub Check: build demo + docs
🧰 Additional context used
📓 Path-based instructions (13)
**/*

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

  • scripts/accuracysnes/ares_host/ares_host.cpp
  • crates/rustysnes-test-harness/tests/accuracysnes.rs
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
  • CHANGELOG.md
  • scripts/accuracysnes/ares_host/README.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/ares_host/ares_host.cpp
  • scripts/accuracysnes/ares_host/README.md
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/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
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs
  • CHANGELOG.md
  • scripts/accuracysnes/ares_host/README.md
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/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/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
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
**/*.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:

  • CHANGELOG.md
  • scripts/accuracysnes/ares_host/README.md
🧠 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
🪛 Clang (14.0.6)
scripts/accuracysnes/ares_host/ares_host.cpp

[warning] 178-178: variable 'MEAS' is not initialized

(cppcoreguidelines-init-variables)


[warning] 179-179: variable 'i' is not initialized

(cppcoreguidelines-init-variables)


[warning] 180-180: variable 'v' is not initialized

(cppcoreguidelines-init-variables)


[warning] 180-180: variable name 'v' is too short, expected at least 3 characters

(readability-identifier-length)


[warning] 181-181: statement should be inside braces

(readability-braces-around-statements)

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

8090-8130: 🎯 Functional Correctness

Verify that the generated AccuracySNES cartridge was rebuilt.

This file is under tests/roms/AccuracySNES/gen/. A stale generated cartridge can execute the previous E3.06 program while the source review appears correct. Confirm that the rebuild used the generator source and did not hand-edit generated assembly.

As per coding guidelines, rebuild AccuracySNES after every change to gen/ or asm/, and never hand-edit generated assembly.

Source: Coding guidelines


8098-8104: 🗄️ Data Integrity & Integration

No measurement-slot collision exists for slots 266 and 267. The generated writers claim these slots only in E3.06, and dossier::check_slots validates all literal and computed record slots.

			> Likely an incorrect or invalid review comment.
crates/rustysnes-test-harness/tests/accuracysnes.rs (1)

599-617: LGTM!

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

43-43: LGTM!

Comment on lines +175 to +182
// The measurement channel, which is where a row puts the numbers a one-byte verdict cannot carry.
// Emitted for every slot rather than a curated list: this host exists to arbitrate disagreements,
// and which slot matters is decided by whichever row turns out to disagree.
const u32 MEAS = 0xE200;
for(u32 i = 0; i < 512; i++) {
u32 v = wram[MEAS + i * 2] | (wram[MEAS + i * 2 + 1] << 8);
if(v) printf("meas %u %u\n", i, v);
}

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

Do not suppress zero-valued measurements.

When timer 2 wraps from 15 to 0, line 181 drops slot 267. The output then cannot distinguish a valid wrapped zero from an unwritten slot. This prevents the host from establishing the ares result described by the README and changelog.

Print every slot, or emit explicit zero records for all owned slots.

Proposed fix
-    if(v) printf("meas %u %u\n", i, v);
+    printf("meas %u %u\n", i, v);

As per path instructions, the cross-validation harness must not let omitted measurement output masquerade as a valid result.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// The measurement channel, which is where a row puts the numbers a one-byte verdict cannot carry.
// Emitted for every slot rather than a curated list: this host exists to arbitrate disagreements,
// and which slot matters is decided by whichever row turns out to disagree.
const u32 MEAS = 0xE200;
for(u32 i = 0; i < 512; i++) {
u32 v = wram[MEAS + i * 2] | (wram[MEAS + i * 2 + 1] << 8);
if(v) printf("meas %u %u\n", i, v);
}
// The measurement channel, which is where a row puts the numbers a one-byte verdict cannot carry.
// Emitted for every slot rather than a curated list: this host exists to arbitrate disagreements,
// and which slot matters is decided by whichever row turns out to disagree.
const u32 MEAS = 0xE200;
for(u32 i = 0; i < 512; i++) {
u32 v = wram[MEAS + i * 2] | (wram[MEAS + i * 2 + 1] << 8);
printf("meas %u %u\n", i, v);
}
🧰 Tools
🪛 Clang (14.0.6)

[warning] 178-178: variable 'MEAS' is not initialized

(cppcoreguidelines-init-variables)


[warning] 179-179: variable 'i' is not initialized

(cppcoreguidelines-init-variables)


[warning] 180-180: variable 'v' is not initialized

(cppcoreguidelines-init-variables)


[warning] 180-180: variable name 'v' is too short, expected at least 3 characters

(readability-identifier-length)


[warning] 181-181: statement should be inside braces

(readability-braces-around-statements)

🤖 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/ares_host/ares_host.cpp` around lines 175 - 182, Update
the measurement loop in the ares host output path to print a record for every
slot, including measurements whose value is zero. Remove the conditional
suppression around the printf in the loop iterating over MEAS so wrapped timer
values remain distinguishable from unwritten slots.

Source: Path instructions

Comment on lines +8106 to +8130
a.c("Timer 0 first: one tick, maybe two. Zero would make the ratio below unmeasurable.");
a.l("lda f:$7E0101");
a.l("and #$00FF");
a.assert_a16_range(
1,
3,
"timer 0 did not tick once over this interval, or ticked more than three times — either \
way the interval is not the one this test needs and the ratio below means nothing",
);
a.c("Timer 2 over the SAME interval: eight times the rate, so eight or more ticks.");
a.c(
"Timer 2 over the SAME interval: eight times the rate, so eight or more ticks. The band is",
);
a.c("8..15 and `TnOUT` wraps at 16, which leaves NO headroom above -- and that is structural,");
a.c("not a choice: timer 0 must tick at least once for the ratio to mean anything, and one");
a.c("timer-0 period IS eight timer-2 periods, so timer 2 can never be below 8 and the wrap is");
a.c("only a factor of two away. Measured: RustySNES 10, ares 0 -- and 0 is what 16 reads as.");
a.l("lda f:$7E0102");
a.l("and #$00FF");
a.assert_a16_range(
8,
15,
"timer 2 did not count roughly eight times what timer 0 did over the same interval, so it \
is not running from the 64 kHz stage — a core reading $01 here runs every timer at 8 kHz",
is not running from the 64 kHz stage — a core reading $01 here runs every timer at 8 kHz. \
NOTE: this row cannot distinguish that from timer 2 having WRAPPED past 16, so read slot \
267 before concluding anything; ares fails here with a recorded 0",

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

Make the E3.06 assertion and documentation match the modulo-16 timer contract.

The test accepts timer 0 values from 1 through 3 but checks timer 2 against 8 through 15. If timer 0 reads 2, a 32 kHz timer 2 can produce 8 and pass, while a correct 64 kHz timer 2 produces 16 and reads as 0. The current implementation can accept the wrong rate and reject the correct rate while claiming to validate an 8:1 ratio.

  • tests/roms/AccuracySNES/gen/src/tests/apu.rs#L8106-L8130: compare timer 2 with timer 0 modulo 16, or require timer 0 to equal 1 before applying the 8–15 band.
  • scripts/accuracysnes/ares_host/README.md#L56-L63: remove the claim that timer 2 cannot read below 8 and document the actual ambiguity.
  • CHANGELOG.md#L14-L25: update the Unreleased note to match the corrected assertion semantics.

As per path instructions, an AccuracySNES test must distinguish the named hardware behavior from alternatives.

📍 Affects 3 files
  • tests/roms/AccuracySNES/gen/src/tests/apu.rs#L8106-L8130 (this comment)
  • scripts/accuracysnes/ares_host/README.md#L56-L63
  • CHANGELOG.md#L14-L25
🤖 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 8106 - 8130,
Update the APU timer test around the timer 0 and timer 2 assertions to enforce
the intended 8:1 relationship under modulo-16 reads, either by requiring timer 0
to equal 1 before applying the 8–15 timer 2 range or by comparing timer 2
against timer 0 modulo 16; ensure the test distinguishes the 64 kHz behavior
from the 32 kHz alternative. In scripts/accuracysnes/ares_host/README.md lines
56-63, remove the claim that timer 2 cannot read below 8 and document the
modulo-16 ambiguity. In CHANGELOG.md lines 14-25, revise the Unreleased entry to
describe the corrected assertion semantics.

Source: Path instructions

@doublegate
doublegate merged commit 122ef84 into main Aug 1, 2026
17 checks passed
@doublegate
doublegate deleted the fix/e3-06-records-its-counts branch August 1, 2026 14:08
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