Skip to content

feat(harness): make the accuracy battery real (T-71-001/T-HARNESS-03) - #165

Merged
doublegate merged 3 commits into
mainfrom
feat/accuracy-battery
Jul 29, 2026
Merged

feat(harness): make the accuracy battery real (T-71-001/T-HARNESS-03)#165
doublegate merged 3 commits into
mainfrom
feat/accuracy-battery

Conversation

@doublegate

Copy link
Copy Markdown
Owner

Motivation

The v0.8.0 cut criterion is "the battery hits its target pass rate, and every
residual has a ledger entry". The second half is largely satisfied; the first half
could not be — the battery was default_battery_stub(), an empty probe set, and
docs/STATUS.md reported it as "0% (battery stubbed)". This makes it real.

Changes

  • default_battery_stubdefault_battery. Scores every committed Angrylion
    RDP conformance vector: 35 probes (rdp-conformance/<vector>), each replayed
    through RustyN64's own RDP and compared byte-for-byte against the oracle's golden
    framebuffer. Every expected value is externally defined — never our own output,
    which is the honesty constraint the plan sets for this battery. STATUS now reports
    a real 100% (35/35).
  • An empty battery no longer scores 1.0. AccuracyReport::ratio documented
    itself as returning "1.0 for an empty battery — vacuously green". A battery that
    ran nothing has proven nothing, so it returns 0.0 now. This is the exact
    vacuous-pass hazard the project keeps a lesson about, sitting in the scorer.
    Added failures() so a regression names the offending vectors.
  • New conformance module. The .rvec parse/replay moved from the test file
    into src/, so the conformance test and the battery share one implementation and
    one vector table. Previously a vector could be exercised by the test while being
    invisible to the battery; vector_bytes(name) now panics on an unknown name, so a
    typo fails loudly instead of silently skipping a vector.

Guards against the battery going quietly hollow

  • default_battery_scores_every_committed_vector — one probe per table entry, and
    asserts a floor on the count.
  • every_committed_vector_parses / vector_names_are_unique — a shrunken or
    duplicated table fails.
  • empty_battery_scores_zero_not_one — pins the non-vacuous ratio.
  • default_battery_matches_the_oracle — the 100% is asserted, and a regression in
    any RDP path fails here with the vector named.

Gates run locally

cargo fmt --all --check, cargo clippy --workspace --all-targets -D warnings,
cargo test --workspace, RUSTDOCFLAGS=-D warnings cargo doc, pre-commit run markdownlint, and the no_std cross-build — all green. 36/36 conformance tests
plus the 4 battery tests pass.

🤖 Generated with Claude Code

The v0.8.0 cut criterion is "the battery hits its target pass rate", but the
battery was `default_battery_stub()` — an empty probe set that `docs/STATUS.md`
reported as "0% (battery stubbed)".

`default_battery()` now scores every committed Angrylion RDP conformance
vector: 35 probes named `rdp-conformance/<vector>`, each replayed through
RustyN64's own RDP and compared byte-for-byte against the oracle's golden
framebuffer. Every expected value is externally defined — never RustyN64's own
output, which is the honesty constraint the plan sets for this battery.
STATUS now reports a real 100% (35/35).

An empty battery no longer scores 1.0. `AccuracyReport::ratio` returned "1.0
for an empty battery — vacuously green"; a battery that ran nothing has proven
nothing, so it now returns 0.0. This is the vacuous-pass hazard the project
already has a lesson about, sitting in the scorer itself. Added
`AccuracyReport::failures()` so a regression names the offending vectors.

New `conformance` module: the `.rvec` parse/replay moved out of the test file
into `src/` so the conformance test and the battery share ONE implementation
and ONE vector table (`RDP_VECTORS`). Previously a vector could be exercised by
the test while being invisible to the battery; now `vector_bytes(name)` panics
on an unknown name, so a typo fails loudly instead of silently skipping.

Gates run locally: fmt, clippy -D warnings, cargo test --workspace, rustdoc,
markdownlint, no_std cross-build (all green). 36/36 conformance tests and the
4 battery tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 28, 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: 3 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: 82e28a12-ab43-4474-bdbb-45e5bd0a37c4

📥 Commits

Reviewing files that changed from the base of the PR and between fee7898 and 25e51c3.

📒 Files selected for processing (1)
  • crates/rustyn64-test-harness/src/conformance.rs
📝 Walkthrough

Walkthrough

The PR introduces a shared .rvec conformance module, reuses it for the RDP test suite and accuracy battery, changes empty-battery scoring to 0%, adds failure reporting, and updates project documentation for the oracle-backed battery.

Changes

RDP accuracy and conformance

Layer / File(s) Summary
Shared conformance engine
crates/rustyn64-test-harness/src/conformance.rs, crates/rustyn64-test-harness/src/lib.rs
Adds validated .rvec parsing, RDP replay, framebuffer mismatch reporting, committed vector registration, lookup helpers, module tests, and crate-root exports.
Oracle-backed accuracy scoring
crates/rustyn64-test-harness/src/accuracy.rs
Replaces the stub battery with one probe per committed vector, changes empty-battery ratios to 0.0, and adds ordered failed-probe reporting.
Shared test assertions and project records
crates/rustyn64-test-harness/tests/rdp_conformance.rs, CHANGELOG.md, docs/STATUS.md
Routes conformance tests through shared helpers and records the oracle-backed battery and updated scoring semantics.

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

Sequence Diagram(s)

sequenceDiagram
  participant AccuracyScorer
  participant RDP_VECTORS
  participant Conformance
  participant RustyN64
  participant OracleGolden
  AccuracyScorer->>RDP_VECTORS: Iterate committed vectors
  AccuracyScorer->>Conformance: Parse and replay vector
  Conformance->>RustyN64: Load preload and command stream
  RustyN64-->>Conformance: Render framebuffer
  OracleGolden-->>Conformance: Provide golden framebuffer
  Conformance-->>AccuracyScorer: Return match or first mismatch
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 8 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Oracle Number Is Stated ⚠️ Warning The PR states 35/35 and 100% in STATUS, but never records any n64-systemtest failing-assertion delta or an explicit 'not measured' note. Add a changelog/STATUS note with the n64-systemtest before→after count, or state plainly that this change was not measured there.
Measured, Never Tuned ⚠️ Warning FAIL: conformance.rs:166 adds a new n_words * 2 + 16 tick budget, and docs/accuracy-ledger.md:11-22 requires measured constants to record provenance; no ledger entry covers it. Add an accuracy-ledger entry naming the replay budget and how it was measured, or replace it with a cited manual/wiki-derived value.
✅ Passed checks (8 passed)
Check name Status Explanation
Title check ✅ Passed Matches Conventional Commits and the scope is aligned with the harness accuracy work.
Description check ✅ Passed It describes the same battery and conformance changes as the diff and objectives.
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.
Docs-As-Spec Sync ✅ Passed No rustyn64-cpu/rsp/rdp/audio/cart/core files changed; only test-harness code and docs/STATUS.md moved, so the docs-sync rule is not triggered.
Changelog Entry For User-Visible Changes ✅ Passed CHANGELOG.md has an [Unreleased] entry covering the user-visible battery, conformance, and ratio changes.
Unsafe Stays Out Of The Chip Crates ✅ Passed PASS: PR diff adds no unsafe syntax outside frontend and removes no #![forbid(unsafe_code)]; chip/core crate roots still carry the forbid guards.

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

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

🤖 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 `@crates/rustyn64-test-harness/src/conformance.rs`:
- Around line 164-168: Replace the fixed n_words * 2 + 16 loop in the
conformance harness with polling for the architectural RDP completion state
before scoring output. Retain a safety bound only if it is empirically measured,
document its provenance in docs/accuracy-ledger.md, and add a regression vector
requiring more ticks than the former quota to verify incomplete output cannot be
scored.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 15a8c799-6730-4a96-bd04-6671645fdda1

📥 Commits

Reviewing files that changed from the base of the PR and between c5fa951 and fee7898.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • crates/rustyn64-test-harness/src/accuracy.rs
  • crates/rustyn64-test-harness/src/conformance.rs
  • crates/rustyn64-test-harness/src/lib.rs
  • crates/rustyn64-test-harness/tests/rdp_conformance.rs
  • docs/STATUS.md

Comment thread crates/rustyn64-test-harness/src/conformance.rs Outdated
Making `.rvec` parse/replay public put three previously-unreachable paths in
reach of a directly-constructed `Vector` (all its fields are public), where
before only `parse()`-validated vectors could get in:

- `(width * height * bpp) as usize` multiplied in u32 BEFORE the cast, so an
  oversized geometry wrapped to a bogus framebuffer length instead of failing.
  Widen each operand first.
- the RDRAM bounds assertion used raw `+`, which can overflow `usize` on a
  32-bit host for an address near u32::MAX. Use `checked_add`.
- `first_mismatch` iterated over `got.len()` while indexing `golden_fb`, so a
  length mismatch would panic out of bounds rather than being reported. Bound
  the scan by the shorter slice, which also makes the length-mismatch return
  properly reachable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@doublegate

Copy link
Copy Markdown
Owner Author

Adjudication of the Antigravity review (accuracy battery, #165):

Blocking — (v.width * v.height * v.bpp) as usize multiplies in u32 before the cast: adopted (a0a5b57). Correct, and worth stating why it became reachable: this code moved verbatim out of the test file, where the only way in was through parse(), which already validates the geometry with checked_mul. Making replay public — over a Vector whose fields are all public — put a directly-constructed oversized geometry in reach, where the u32 product would wrap to a bogus length instead of failing. Each operand is now widened before multiplying.

Suggestion — first_mismatch indexes golden_fb by got.len(): adopted (a0a5b57). Same root cause: for a parsed vector the two lengths are equal by construction, but not for a hand-built one, and the mismatch path would then panic out of bounds instead of reporting the length difference it exists to report. The scan is now bounded by the shorter slice.

Suggestion — checked_add for the RDRAM bounds: adopted (a0a5b57). Cheap, and the assertion's whole job is to be the thing that catches a bad address — it should not itself be the overflow.

Nitpick — the fallback Mismatch is unreachable when the lengths are equal: resolved by the above. It was always intended as the length-mismatch signal (empty got/golden, which the caller turns into "framebuffer length differs from the golden"), and bounding the loop by min(len) is exactly what makes it properly reachable in that case rather than being pre-empted by an out-of-bounds panic.

Good catch on all three — they were latent the moment this became a public API rather than test-file-internal.

…d budget

The replay drained the FIFO for `n_words * 2 + 16` ticks — an undocumented
fitted constant with no proof the list had finished. A vector needing more
ticks would have been scored from a partly rendered frame, silently
invalidating an oracle result. That is the same class of defect as a vacuous
pass, and it now sits under the accuracy battery.

Drain instead until the FIFO is OBSERVABLY empty (`DPC_CURRENT >= DPC_END` —
the very condition `Rdp::tick` no-ops on). The remaining cap is a liveness
backstop only, and it ASSERTS rather than falling through, so a command that
stalls the FIFO fails loudly instead of quietly producing a wrong comparison.

All 36 conformance vectors and the battery still pass, which now also
demonstrates that each list genuinely drains rather than being assumed to.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

Replaces the stubbed accuracy battery with a 35-probe suite backed by Angrylion RDP conformance vectors, changes empty battery scoring from 1.0 to 0.0, and moves .rvec parsing and replay into a shared conformance module.

Blocking issues

None found.

Suggestions

  • crates/rustyn64-test-harness/src/conformance.rs:148: replay uses direct multiplication (v.width as usize) * (v.height as usize) * (v.bpp as usize). Because Vector fields are public, custom or invalid dimensions can overflow usize (particularly on 32-bit targets). Use checked_mul here as is done in parse.
  • crates/rustyn64-test-harness/src/conformance.rs:207: In first_mismatch, if got or v.golden_fb length is not a multiple of bpp, got[i..i + bpp] will panic out-of-bounds on the final loop iteration. Align common down to the nearest multiple of bpp before stepping (common - (common % bpp)).
  • crates/rustyn64-test-harness/src/conformance.rs:64: parse does not validate that width or height are non-zero. A zero width will cause a division-by-zero panic in first_mismatch at px % v.width. Enforce width > 0 && height > 0 directly in parse.

Nitpicks

  • crates/rustyn64-test-harness/src/conformance.rs:64: parse panics on malformed vector headers instead of returning a Result<Vector, ParseError>.
  • crates/rustyn64-test-harness/src/accuracy.rs:120: default_battery() allocates a new String for every probe via format!("rdp-conformance/{name}") on each invocation.

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

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