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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **`C5.15` is now blessed coverage: excluding the one undefined pixel made the rest of Mode 5
agree.** The retraction below establishes that the first hi-res pixel of a line is a genuine
reference disagreement on a value ares flags as *not confirmed on hardware*. Hashing it would have
made **every** hi-res scene permanently unblessable over one pixel.

`HiResEven` now excludes column 0, so the sample is 255x224 rather than 256x224 — a deliberate
part of the contract, recorded in the golden file's own header and in the variant's doc. With that
one column out, all three hosts agree bit-for-bit:

```

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 language to the fenced hash block.

markdownlint reports MD040 for Line 23. Add text to the opening fence.

Proposed fix
-  ```
+  ```text
📝 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
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 23-23: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 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 23, Update the fenced hash block in CHANGELOG.md by
adding the text language identifier to its opening fence, resolving the
markdownlint MD040 violation while leaving the block contents unchanged.

Sources: Path instructions, Linters/SAST tools

snes9x 0xf7ed8ab9ecd95d85
Mesen2 0xf7ed8ab9ecd95d85
RustySNES 0xf7ed8ab9ecd95d85
```

Rule 4 is satisfied, so the golden is blessed. **55 scenes match on both hosts, zero unblessed.**
Coverage `358 → 359 of 443`; `C5` drops to two uncovered rows (`C5.06`/`C5.07`, still blocked by
the mainscreen-column disagreement).

- **RETRACTION: the Mode-5 first-pixel divergence is a 2-vs-2 reference disagreement, not a
RustySNES defect.** The entry below claims the two references agree bit-for-bit against RustySNES,
"this project's signature for a real defect". That was published from **two** references without
Expand Down
30 changes: 25 additions & 5 deletions crates/rustysnes-test-harness/tests/accuracysnes_scenes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,15 @@ fn manifest_path() -> PathBuf {
enum Extract {
/// 256-wide frame, rows `FIRST_ROW..FIRST_ROW + SCENE_H`.
Direct,
/// 512-wide frame: the even columns, which are the subscreen half.
/// 512-wide frame: the even columns — the subscreen half — **starting at column 2**.
///
/// Column 0 is deliberately excluded. It is the first hi-res pixel of the line, and the
/// references genuinely disagree about it: RustySNES and ares emit black, snes9x and Mesen2 the
/// backdrop, and ares' own source says *"exact value initializations are not confirmed on
Comment on lines +74 to +78
/// hardware"*. Under ADR 0013 rule 4 a golden may not be blessed from a pixel the references
/// disagree about — so hashing it would make every hi-res scene permanently unblessable over
/// one undefined pixel. Excluding it makes the other 255 columns, which all four
/// implementations agree on, into evidence.
HiResEven,
}

Expand Down Expand Up @@ -181,6 +189,13 @@ fn hash_scene(fb: &[u16], width: usize, extract: Extract) -> (u64, Vec<u16>) {
2
}
};
// Column 0 is skipped under `HiResEven` — see the variant's own doc. The sample is therefore
// 255 columns wide there, not 256, and that is a deliberate part of the contract rather than an
// off-by-one.
let first_col = match extract {
Extract::Direct => 0,
Extract::HiResEven => 1,
};
assert!(
fb.len() >= (FIRST_ROW + SCENE_H) * width,
"the framebuffer holds {} pixels, too few for rows {FIRST_ROW}..{} at width {width} — the \
Expand All @@ -190,13 +205,18 @@ fn hash_scene(fb: &[u16], width: usize, extract: Extract) -> (u64, Vec<u16>) {
FIRST_ROW + SCENE_H
);
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
let mut px = Vec::with_capacity(SCENE_W * SCENE_H);
// The sample is narrower than the region under `HiResEven` — see `first_col`. All three hosts
// loop over the SAMPLE index and derive the source column from it, rather than looping over
// source columns, so the three implementations line up statement for statement; cross-host
// drift in this loop is the one thing a golden cannot detect.
let sample_w = SCENE_W - first_col;
let mut px = Vec::with_capacity(sample_w * SCENE_H);
for y in FIRST_ROW..FIRST_ROW + SCENE_H {
for x in 0..SCENE_W {
// Even columns only under `HiResEven`: the subscreen half, which is the half the three
for x in 0..sample_w {
// Even columns only under `HiResEven`: the subscreen half, which is the half the
// references agree on. RustySNES's own framebuffer is not line-doubled, so the row
// index needs no step — Mesen2's does, and that is handled in its own host.
let p = fb[y * width + x * step];
let p = fb[y * width + (x + first_col) * step];
let canonical = ((p & 0x1F) << 10) | (p & 0x03E0) | ((p >> 10) & 0x1F);
px.push(canonical);
h ^= u64::from(canonical);
Expand Down
6 changes: 3 additions & 3 deletions docs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ compositor (`docs/adr/0014`, T-CA-10) becomes the sole renderer — its compose/
per-dot against live registers (mid-line CGRAM/OAM/`INIDISP` at dot resolution; BG-fetch-ahead is
Phase 4c, not yet landed) — and the first-party AccuracySNES
cartridge matures into a usable instrument (AccuracyCoin-style paged menu + automatic skyline
results + per-test B-skip + a Select WRAM debug viewer) with its battery at **358 of 443** dossier
assertions (302 on-cart + 54 rendered scenes + 2 host-side).
results + per-test B-skip + a Select WRAM debug viewer) with its battery at **359 of 443** dossier
assertions (302 on-cart + 55 rendered scenes + 2 host-side).
**`v1.22.0 "Horizon"` fixes the long-standing DSP-1 continuous-mode Mode-7 flat floor** (Pilotwings
flight, SMK track): the shared NEC-DSP host-sync (`run_until_rqm`) stopped one host-write too early,
so reading a host input word raised RQM before the firmware cleared DRC to 16-bit — mis-framing the
Expand Down Expand Up @@ -241,7 +241,7 @@ tracked here, always current, reaffirmed every release:
| Core/Curated coprocessors (oracle-gated) | ✅ **3 / 3, honesty gate green** | DSP-1 (4 commercial ROMs), Super FX/GSU (58 Krom ROMs + per-opcode suite), SA-1 (18 commercial carts) — `ORACLE_COPROCESSORS` |
| BestEffort coprocessors, real-title validated | ✅ **10 / 11** | DSP-2, DSP-4, ST010, S-DD1, CX4, OBC1 boot a real commercial title to real gameplay content. **DSP-3 (SD Gundam GX) + ST011 (2-dan Morita Shougi)** are liveness/determinism-validated — detection + `host_accesses > 0` + bit-identical framebuffer (`dsp3_st011_oncart`). **ST018 (Nidan Morita Shogi 2) + S-RTC (Daikaijuu Monogatari II)** are detection + boot + determinism-validated against their real carts (`srtc_st018_oncart`), but their coprocessor's core function is **usage-gated** (the ST018 shogi AI runs only on the computer's move; the S-RTC clock is read at specific moments), so it is not exercised in a headless boot |
| BestEffort coprocessors, no booting dump | ⚠️ **1 / 11** | SPC7110 — **two** local Tengai Makyou Zero dumps both fail to boot to content: the 7 MiB one is a fan-translation ROM hack needing a patch-only memory region no cartridge has (`docs/audit/spc7110-boot-crash-2026-07-08.md`); a 5 MiB dump (sha256 `8620203d…`) freezes at a near-blank screen with zero coprocessor activity, and does not match the documented-good original (`69d06a3f…`). The correct original-cartridge dump remains the ROM-sourcing gap (`docs/rom-test-corpus.md`) |
| AccuracySNES (first-party battery) | ✅ **358 / 443 assertions covered, 100% on-cart pass** | A 343-test self-scoring battery spanning Groups A-G. **358 of 443** dossier assertions covered: **302 on-cart** + **54 via rendered scenes** (`docs/adr/0013`) + **2 host-side** (`dossier.rs::HOST_COVERED`, admitted only where the cart physically cannot observe the assertion), kept as separate columns on purpose (`docs/accuracysnes-coverage.md`, regenerated with the ROM so it cannot drift). Every scored row is inject-verified for non-vacuity and cross-validated headlessly against **Mesen2, snes9x and ares** — three independent references as of `v1.29.0`, with MesenCE additionally serving as the per-dot compositor's blueprint and exact-frame oracle. Provenance gate green. Ships an AccuracyCoin-style on-cart UI as of `v1.21.0` (paged menu + automatic skyline results + per-test B-skip + a Select WRAM debug viewer). Remaining ~85 assertions are compositor-gated hi-res, second-image G-rows, treacherous CPU/dot-model timing, or provably uncoverable — though "uncoverable" has twice proved too strong (`E5.06` and `B2.02`/`B2.03` all landed after being parked) (`docs/accuracysnes-coverability-audit-2026-07-23.md`) |
| AccuracySNES (first-party battery) | ✅ **359 / 443 assertions covered, 100% on-cart pass** | A 343-test self-scoring battery spanning Groups A-G. **359 of 443** dossier assertions covered: **302 on-cart** + **55 via rendered scenes** (`docs/adr/0013`) + **2 host-side** (`dossier.rs::HOST_COVERED`, admitted only where the cart physically cannot observe the assertion), kept as separate columns on purpose (`docs/accuracysnes-coverage.md`, regenerated with the ROM so it cannot drift). Every scored row is inject-verified for non-vacuity and cross-validated headlessly against **Mesen2, snes9x and ares** — three independent references as of `v1.29.0`, with MesenCE additionally serving as the per-dot compositor's blueprint and exact-frame oracle. Provenance gate green. Ships an AccuracyCoin-style on-cart UI as of `v1.21.0` (paged menu + automatic skyline results + per-test B-skip + a Select WRAM debug viewer). Remaining ~84 assertions are compositor-gated hi-res, second-image G-rows, treacherous CPU/dot-model timing, or provably uncoverable — though "uncoverable" has twice proved too strong (`E5.06` and `B2.02`/`B2.03` all landed after being parked) (`docs/accuracysnes-coverability-audit-2026-07-23.md`) |
| Determinism contract | ✅ **proven** | bit-identical framebuffer/audio across runs; save-state round-trip proven across all three board tiers (no-coprocessor, Curated, BestEffort) |

**Named residuals, tracked not hidden:** the 65816 `e1.e` divergence (`docs/adr/0002`);
Expand Down
4 changes: 2 additions & 2 deletions docs/accuracysnes-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Every sub-group of Part V is enumerated, so this is a **complete** statement of
| `C2` | 10 | 10 | 0 | 0 | — |
| `C3` | 10 | 9 | 0 | 0 | C3.10 |
| `C4` | 5 | 0 | 5 | 0 | — |
| `C5` | 15 | 0 | 12 | 0 | C5.06, C5.07, C5.15 |
| `C5` | 15 | 0 | 13 | 0 | C5.06, C5.07 |
| `C6` | 7 | 0 | 6 | 0 | C6.07 |
| `C7` | 16 | 8 | 6 | 0 | C7.07, C7.12 |
| `C8` | 12 | 0 | 11 | 0 | C8.09 |
Expand All @@ -52,7 +52,7 @@ Every sub-group of Part V is enumerated, so this is a **complete** statement of
| `F1` | 22 | 13 | 0 | 0 | F1.13, F1.15, F1.16, F1.17, F1.18, F1.19, F1.20, F1.21, F1.22 |
| `G1` | 18 | 15 | 0 | 2 | G1.13 |

**302 of 443** enumerated assertion rows covered by an on-cart test, plus **54** covered only by a rendered scene (`docs/adr/0013`) and **2** covered only by a host-side test — **358 of 443** in total.
**302 of 443** enumerated assertion rows covered by an on-cart test, plus **55** covered only by a rendered scene (`docs/adr/0013`) and **2** covered only by a host-side test — **359 of 443** in total.

The three columns are kept apart on purpose, in descending order of what the evidence is worth. An on-cart result means the same thing on any emulator and on real hardware; a rendered scene needs a host holding the golden; a **host-side** cover is this project testing its own code, which is the one thing AccuracySNES exists to stop being the only evidence. The host tier is admitted only where the cart *physically cannot* observe the assertion — the stimulus comes from outside the cartridge, or the subject is the loader rather than the machine — and every entry names the test and the reason (`dossier.rs::HOST_COVERED`). Adding the columns into one figure would quietly change what the number claims.

Expand Down
4 changes: 2 additions & 2 deletions docs/accuracysnes-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ AccuracySNES closed ticket **T-04**. The follow-on tickets minted here are **T-0
| | |
|---|---|
| Tests | **343** (scoring + golden vectors + region SKIP per image) — *tests, not assertions; see the note below the table* |
| Assertion coverage | **358 of 443** dossier assertions — **302 on-cart** + **54 rendered scenes** + **2 host-side**, kept as separate columns in descending order of what the evidence is worth (`docs/accuracysnes-coverage.md`) |
| Rendered scenes | **54** declared, all blessed and matching on both scene hosts (`docs/adr/0013`); **54** dossier rows have a scene as their only cover |
| Assertion coverage | **359 of 443** dossier assertions — **302 on-cart** + **55 rendered scenes** + **2 host-side**, kept as separate columns in descending order of what the evidence is worth (`docs/accuracysnes-coverage.md`) |
| Rendered scenes | **55** declared, all blessed and matching on both scene hosts (`docs/adr/0013`); **55** dossier rows have a scene as their only cover. One uses the `hires-even` extraction (`C5.15`, Mode 5) |
| Pass rate | **100.00%** on-cart, floor enforced at 1.00 by `tests/accuracysnes.rs` |
| Cross-validated | **Three references** as of `v1.29.0`. Mesen2 agrees on every test but `F1.03`, which clocks both ports out of one latch and so needs the port-2 input its Lua runner cannot drive; snes9x has 14 recorded divergences and **ares** 3, each with a citation in `scripts/accuracysnes/crossval.sh`. A headless **MesenCE** is separately the per-dot compositor's blueprint + exact-frame oracle. All images. |
| Groups shipped | **A** (65C816) · **B** (5A22) · **C** (PPU, on-cart and rendered) · **D** (DMA/HDMA) · **E** (SPC700 + S-DSP) · **F** (controller ports) · **G** (cartridge/memory map) — all seven, all partial |
Expand Down
15 changes: 12 additions & 3 deletions scripts/accuracysnes/libretro_crossval.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,12 @@ static void video_refresh(const void *d, unsigned w, unsigned h, size_t p) {
* measured 2026-08-02, emits 512x224 and does not). */
const unsigned want_w = (want_extract == EXTRACT_HIRES_EVEN) ? SCENE_W * 2u : SCENE_W;
const unsigned xstep = (want_extract == EXTRACT_HIRES_EVEN) ? 2u : 1u;
/* Column 0 is EXCLUDED under `hires-even`, so the sample is 255 columns wide there rather than
* 256. It is the first hi-res pixel of the line and the references genuinely disagree about it
* -- RustySNES and ares emit black, snes9x and Mesen2 the backdrop, and ares' own source says
* "exact value initializations are not confirmed on hardware". Hashing it would make every
* hi-res scene permanently unblessable under ADR 0013 rule 4 over one undefined pixel. */
const unsigned first_col = (want_extract == EXTRACT_HIRES_EVEN) ? 1u : 0u;
const unsigned ystep = (d && h >= (SCENE_H + FIRST_ROW) * 2u) ? 2u : 1u;
/* The warn condition is the DROP condition, character for character. They disagreed in the
* first draft (`<` here, `!=` below), so a frame taller than the contract was dropped without a
Expand Down Expand Up @@ -223,13 +229,16 @@ static void video_refresh(const void *d, unsigned w, unsigned h, size_t p) {
uint64_t hash = 0xcbf29ce484222325ull;
for (unsigned y = 0; y < SCENE_H; y++) {
const uint8_t *row = (const uint8_t *)d + (size_t)(y + FIRST_ROW) * ystep * p;
for (unsigned x = 0; x < SCENE_W; x++) {
/* Loop the SAMPLE index and derive the source column, matching the Rust and Lua hosts
* statement for statement -- cross-host drift in this loop is the one thing a golden
* cannot detect. */
for (unsigned x = 0; x < SCENE_W - first_col; x++) {
unsigned r, g, b;
Comment on lines 230 to 236
if (pixel_format == FMT_XRGB8888) {
uint32_t v = ((const uint32_t *)row)[x * xstep];
uint32_t v = ((const uint32_t *)row)[(x + first_col) * xstep];
r = (v >> 19) & 0x1F; g = (v >> 11) & 0x1F; b = (v >> 3) & 0x1F;
} else {
uint16_t v = ((const uint16_t *)row)[x * xstep];
uint16_t v = ((const uint16_t *)row)[(x + first_col) * xstep];
if (pixel_format == FMT_RGB565) {
/* Green is 6 bits here because the core widened a 5-bit channel; dropping the
* low bit recovers the original rather than inventing precision. */
Expand Down
12 changes: 10 additions & 2 deletions scripts/accuracysnes/mesen_scenes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ local function hashFrame(id)
local hires = EXTRACT[id] == "hires-even"
local width = hires and (SCENE_W * 2) or SCENE_W
local xstep = hires and 2 or 1
-- Column 0 is EXCLUDED under `hires-even`, so the sample is 255 columns wide there rather than
-- 256. It is the first hi-res pixel of the line and the references genuinely disagree about it:
-- RustySNES and ares emit black, snes9x and Mesen2 the backdrop, and ares' own source says
-- "exact value initializations are not confirmed on hardware". Hashing it would make every
-- hi-res scene permanently unblessable under ADR 0013 rule 4 over one undefined pixel.
local firstCol = hires and 1 or 0
local ystep = hires and 2 or 1
local wantLen = hires and (SCENE_BUF_LEN * 4) or SCENE_BUF_LEN
if #buf ~= wantLen then
Expand All @@ -153,8 +159,10 @@ local function hashFrame(id)
local px = {}
for y = 0, SCENE_H - 1 do
local row = (y + FIRST_ROW) * ystep * width
for x = 0, SCENE_W - 1 do
local v = buf[row + x * xstep + 1]
-- Loop the SAMPLE index and derive the source column, matching the C and Rust hosts
-- statement for statement; cross-host drift here is the one thing a golden cannot detect.
for x = 0, SCENE_W - 1 - firstCol do
local v = buf[row + (x + firstCol) * xstep + 1]
-- Mesen hands back 24-bit RGB; the SNES channels are 5-bit widened to 8, so the top
-- five bits recover the original rather than inventing precision.
local r = (v >> 19) & 0x1F
Expand Down
10 changes: 9 additions & 1 deletion tests/golden/accuracysnes-scenes.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
#
# Format: <scene id>\t<hash>
#
# The hash is FNV-1a over a fixed 256x224 region of canonical 0RRRRRGGGGGBBBBB pixels. Fixed and
# The hash is FNV-1a over a fixed 256x224 region of canonical 0RRRRRGGGGGBBBBB pixels -- except
# under the `hires-even` extraction, where it is 255x224: the frame is 512 wide, the sample is its
# EVEN columns (the subscreen half), and column 0 is EXCLUDED. That first hi-res pixel is the one
# thing the references genuinely disagree about -- RustySNES and ares emit black, snes9x and Mesen2
# the backdrop, and ares' own source says "exact value initializations are not confirmed on
# hardware" -- so hashing it would make every hi-res scene permanently unblessable under rule 4 over
# one undefined pixel. Excluding it, all three hosts agree bit-for-bit. Which rule a scene uses is
# declared in build/scenes.tsv's fourth column (docs/adr/0013, 2026-08-02 supplement). Fixed and
Comment on lines +11 to +12
# canonical because emulators agree about neither geometry nor pixel format, and a golden has to
# compare pictures rather than output conventions. Each host declares where its own picture starts
# in its own buffer (`FIRST_ROW`): RustySNES 0, snes9x's libretro core 0, Mesen2 7.
Expand Down Expand Up @@ -202,3 +209,4 @@ c7-hflip-sliver-order 0x863f085bccebd107
c8-force-black-outside-window 0x21a5b8a828e748d7
c5-4bpp-bitplane-order 0xf11def7ad73be325
c11-mode7-product-low-bits-masked 0xc032679c9076440a
c5-mode5-hires-16px-tiles 0xf7ed8ab9ecd95d85
Loading