Skip to content

test(accuracysnes): C7.06 — Time Over reads set by V = YLOC + 1 - #286

Merged
doublegate merged 3 commits into
mainfrom
feat/accuracysnes-c7-06
Jul 31, 2026
Merged

test(accuracysnes): C7.06 — Time Over reads set by V = YLOC + 1#286
doublegate merged 3 commits into
mainfrom
feat/accuracysnes-c7-06

Conversation

@doublegate

@doublegate doublegate commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Stacked on #285 (C7.05). C7 on-cart coverage 7 → 8 of 16.

8x8 sprites can never reach Time Over

The budget is 34 tiles per line, but range evaluation stops at the 33rd in-range sprite — so 8x8
sprites cap out at 32 tiles, under the limit, permanently. #285's forty 8x8 sprites set Range Over
and leave Time Over clear, and scripts/probes/eval-line-213e reports the same for the same reason.
Anything reaching for Time Over with small sprites is measuring nothing.

Twenty 16x16 sprites give 40 tiles while staying under the 32-sprite range limit. That separation
is itself an assertion: Time Over must set while Range Over stays clear, so a core that raises
the two together, or raises whichever it computes first, fails.

The bracket runs across the line boundary

Unlike #285 there is no index to sweep — the asserted position is fixed. So:

phase sampled on Time Over Range Over
A V = 100 (the eval line) clear clear
B V = 101 (YLOC + 1) set clear

Phase B samples on line 101 rather than late on line 100 on purpose. RustySNES raises the flag at
HBLANK_START_DOT of the eval line, which is earlier than the dossier requires; asserting that
exact dot would fail a core that raises it at (101, 0) exactly, which the assertion permits.
Sampling on 101 accepts both and still rejects anything later — the test asserts what the row says,
not what this emulator happens to do.

The control, and the injection matrix

Phase A alone cannot tell "not yet" from "never"; phase B alone cannot tell "correct" from "always
set". A third run keeps the same 20 sprites at 8x8 (20 tiles, under budget) and requires Time
Over clear at phase B's sampling point.

injection fails
flag Time Over on any sprite in range code 4 — the low-tile control
never set Time Over code 2 — phase B
set it at dot 0 code 1 — phase A

Every assertion is load-bearing; none is reachable by another's failure.

Cross-validation

snes9x fails this row, recorded as an expected divergence (SNES9X_KNOWN_FAILURES 13 → 14): it
already reads Time Over set on V = 100, flagging the overflow a line early because it evaluates and
paints in one pass — the same scanline-granular model that fails #285, seen from the other side. The
20-sprite/8x8 control passes on snes9x, so it is the position that is wrong there, not the budget.

Mesen2 could not arbitrate; its headless runner times out in this environment, which predates this
work.

cargo run -p accuracysnes-gen reproduces the committed ROM; battery 100% on-cart at 335 tests; fmt
and clippy --workspace -D warnings clean.

🤖 Generated with Claude Code

Adds AccuracySNES dossier assertion C7.06 for sprite Time Over timing at V = YLOC + 1.

The test claims that 20 16×16 sprites set Time Over at V = 101, while $213E remains clear at V = 100 and Range Over remains clear. A 20-sprite 8×8 control must not set Time Over. The claim is false if hardware sets the flag early, fails to set it on the following line, sets Range Over, or flags the 8×8 control.

C7 coverage increases from 7/16 to 8/16. The change records snes9x as an expected failure because it flags Time Over early. It adds injection cases for early, missing, and incorrect behavior.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@doublegate, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 780e8a87-cdac-46bb-b9a2-f90cf9a8fd85

📥 Commits

Reviewing files that changed from the base of the PR and between d52de7c and 88619d2.

⛔ Files ignored due to path filters (4)
  • 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 (3)
  • CHANGELOG.md
  • docs/accuracysnes-plan.md
  • tests/roms/AccuracySNES/gen/src/tests/ppu.rs

Walkthrough

Added AccuracySNES C7.06 coverage for PPU Time Over timing. The test compares 16x16 and 8x8 sprite layouts at the line boundary, registers the assertion, and documents the expected snes9x divergence.

Changes

AccuracySNES C7.06

Layer / File(s) Summary
C7.06 sprite-budget timing test
tests/roms/AccuracySNES/gen/src/tests/ppu.rs
Adds IRQ-based $213E sampling across the sprite evaluation line. The test verifies Time Over with 20 16x16 sprites, keeps Range Over clear, and uses 20 8x8 sprites as a control.
Test and dossier registration
tests/roms/AccuracySNES/gen/src/tests/ppu.rs, tests/roms/AccuracySNES/gen/src/dossier.rs
Adds c7_06() to the Group C menu and maps it to dossier assertion C7.06.
Coverage and emulator divergence records
CHANGELOG.md, docs/accuracysnes-plan.md, scripts/accuracysnes/crossval.sh
Marks C7.06 as landed, documents the timing controls, and records snes9x early Time Over flagging as a known failure.

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 9 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Accuracysnes Bookkeeping ⚠️ Warning C7.06 is mapped and all required generated artifacts are in the diff, but plan counts stay at 344/443 and 291 on-cart while coverage increases by one. Update docs/accuracysnes-plan.md hand-maintained counts by one, from 344 to 345 total and 291 to 292 on-cart.
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title uses a valid Conventional Commits type and scope and clearly describes the C7.06 test change.
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 PR diff against origin/main adds a 24-line CHANGELOG.md entry for AccuracySNES C7.06 and its cartridge coverage change.
Docs-As-Spec ✅ Passed The full diff against origin/main changes no files under crates/rustysnes-*; it adds the AccuracySNES C7.06 test, documentation, scripts, and generated ROM artifacts instead.
No Panic On Untrusted Input ✅ Passed The diff adds no new .unwrap(), .expect(), or panic!(). C7.06 uses locally constructed constants and existing Asm APIs; existing dossier panics are unchanged.
Safety Comment On New Unsafe ✅ Passed The commit adds no unsafe { ... } block or unsafe fn; the added Rust diff contains zero unsafe tokens, so no SAFETY comment is required.

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

@doublegate
doublegate changed the base branch from feat/accuracysnes-c7-05 to main July 31, 2026 20:28
C7 on-cart coverage 7 -> 8 of 16.

8x8 sprites can NEVER reach Time Over. The budget is 34 tiles per line,
but range evaluation stops at the 33rd in-range sprite, so 8x8 caps at 32
-- under the limit, permanently. C7.05's forty 8x8 sprites set Range Over
and leave Time Over clear, and the eval-line probe reports the same for
the same reason.

Twenty 16x16 sprites give 40 tiles while staying under the 32-sprite
range limit. That separation is itself an assertion: Time Over must set
while Range Over stays clear, so a core raising the two together fails.

The bracket runs across the line boundary rather than within a line,
because the asserted position is fixed and there is no index to sweep:
clear on the eval line V = 100, set on V = 101. Phase B samples on line
101 deliberately -- RustySNES raises the flag at HBLANK_START_DOT of the
eval line, EARLIER than the assertion requires, and pinning that exact
dot would fail a core raising it at (101, 0) exactly, which the assertion
permits.

A low-tile control (the same 20 sprites at 8x8, 20 tiles) must read clear
at phase B's sampling point; without it "set" would only have meant
"sprites are present". Four injections, each failing its own code:
flagging on any sprite fails code 4 (the control), never setting fails
code 2, setting at dot 0 fails code 1.

snes9x fails it and is recorded as an expected divergence
(SNES9X_KNOWN_FAILURES 13 -> 14): it already reads Time Over set on
V = 100, flagging the overflow a line early because it evaluates and
paints in one pass. The control passes there, so it is the position that
is wrong, not the budget.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@doublegate
doublegate force-pushed the feat/accuracysnes-c7-06 branch from c034f1d to d52de7c Compare July 31, 2026 20:28

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

🤖 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`:
- Line 14: Update the AccuracySNES C7.06 changelog entry to remove the
unsupported “H = 0” claim, while retaining the measured V = OBJ.YLOC + 1
behavior and the IRQ service-point context. Do not present (101, 0) as the
asserted timing position.

In `@docs/accuracysnes-plan.md`:
- Around line 559-560: Complete the C7.06 injection matrix by updating
docs/accuracysnes-plan.md lines 559-560 to state that raising Range Over with
Time Over fails code 3, and add the same code-3 injection to CHANGELOG.md lines
29-32.

In `@scripts/accuracysnes/crossval.sh`:
- Around line 159-166: Update the C7.06 known-failure comment adjacent to
SNES9X_KNOWN_FAILURES to include exact URLs and section or page references for
nocash fullsnes and the SNESdev Wiki covering the 34-tile budget and fetch-phase
timing. Preserve the existing failure description and ensure the citations
clearly support the documented timing divergence.
🪄 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: 01c099c3-1669-434d-94fc-ab77de915128

📥 Commits

Reviewing files that changed from the base of the PR and between 1b0020f and d52de7c.

⛔ 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 (5)
  • CHANGELOG.md
  • docs/accuracysnes-plan.md
  • scripts/accuracysnes/crossval.sh
  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • tests/roms/AccuracySNES/gen/src/tests/ppu.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: test-light
  • GitHub Check: accuracysnes
  • GitHub Check: lint
  • 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/crossval.sh
  • tests/roms/AccuracySNES/gen/src/dossier.rs
  • docs/accuracysnes-plan.md
  • CHANGELOG.md
  • tests/roms/AccuracySNES/gen/src/tests/ppu.rs
scripts/accuracysnes/**

⚙️ CodeRabbit configuration file

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

Files:

  • scripts/accuracysnes/crossval.sh
**/*.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/dossier.rs
  • tests/roms/AccuracySNES/gen/src/tests/ppu.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/dossier.rs
  • docs/accuracysnes-plan.md
  • CHANGELOG.md
  • tests/roms/AccuracySNES/gen/src/tests/ppu.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/ppu.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/dossier.rs
  • tests/roms/AccuracySNES/gen/src/tests/ppu.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/ppu.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/ppu.rs
docs/**/*.md

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

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

New subsystems must add documentation under docs/.

Files:

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

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

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

Files:

  • docs/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/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/accuracysnes-plan.md
  • CHANGELOG.md
CHANGELOG.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

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

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

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • tests/roms/AccuracySNES/gen/src/tests/ppu.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/ppu.rs
🔇 Additional comments (4)
docs/accuracysnes-plan.md (1)

472-472: LGTM!

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

74-74: LGTM!

Also applies to: 2564-2607, 2609-2638


2444-2562: 🎯 Functional Correctness

No change required. HBLANK_START_DOT is 274, after the ~93-dot sample point, so Phase A remains clear as documented.

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

215-215: LGTM!

Comment thread CHANGELOG.md

### Added

- **AccuracySNES: `C7.06` — Time Over reads set by `V = OBJ.YLOC + 1, H = 0`.** `C7` on-cart

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 | 🟡 Minor | ⚡ Quick win

Remove the unsupported H = 0 precision.

The C7.06 test samples $213E at an IRQ service point on V = 101. It does not assert that Time Over becomes set at H = 0. The plan explicitly treats (101, 0) as one permitted implementation, not as the measured position.

Proposed wording
-- **AccuracySNES: `C7.06` — Time Over reads set by `V = OBJ.YLOC + 1, H = 0`.**
+- **AccuracySNES: `C7.06` — Time Over reads set by `V = OBJ.YLOC + 1`.**
📝 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
- **AccuracySNES: `C7.06` — Time Over reads set by `V = OBJ.YLOC + 1, H = 0`.** `C7` on-cart
- **AccuracySNES: `C7.06` — Time Over reads set by `V = OBJ.YLOC + 1`.**
🤖 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` at line 14, Update the AccuracySNES C7.06 changelog entry to
remove the unsupported “H = 0” claim, while retaining the measured V = OBJ.YLOC
+ 1 behavior and the IRQ service-point context. Do not present (101, 0) as the
asserted timing position.

Source: Path instructions

Comment thread docs/accuracysnes-plan.md Outdated
Comment on lines +559 to +560
line passes both A and B. Four injections, each failing its own code — flag on any sprite fails
**code 4** (the control), never setting fails **code 2**, and setting at dot 0 fails **code 1**.

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 | 🟡 Minor | ⚡ Quick win

Keep the C7.06 injection matrix complete.

Both documents claim four injections but omit the Range Over independence failure.

  • docs/accuracysnes-plan.md#L559-L560: state that raising Range Over with Time Over fails code 3.
  • CHANGELOG.md#L29-L32: add the same code-3 injection to the release note.
📍 Affects 2 files
  • docs/accuracysnes-plan.md#L559-L560 (this comment)
  • CHANGELOG.md#L29-L32
🤖 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 `@docs/accuracysnes-plan.md` around lines 559 - 560, Complete the C7.06
injection matrix by updating docs/accuracysnes-plan.md lines 559-560 to state
that raising Range Over with Time Over fails code 3, and add the same code-3
injection to CHANGELOG.md lines 29-32.

Source: Path instructions

Comment on lines +159 to +166
# snes9x, +1 test (C7.06 "TimeOver by YLOC+1", fails phase A with code 1): the same scanline-granular
# sprite model as C7.05, seen from the other side. The row requires Time Over to be clear while the
# sprites' OWN line is being evaluated (V = 100) and set once the line they paint on has begun
# (V = 101 = YLOC + 1); snes9x already reads it set on V = 100, i.e. it flags the tile-budget
# overflow a line early because it evaluates and paints in the same pass. The 20-sprite/8x8 control
# passes there, so this is the position being wrong, not the budget. Documented by nocash fullsnes
# and the SNESdev Wiki (34-tile budget, raised by the fetch phase). Region-independent.
SNES9X_KNOWN_FAILURES=14

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

Add a traceable source citation.

The known failure is recorded correctly, but the comment only names “fullsnes” and “the SNESdev Wiki.” Add the exact URL and section or page for the 34-tile budget and fetch-phase timing. This lets reviewers distinguish a documented reference divergence from an accidental expected failure.

🤖 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 159 - 166, Update the C7.06
known-failure comment adjacent to SNES9X_KNOWN_FAILURES to include exact URLs
and section or page references for nocash fullsnes and the SNESdev Wiki covering
the 34-tile budget and fetch-phase timing. Preserve the existing failure
description and ensure the citations clearly support the documented timing
divergence.

Source: Path instructions

…7/C7.12

C7.12 is a scene needing known-field publication (v1.29.0, shared with
C9.03/C9.06). C7.07 is reachable as a test but would fail: RustySNES
skips a fully-offscreen sprite before either counter, so no false Time
Over can arise, and the row needs a PPU behaviour change first.

That change should not be made on the available evidence -- a single
errata line, with snes9x the only cross-check, and snes9x is already
scanline-granular here and fails both C7.05 and C7.06 for that reason.
The $F8/$F9 retraction is what this caution exists for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Raised in review. With the poison $FF still in the slot, $FF satisfies
or violates the flag tests by accident: phase A reads bit 7 set and
blames 'Time Over already set', phase B passes bit 7 and then blames
'Range Over set alongside'. Both name a cause that did not happen, and
ERROR_CODES.md is meant to be the complete account of failure bytes.

Each phase now asserts the sampled byte is not the poison first, with its
own code. Verified by suppressing the arm ($4200 = $00): the row now
fails code 1, 'the H/V IRQ never fired', instead of a flag assertion.

Codes shifted, so the injection matrix was re-run and the docs corrected:
flag-on-any-sprite now fails code 7, never-setting code 5, set-at-dot-0
code 2; 1/3/4 are the sentinels.

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

Copy link
Copy Markdown
Owner Author

Review adjudication

No blocking issues. The second suggestion was right and is applied; the first and the nitpicks are
declined with evidence.

Applied: the poison value produced misattributed failure codes

Real, and it matters more here than the "suggestion" label implies. With $FF still in the slot, it
satisfies or violates the flag tests by accident: phase A reads bit 7 set and blames "Time Over
already set"
, phase B passes bit 7 and then blames "Range Over set alongside Time Over". Both name
a cause that did not happen — and ERROR_CODES.md exists to be the complete account of failure
bytes, so a code that fingers the wrong mechanism is worse than no code.

Each phase now asserts the sampled byte is not the poison first, with its own code. Verified by
suppressing the arm ($4200 = $00): the row fails code 1, "the H/V IRQ never fired", instead of
a flag assertion.

That shifted the other codes, so the injection matrix was re-run rather than assumed — the docs and
CHANGELOG now carry the true mapping:

injection was now
flag Time Over on any sprite (the control) code 4 code 7
never set Time Over code 2 code 5
set it at dot 0 code 1 code 2

Codes 1, 3 and 4 are the three per-phase sentinels.

Declined: "the checked-in assembly does not match the generator"

Not reproduced. The finding compares a line in tests_group_a.s against a line in ppu.rs — two
different files with unrelated numbering. tests_group_a.s is generated output, and CI's
"Assert the committed artifacts are byte-identical" step re-runs accuracysnes-gen and diffs the
result; it passed on the reviewed commit, which is a stronger check than a line comparison. The
duplicate .a8 is the DSL emitting its own width directive alongside the explicit one — cosmetic in
generated output, and not a mismatch.

Declined: the two nitpicks

sep #$20 / .a8 before lda #$8F, and cmp #$00 after and, are both emitted by the shared
Asm helpers (assert_a8 always emits the compare). Special-casing them would mean the helper
sometimes emits a compare and sometimes not, depending on the preceding instruction — which trades a
redundant two-cycle instruction for a helper whose output a reader can no longer predict. In a
generator whose output is committed and diffed byte-for-byte, predictable emission is worth more.

@github-actions

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

This PR adds the C7.06 on-cart test ROM to AccuracySNES to verify that the PPU Time Over flag sets by V = OBJ.YLOC + 1, H = 0 when 16x16 sprites exceed the 34-tile scanline budget, updating coverage documentation and marking snes9x as an expected divergence.

Blocking issues

  • Incorrect sentinel assertion logic in c7_06() generator (tests/roms/AccuracySNES/gen/src/tests/ppu.rs, lines 2533–2560)
    Phase B calls assert_handler_ran(&mut a) twice sequentially, generating duplicate checks for poison value $FF in tests_group_a.s that make error code 4 ($08, @fail4) unreachable. Meanwhile, Phase C (the 8x8 control phase) omits assert_handler_ran entirely. If Phase C's IRQ fails to fire, the uncleared poison byte $FF ($FF & $80 != 0) will falsely trigger error code 7 ($0E, @fail7 — "Time Over set with only 20 sprite-tiles due") instead of reporting sentinel failure code 4 ($08), directly violating the diagnostic sentinel contract described in ERROR_CODES.md and accuracysnes-plan.md.

Suggestions

  • tests/roms/AccuracySNES/gen/src/tests/ppu.rs: Move one of the two assert_handler_ran(&mut a) calls from Phase B (line 2534) into Phase C (before line 2551) so that all three test phases independently verify their IRQ execution.

Nitpicks

  • tests/roms/AccuracySNES/ERROR_CODES.md: The table entries for codes 1 ($02), 3 ($06), and 4 ($08) use identical text ("the H/V IRQ never fired, so STAT77 was never sampled at all"). Explicitly label which phase (Phase A, B, or C) failed to fire in each description.

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

@doublegate
doublegate merged commit e194a38 into main Jul 31, 2026
16 checks passed
@doublegate
doublegate deleted the feat/accuracysnes-c7-06 branch July 31, 2026 21:21
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.

1 participant