Skip to content

test(rdp): oracle-validate the 4-bit texture path (ledger R-7) - #142

Merged
doublegate merged 3 commits into
mainfrom
test/rdp-4bit-texture-oracle
Jul 25, 2026
Merged

test(rdp): oracle-validate the 4-bit texture path (ledger R-7)#142
doublegate merged 3 commits into
mainfrom
test/rdp-4bit-texture-oracle

Conversation

@doublegate

Copy link
Copy Markdown
Owner

Motivation

Continuing gap-analysis Stage D. With the CPU-timing cluster done/deferred, this
is the first RDP residual, validated against the Angrylion .rvec conformance
oracle. It closes the validated part of ledger R-7 (4-bit textures) and — more
importantly — corrects a misconception the ledger carried.

The finding

Studying the Angrylion oracle's source (tex.c:526-533) shows there is no 4-bit
texel load
on the N64: a 4-bit texture-image load is invalid and crashes the
RDP pipeline (rdp_pipeline_crashed = 1). Games load 4-bit textures by lying
about the format
— an 8-bit texture image + 8-bit LOAD tile loads the packed
bytes raw, then a separate 4-bit render tile extracts nibbles at fetch.

RustyN64 already implements both halves (the 8-bit load, and fetch_texel for
I4/IA4/CI4), so the canonical 4-bit path already works. R-7's "4-bit texels not
loaded, needs nibble addressing" framing was wrong — 4-bit is never DMA'd as
nibbles.

What this PR does

  • Adds conformance vector tex_tri_i4_16: eight I4 texels packed two-per-byte
    (0xFD 0xB9 0x75 0x31), an 8-bit Load Tile, then a 4-bit render tile sampled
    across the triangle. RustyN64 reproduces Angrylion byte-for-byte.
  • Non-vacuous by construction: the intensities are descending and non-zero
    (texel 0 = white 0xFFFF), so an all-zero no-op load would render black and
    fail — the golden distinguishes a correct load from a load that did nothing.
  • No production code change — the existing bail on a 4-bit texture image
    already matches hardware's invalid-load behaviour.
  • Corrects ledger R-7, docs/rdp.md, and CHANGELOG to the accurate model,
    and precisely characterizes the two genuinely-remaining gaps (the 32-bit
    Load Block split; a direct 4-bit LOAD tile with an 8-bit texture image — the
    ti_size-vs-tile.size load granularity).

Gates run locally

  • cargo test -p rustyn64-test-harness --test rdp_conformance — 22 pass, 1 ignored
  • cargo fmt --all --check · cargo clippy -p rustyn64-test-harness --all-targets -- -D warnings
  • pre-commit run markdownlint on the touched docs · scripts/check_no_roms.sh clean
  • The regenerated golden is deterministic — no other committed vector changed.

🤖 Generated with Claude Code

Studying the Angrylion oracle source (tex.c:526-533) corrected a misconception
behind ledger R-7. There is NO 4-bit texel LOAD on the N64: a 4-bit texture-image
load is invalid and crashes the RDP pipeline. Games load 4-bit textures by lying
about the format -- an 8-bit texture image + 8-bit LOAD tile loads the packed
bytes raw, then a SEPARATE 4-bit render tile extracts nibbles at fetch. RustyN64
already implements both halves (8-bit load + 4-bit fetch_texel for I4/IA4/CI4), so
that canonical path already works -- R-7's "4-bit texels not loaded, needs nibble
addressing" framing was wrong.

New conformance vector tex_tri_i4_16 proves it: eight I4 texels packed two-per-byte
(0xFD 0xB9 0x75 0x31), an 8-bit Load Tile, then a 4-bit (format I, size 0) render
tile sampled across the triangle. RustyN64 reproduces Angrylion BYTE-FOR-BYTE. The
eight intensities are descending and non-zero (texel 0 = white 0xFFFF, not black),
so the test is non-vacuous: an all-zero (no-op) load would render black and fail.
Since the load is format-agnostic (a raw byte copy), this validates the load
mechanism for all 4-bit formats; the per-format decoders are separately unit-tested.

No production code change (the bail on a 4-bit texture image already matches the
hardware's invalid-load behaviour). R-7's genuinely-remaining gaps are now precisely
characterized and stay open: the 32-bit Load Block split, and a direct 4-bit LOAD
tile paired with an 8-bit texture image (the ti_size-vs-tile.size load granularity).

- vectors-gen/driver.c: V20 tex_tri_i4_16 generator (+ Angrylion golden .rvec).
- rdp_conformance.rs: the assertion.
- Ledger R-7, docs/rdp.md, CHANGELOG corrected to the accurate hardware model.

Gates: full rdp_conformance suite (22 pass), fmt --check, clippy -D warnings,
markdownlint on the touched docs, check_no_roms clean.

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

coderabbitai Bot commented Jul 25, 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: 14 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: 42d305e9-7143-47fe-8943-82ee49414621

📥 Commits

Reviewing files that changed from the base of the PR and between 7c2de6f and 30e345b.

📒 Files selected for processing (3)
  • crates/rustyn64-test-harness/vectors-gen/driver.c
  • docs/accuracy-ledger.md
  • docs/rdp.md
📝 Walkthrough

Walkthrough

Adds an Angrylion-validated I4 textured-triangle vector, its regression test, generated RVEC payload, and documentation updates describing the canonical load/fetch path and remaining deferred cases.

Changes

I4 texture conformance

Layer / File(s) Summary
I4 vector definition and emission
crates/rustyn64-test-harness/vectors-gen/driver.c
Defines packed I4 texels, configures the 8-bit load and separate 4-bit render tiles, and emits the new vector.
Oracle regression test and payload
crates/rustyn64-test-harness/tests/rdp_conformance.rs, crates/rustyn64-test-harness/tests/vectors/tex_tri_i4_16.rvec
Adds tex_tri_i4_16_matches_angrylion and its RVEC input for byte-for-byte framebuffer comparison.
Accuracy documentation
docs/accuracy-ledger.md, docs/rdp.md, CHANGELOG.md
Records canonical I4 handling as oracle-validated and retains the 32-bit Load Block split and direct 4-bit LOAD tile cases as open.

|

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 10
✅ Passed checks (10 passed)
Check name Status Explanation
Title check ✅ Passed The title matches Conventional Commits and accurately summarises the 4-bit texture oracle validation.
Description check ✅ Passed The description is directly related to the RDP conformance test, ledger update, and docs changes in this PR.
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.
Oracle Number Is Stated ✅ Passed PASS: This is docs/tooling only, and the touched specs explicitly say n64-systemtest impact: not measured; docs/STATUS.md remains the authoritative 90.
Docs-As-Spec Sync ✅ Passed No chip-crate files changed; docs/rdp.md and accuracy-ledger R-7 were updated to match the new I4 oracle-validation wording.
Changelog Entry For User-Visible Changes ✅ Passed PASS: CHANGELOG.md has an [Unreleased] entry for the 4-bit texture validation, satisfying the user-visible-change rule.
Measured, Never Tuned ✅ Passed PASS: R-7 remains a coverage boundary, not a fitted constant; no new hardware constant or timing value was added outside docs/accuracy-ledger.md.
Unsafe Stays Out Of The Chip Crates ✅ Passed PR diff adds no unsafe syntax outside frontend; the changed Rust file is test-only, and #![forbid(unsafe_code)] remains on core/chip crates.

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

Adopt Antigravity review:
- Nitpick: clarify the LOAD tile's line=1 is one 64-bit word (not a byte count).
- Suggestion (R-7 count 90 vs neighbours' 93): the reviewer flagged a real
  inconsistency but the fix direction is inverted — 90 is authoritative
  (docs/STATUS.md, after Phase 5 dropped 93->90); the surrounding 93s are stale.
  Resolve by pointing the R-7 narrative at STATUS.md rather than citing a number,
  per the docs-as-spec rule, avoiding both the stale 93 and a ledger-wide count
  sweep in this focused PR.

Rejected: the uint8_t-vs-uint16_t preload suggestion — the .rvec byte emit is
explicitly big-endian and host-independent (documented in driver.c), the u16
array is the established Vector.texels pattern, and the byte-for-byte pass proves
the byte order is correct.

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

Copy link
Copy Markdown
Owner Author

Adjudicated:

Suggestion 1 (TEX_I4_RAMP as uint8_t[4] for endianness safety) — Rejected. The byte order is host-independent: the .rvec preload region is emitted explicitly big-endian ({v->texels[i] >> 8, v->texels[i]} in emit_vector), and Angrylion reads its RDRAM via RREADIDX32 as a u32 value ((hi<<16)|lo), not a raw host-byte read — both documented in driver.c:145-149. The uint16_t[] form is the established Vector.texels API every texture vector uses (V11/V12/…), and the byte-for-byte pass against Angrylion empirically proves the byte stream is correct on this host. Switching to uint8_t would require changing the shared preload API for no correctness benefit.

Suggestion 2 (R-7 count "90" vs neighbours' "93") — Real inconsistency, but the fix direction is inverted: 90 is authoritative (docs/STATUS.md: Phase 5's cart/PIF/SI work dropped the suite-wide count 93→90); the surrounding 93s are stale narrative predating that. Harmonising to 93 would make the ledger factually wrong. Fixed in d5edd58 by pointing R-7's narrative at docs/STATUS.md (the single source of truth per the docs-as-spec rule) instead of citing a number — which also avoids pulling a ledger-wide 93→90 sweep into this focused test PR (that stale-count sweep is worth a separate docs-sync change).

Nitpick (line=1 units) — Adopted in d5edd58: the comment now reads line=1 (one 64-bit word).

@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 `@docs/rdp.md`:
- Around line 281-289: Update the later summary near the remaining deferred-work
section to remove the generic “4-bit loading” wording and explicitly name only
the two open cases: the 32-bit Load Block split and direct 4-bit LOAD-tile
granularity with an 8-bit texture image. Preserve the clarification that 4-bit
texture-image loads are invalid and must not be described as an implementation
target.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 56081dbb-9cb7-470c-857d-f602cd3a3f9d

📥 Commits

Reviewing files that changed from the base of the PR and between 9a6d572 and 7c2de6f.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • crates/rustyn64-test-harness/tests/rdp_conformance.rs
  • crates/rustyn64-test-harness/tests/vectors/tex_tri_i4_16.rvec
  • crates/rustyn64-test-harness/vectors-gen/driver.c
  • docs/accuracy-ledger.md
  • docs/rdp.md

Comment thread docs/rdp.md
…bbit)

Two later spots in rdp.md still said generic '4-bit loading (nibble Load Tile/
Load Block) pending R-7', conflicting with the corrected model earlier in the same
file. Reconcile both to the accurate statement: there is no 4-bit load; 4-bit
textures load as 8-bit + render with a 4-bit tile (validated by tex_tri_i4_16), and
only the 32-bit Load Block split + a direct 4-bit LOAD tile remain deferred.

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

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

This PR adds an RDP conformance test (tex_tri_i4_16) validating the hardware-canonical 4-bit (I4) texture path against Angrylion and updates documentation to clarify that 4-bit textures are loaded via 8-bit texture images and sampled using a 4-bit render tile.

Blocking issues

None found.

Suggestions

  • driver.c: TEX_I4_RAMP is declared as uint16_t[2] (4 bytes total), while Vector v20 on line 956 passes payload length 2. If Vector payload lengths expect 32-bit word counts (matching sizeof(...) / 4 used for the command payload on line 957), length 2 will read 8 bytes from a 4-byte buffer. Use an explicit uint8_t or uint32_t array definition to eliminate host endianness and word-unit ambiguity.

Nitpicks

  • rdp_conformance.rs: The test doc comment duplicates prose almost verbatim from driver.c and CHANGELOG.md; keep it concise and focused strictly on what tex_tri_i4_16 exercises.

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

@doublegate
doublegate merged commit 3a800b0 into main Jul 25, 2026
11 checks passed
@doublegate
doublegate deleted the test/rdp-4bit-texture-oracle branch July 25, 2026 07:47
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