Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions docs/accuracy-ledger.md

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions docs/residuals/R-10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# R-10

> Extracted verbatim from the `R-10` row of `../accuracy-ledger.md` on
> 2026-07-29, because the row had grown past 4,000 characters in a single
> Markdown table cell — which had already begun corrupting the table (see
> below). **Nothing was summarised, reworded or dropped**: every cell is
> reproduced as written, and the append-only rule continues to apply here —
> superseded claims stay, marked, rather than being edited away.

## Symptom

The colour combiner (T-33-002) models the common inputs (combined, texel0/1, primitive, shade, environment, one, zero, and the C-slot alpha taps); the **exotic inputs** — noise, LOD fraction / prim-LOD-fraction, the chroma-key centre/scale, and the convert (`K4`/`K5`) constants — are not modelled and read as **zero**

## Suspected mechanism

These inputs need the LOD pipeline (mip level fraction), the key/convert registers (`Set Key`/`Set Convert`), and a noise source, none of which exist yet; they appear in a small minority of combine modes. Reading them as zero is a bounded, documented gap, not a fabricated value

## Classification

absolute — a coverage boundary, not a fitted constant

## Status

**Partially resolved (2026-07-25) — see the RESOLUTION below.** The `(A − B) * C + D` arithmetic (the `special_expand` asymmetric 9-bit fold, the `+0x80`-before-`>>8` rounding, D added unscaled) and the clamp are validated bit-for-bit against hand-computed values; the 16-field decode, the input mux, and the 2-cycle chaining are unit-tested. The remaining exotic inputs land with the LOD/key/noise state and are validated against the ParaLLEl-RDP conformance vectors (T-33-005). **RESOLUTION (2026-07-25) — the register-sourced exotic inputs are wired.** `PRIM_LOD_FRAC` (RGB mul-select 14, alpha mul-select 6, extracted from `Set Prim Color` word-0 low byte; `min_level`, bits 12:8, stays deferred — it is not stored, and lands with its LOD consumer rather than as unread state) and the `Set Convert` (`0x2C`) constants `K4` (RGB sub-B select 7) and `K5` (RGB mul-select 15, both raw 9-bit from `lo[17:9]`/`lo[8:0]`) now route through the combiner instead of reading zero. Validated byte-for-byte against Angrylion by three new non-vacuous conformance vectors — `tex_tri_primlodfrac_16` (`One * prim_lod_frac`, golden `0x8421` gray vs black if unwired), `tex_tri_convert_k45_16` (`(One − K4) * K5` with bit-8-clear K4/K5, golden `0x94a5` vs black if unwired), and `tex_tri_convert_kneg_16` (a **negative** `K4 = 0x1C0` = −64, golden `0x5295` gray — correct only if the raw 448 is `special_9bit`-expanded to −64; a raw-positive read clamps black) — plus two mutation-checked unit tests (`combine_cycle_routes_prim_lod_frac`, `combine_cycle_routes_convert_k4_k5`). The sign path is faithful to Angrylion, which likewise stores K4/K5 **raw 0..511** (`rdp_set_convert`) and sign-extends in the equation: sub-B via `special_9bit_exttable` (`combiner.c:481`, reproduced bit-for-bit by RustyN64's `special_expand`) and the mul via `SIGNF(c, 9)` (RustyN64's `sext9`). **n64-systemtest impact: none** — the suite has no RDP-combiner coverage, so its failing-assertion count is unchanged (90); R-10 is validated only by the Angrylion conformance vectors. **RESOLUTION (2026-07-25) — the chroma-key combiner inputs are wired.** `Set Key GB` (`0x2A`) and `Set Key R` (`0x2B`) now decode the per-channel key **centre** and **scale** (bit-layout ported from Angrylion `rdp_set_key_gb`/`rdp_set_key_r`: GB `lo` = `centre_g[31:24] scale_g[23:16] centre_b[15:8] scale_b[7:0]`, R `lo` = `width_r[31:16] centre_r[15:8] scale_r[7:0]`), and they route through the combiner as **KeyCentre** (RGB sub-B select 6) and **KeyScale** (RGB mul-select 6) instead of reading zero — matching Angrylion `combiner.c` cases 6. The key **width** is not stored: it drives only the deferred chroma-key alpha compare, not the combiner mux, so it lands with that consumer (the `min_level` precedent). Validated by two mutation-checked unit tests — `set_key_decodes_centre_and_scale_per_channel` (distinct per-channel values pin the decode field positions) and `combine_cycle_routes_chroma_key` (`(One − centre) * scale >> 8` with per-channel centre `[32,64,96]`/scale `[64,128,192]` → `[56,96,120]`; unwiring either input changes the result) — **and byte-for-byte against Angrylion** by the end-to-end conformance vector `tex_tri_chromakey_16` (centre `[0x20,0x40,0x60]`, scale `[0x40,0x80,0xC0]` → RGBA5551 `0x3b1f`, black if unwired), which exercises the decode + mux through the real RDP pipeline. **n64-systemtest impact: none** — no RDP-combiner coverage; count unchanged (90). **Still open** (genuinely need machinery that does not exist yet, read as zero): **noise** (RGB sub-A select 7 — needs a per-pixel noise source), the **derivative-computed `lod_frac`** (RGB mul-select 13 / alpha mul-select 0 — needs the LOD/mip pipeline, pairs with R-13's mip tile selection), and the **YUV convert `K0`–`K3`** coefficients (the `Set Convert` hi word, for the YUV texture path). **RESOLUTION (2026-07-26) — the chroma-key alpha compare (`key_en`) is wired.** `Set Other Modes` bit 40 (`hi >> 8 & 1`) decodes to `OtherModes.key_en`, and `Set Key GB`/`R` now also store the per-channel `key_width` (GB `hi[23:12]/[11:0]`, R `lo[31:16]`). When `key_en`, `Rdp::combine` takes the Angrylion `combiner_1cycle` key path (gated so the common path stays byte-identical — all prior 31 `.rvec` vectors unchanged): the RGB output is the sub-A **chromabypass** colour (clamped), and the pixel alpha is `chroma_key_min` over the **pre-`>>8` 17-bit** combined colour (`combine_channel_17bit` = `((A−B)*C + (D<<8) + 0x80) & 0x1ffff`, matching `color_combiner_equation`) and the key widths — per channel `SIGN(col,17)` folded (`-k`, or `-k+0x10` when the low nibble is 8), `+ (width<<4)`, `min`-of-3, clamp `[0,0xff]`. Validated byte-for-byte against Angrylion **end-to-end** by `tex_tri_chromakey_alpha_16`, which makes the key alpha **observable** via alpha-compare (bit 0) at a `Set Blend Color` threshold of `0x80`: the Shade triangle is written only where `chroma_key_min >= 0x80` (the combine yields exactly `0x80`, so it is drawn — a `chroma_key_min` off by −1 shifts it below the threshold and the triangle vanishes, mutation-verified; clearing `key_en` outputs the combined colour instead of Shade). The `chroma_key_min` fold is **additionally** unit-tested directly with hand-computed values including the bit-16-set (negative) branch (`chroma_key_min_folds_and_takes_the_minimum`). **n64-systemtest impact: none** (no RDP-combiner coverage; count 90). **RESOLUTION (2026-07-26) — the derivative `lod_frac` input is wired** (RGB mul-select 13 / alpha mul-select 0), computed by the 2-cycle LOD ported under R-13 and validated by `tex_tri_lodfrac_16`; see R-13 for the full disposition. **Still open under R-10:** noise (un-oracled — Angrylion fakes it) and the YUV `K0`–`K3` convert
24 changes: 24 additions & 0 deletions docs/residuals/R-13.md

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions docs/residuals/R-18.md

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions docs/residuals/R-19.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# R-19

> Extracted verbatim from the `R-19` row of `../accuracy-ledger.md` on
> 2026-07-29, because the row had grown past 4,000 characters in a single
> Markdown table cell — which had already begun corrupting the table (see
> below). **Nothing was summarised, reworded or dropped**: every cell is
> reproduced as written, and the append-only rule continues to apply here —
> superseded claims stay, marked, rather than being edited away.

## Symptom

**The emulator hangs on the n64-systemtest test `TLB: Execute mapped branch with a non-mapped delay slot`** — a mapped branch whose delay slot lies in a page not currently in the TLB. Both the committed **base** ROM and the `--features timing` ROM stop dead there: `started = 917`, `emux_exited = false`, no test after it ever starts, at an 8×10⁹-tick budget (~2× a normal base completion). It is a genuine loop, not slowness.

## Suspected mechanism

**The committed `systemtest` gate masks it**: `tests/systemtest.rs` asserts Phase-1 *category* `Failed: 0` (those results are captured before test 917) and witnesses `started > 0`, but never requires the ROM to run to `xioctl(EXIT)` — so a mid-suite hang is invisible (the failure mode engineering-lessons §2.2 warns about, one level up). **Fully traced 2026-07-24 — every architectural field is CORRECT, so the defect is NOT in the delivered exception state.** The loop oscillates between `pc = 0x1234_5000` (the non-mapped delay-slot fetch) and `pc = 0x8000_0180` (the general vector), sustaining `EXL = 1`, with: `BadVAddr = 0x1234_5000` (✓ the delay slot), `EPC = 0x1234_4FFC` (✓ the branch, `pc − 4`), `Cause.BD = 1` (✓), `Cause.ExcCode = 2` = `TLBL` (✓), `Context`/`XContext` `BadVPN2 = 0x0_91A2` (✓ `= BadVAddr >> 13`), `EntryHi` VPN2 `= 0x1234_4000` + ASID (✓), 32-bit mode (`Status.KX/SX/UX = 0`). The general vector is correct *given* `EXL = 1` (a refill with `EXL` set uses `0x180`, S-3). `ERET` clears `EXL` correctly (tested). **So `EPC`, `BD`, `Cause`, `BadVAddr`, `Context`, `XContext`, `EntryHi`, the vector, and `ERET` are all right** — the earlier "vector/EPC/EXL is off" guess is disproven. The remaining suspects are in the finer *sequencing* the trace hasn't yet caught: (a) the **`EXL = 0` first fault** (does it reach the refill vector `0x8000_0000` and n64-systemtest's *test* handler, or does the refill handler's own page-table load fault nested straight to the general/"unexpected-exception spin" handler?); and (b) whether n64-systemtest's handler **maps the page and ERETs** expecting the fetch to now hit — in which case a stale **micro-ITLB** (not refilled from the JTLB after the map) would keep the fetch missing.

## Classification

absolute — a hang is a coverage boundary, not a fitted constant

## Status

**Open — fully characterised, not yet root-caused. Blocks the `timing` suite from completing (so it blocks the clean `M` measurement, C-1).** **The test + handler are now understood** (`tlb/exceptions.rs:388` + `exception_handler.rs:247`): the JALR is the last instruction of the mapped page, its delay slot is the first of the next (unmapped) page; the test runs it under `expect_exception(TLBL, -4, …)`, which sets `EXCEPTION_SKIP = -4`, so the handler resumes at `return_to = exceptpc + skip*4 = EPC − 16` — back inside the mapped block, expecting the block's own code there to escape back to the `0x80…` test. n64-systemtest asserts `exceptpc == fault_address − 4` (line 436), and **our `EPC = 0x1234_4FFC` matches that exactly** — a third confirmation the exception state is right. So the loop is not a wrong `EPC`/vector; it is that after the skip-return our CPU re-reaches the JALR and re-faults instead of escaping. **RESOLVED 2026-07-24 (root-caused by a full pipeline-latch trace, not by reasoning).** The defect was NOT in the exception state (all correct, as characterised) but in the **branch-redirect vs. exception-vector race** in `ex_stage`. Sequence: the delay-slot fetch (`0x1234_5000`) faults and the exception is dispatched at the end of the cycle, setting `next_pc = 0x8000_0180` — but the JALR is still sitting **unexecuted** in `rf_ex`. On the next active cycle the JALR reaches EX and unconditionally applied its redirect (`*next_pc = r.target`), and this JALR's target is its **own address** (`v1 = 0x1234_4FFC`), so it clobbered the vector, re-fetched itself, re-faulted its delay slot, and looped forever — exactly the two-state oscillation the latch trace showed (JALR + aborted delay slot circulating, never retiring). Fix (`pipeline.rs::resolve_branch_control`): a branch whose delay slot has aborted (its `ic_rf` latch carries `in_delay_slot` + an abort) **still writes its link** — from the architectural `pc + 8`, since `next_pc` now holds the vector — but its **redirect is suppressed**, so the exception PC wins. This is hardware-accurate: the older branch retires and links (n64-systemtest asserts `RA == fault_address + 4`) while the precise exception on the younger delay slot takes over control flow. With the fix the delay-slot test passes and **the full suite runs to `xioctl(EXIT)` for the first time (950 tests, ~30 s), so `emux_exited` is now `true`.** No regression: golden-log 0-diff, determinism, residue-invariant, and all workspace tests stay green. Completing the run unmasked a distinct pre-existing cluster the hang had hidden — see **R-20**. Discovered + traced + fixed 2026-07-24 during the Stage-C/D timing work
Loading