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

### Added

- **Pitch scales the sample rate, and `$2000` is an octave above `$1000` (`E6.02`).** The first
assertion from the S-DSP's pitch block, and the first on this cart to measure a *rate*. A single
reading of `ENDX` cannot do that — it says "finished" or "not finished", which bounds a rate on one
side only — so three tests play the same 384-sample voice and bound it on both: at `$1000` it has
not finished after six waits and has after sixteen, and at `$2000` the same six waits are enough.

Each pitch is read twice, at waits either side of where it finishes, because one reading bounds a
rate on one side only. The result is two windows — `$1000` consumes 24-64 samples per wait,
`$2000` consumes 64-128 — which both contain the documented rates, do not overlap, and cannot both
be satisfied by a core that ignores the pitch register.

The four waits were **found by bisection rather than calculated** — the first attempt placed them
by arithmetic and the voice had already finished — and then deliberately moved *away* from the
boundaries they found, so no verdict sits close to a finishing point. What the windows do not
establish is that the factor is exactly two: a core scaling by 1.5 fits both, and excluding it
would mean bracketing between adjacent waits, where every verdict is a hair's breadth from
flipping. `docs/accuracysnes-plan.md` records the trade. All four tests agree on snes9x and
Mesen2, on both images.

- **The power-on state is now reachable, and Group G reports out of it (`G1.02`, `G1.04`, `G1.08`,
`G1.09`).** The battery runs long after reset, through a runtime that deliberately puts every PPU
and CPU register into a known state — so until now the whole power-on half of Group G was
Expand Down
5 changes: 3 additions & 2 deletions docs/accuracysnes-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,23 @@ Every sub-group of Part V is enumerated, so this is a **complete** statement of
| `E3` | 14 | 7 | 0 | E3.02, E3.06, E3.07, E3.08, E3.09, E3.12, E3.13 |
| `E4` | 11 | 3 | 0 | E4.03, E4.05, E4.06, E4.07, E4.08, E4.09, E4.10, E4.11 |
| `E5` | 13 | 8 | 0 | E5.01, E5.06, E5.10, E5.12, E5.13 |
| `E6` | 11 | 0 | 0 | E6.01, E6.02, E6.03, E6.04, E6.05, E6.06, E6.07, E6.08, E6.09, E6.10, E6.11 |
| `E6` | 11 | 1 | 0 | E6.01, E6.03, E6.04, E6.05, E6.06, E6.07, E6.08, E6.09, E6.10, E6.11 |
| `E7` | 18 | 7 | 0 | E7.02, E7.03, E7.04, E7.05, E7.06, E7.07, E7.09, E7.12, E7.13, E7.17, E7.18 |
| `E8` | 11 | 1 | 0 | E8.01, E8.02, E8.03, E8.05, E8.06, E8.07, E8.08, E8.09, E8.10, E8.11 |
| `E9` | 20 | 7 | 0 | E9.01, E9.02, E9.03, E9.05, E9.07, E9.08, E9.09, E9.11, E9.13, E9.14, E9.15, E9.16, E9.20 |
| `E10` | 6 | 0 | 0 | E10.01, E10.02, E10.03, E10.04, E10.05, E10.06 |
| `F1` | 22 | 1 | 0 | F1.01, F1.03, F1.04, F1.05, F1.06, F1.07, F1.08, F1.09, F1.10, F1.11, F1.12, F1.13, F1.14, F1.15, F1.16, F1.17, F1.18, F1.19, F1.20, F1.21, F1.22 |
| `G1` | 18 | 8 | 0 | G1.01, G1.03, G1.05, G1.06, G1.07, G1.13, G1.15, G1.16, G1.17, G1.18 |

**182 of 443** enumerated assertion rows covered by an on-cart test, plus **50** covered only by a rendered scene (`docs/adr/0013`) — **232 of 443** in total.
**183 of 443** enumerated assertion rows covered by an on-cart test, plus **50** covered only by a rendered scene (`docs/adr/0013`) — **233 of 443** in total.
Comment thread
doublegate marked this conversation as resolved.

The two columns are kept apart on purpose. An on-cart result means the same thing on any emulator and on real hardware; a rendered scene needs a host holding the golden. Adding them into one figure would quietly change what the number claims.

## Assertions split across several tests

Declared in `dossier.rs::SPLITS`. Each is a claim that the tests assert different things about one enumerated behaviour; an undeclared double-claim fails the build.

- **`E6.02`** — E6.02, E6.02b, E6.02c, E6.02d · one row for a rate — and a single reading of ENDX cannot establish a rate, only "finished" or "not finished", which bounds it on one side. So each pitch is read twice, at waits either side of where it finishes: E6.02/E6.02b bracket $1000 to 24-64 samples per wait and E6.02c/E6.02d bracket $2000 to 64-128. Both windows contain the documented rate and the two do not overlap, which is the increase; none of the four means anything alone, and the pair-of-pairs is what the row is worth
- **`D1.01`** — D1.01, D1.01b · the dossier states "transfer modes 0-7, one test each" as a single row, so it is a range in all but name. Cart D1.01 covers mode 0 (every byte to one register) and D1.01b mode 1 (alternating between two) — the pair is what makes either meaningful, since a core that confuses the two still writes the right bytes to the wrong places
- **`D1.07`** — D1.07, D1.07b · one row for three address-step behaviours that share a two-bit field (0 = increment, 1 = fixed, 2 = decrement, 3 = fixed). Cart D1.07 asserts FIXED and D1.07b DECREMENT; a core that reads the field as two independent flags gets exactly one of them wrong and the other right, which either test alone would miss
- **`A5.01-08`** — A5.S01, A5.S02, A5.S03, A5.S04, A5.S05, A5.S06, A5.S07, A5.S08, A5.S09, A5.S10, A5.S11, A5.S12, A5.S13, A5.S14, A5.S15, A5.S16, A5.S17, A5.S18, A5.S19, A5.S20, A5.S21, A5.S22, A5.S23, A5.S24, A5.S25, A5.S26, A5.S27, A5.S28, A5.S29, A5.S30, A5.S31, A5.S32, A5.S33, A5.S34 · the opcode cycle sweep (T-04-I). The dossier states the base sweep as a single ranged assertion covering all 256 opcodes; the cart implements it as one test per opcode so a failure names the instruction rather than the batch. Every A5.Sxx test is one row of it
Expand Down
23 changes: 22 additions & 1 deletion docs/accuracysnes-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ AccuracySNES closed ticket **T-04**. The follow-on tickets minted here are **T-0

| | |
|---|---|
| Tests | **223** (211 scoring + 11 golden vectors + 1 region SKIP per image) |
| Tests | **227** (215 scoring + 11 golden vectors + 1 region SKIP per image) |
| Rendered scenes | **50**, all cross-validated (`docs/adr/0013`) |
| Pass rate | **100.00%**, floor enforced at 1.00 by `tests/accuracysnes.rs` |
| Cross-validated | RustySNES and Mesen2 agree on every test; snes9x agrees on every test but four, all recorded reference bugs with citations in `scripts/accuracysnes/crossval.sh`. Both images. |
Expand Down Expand Up @@ -331,6 +331,27 @@ Reaching that needs the collapse cases first, which are probabilistic on hardwar
piece of work. Parked with the measurement: `ENDX` reads 1 on RustySNES, snes9x and Mesen2 alike
after a `KOFF`+`KON` pair.

### `E6.02` — a rate needs four assertions, and the exact factor needs eight

`ENDX` reports "finished" or "not finished", which bounds a rate on one side only, so a rate
measurement costs two assertions per pitch. `E6.02`-`E6.02d` spend those four and bracket the two
rates to **24-64** and **64-128** samples per wait — windows that contain the documented 48 and 96,
that do not overlap, and that no core ignoring the pitch register can satisfy.

**What they deliberately do not establish is that the factor is two.** A core scaling by 1.5 fits
both windows. Excluding it means bracketing each rate between *adjacent* waits, which is where
bisection actually puts them (`$1000` between the seventh and eighth, `$2000` between the fourth
and fifth) — but shipping that would be four assertions with roughly a tenth of the elapsed time in
hand each, and a tenth is inside the range that has already broken APU tests here twice when an
unrelated edit moved the battery's code. A longer sample buys finer granularity in wait units and
buys it linearly, so a factor-of-ten margin improvement means a factor-of-ten longer sample and a
battery that spends a visible fraction of a second on one row.

The trade is explicit: **a tighter claim is a thinner margin, and this cartridge would rather state
a wider window it can defend on three emulators than a precise one that flips when a test above it
grows a line.** If the exact factor becomes worth having, the way in is a longer sample, not a
narrower wait.

### Group F — blocked on a *peripheral contract*, and now measured

`F1` (22 assertions) was written down as "needs a mechanism that doesn't exist". The mechanism is
Expand Down
32 changes: 32 additions & 0 deletions tests/roms/AccuracySNES/ERROR_CODES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1541,6 +1541,38 @@ Provenance: **Documented** (SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DS
|---|---|---|
| 1 | `$02` | a fully attacked envelope did not read $7F; $FF or $FE means ENVX is not E >> 4 of an eleven-bit envelope |

### E6.02 — Pitch $1000 is 1:1

Provenance: **Documented** (fullsnes, S-DSP pitch; anomie's DSP doc). Kind: scored.

| Code | Byte | Meaning |
|---|---|---|
| 1 | `$02` | a 384-sample voice at pitch $1000 had already finished after six waits, so it is consuming at least 64 samples per wait — a third above 1:1 |

### E6.02b — Pitch $1000 does finish

Provenance: **Documented** (fullsnes, S-DSP pitch; anomie's DSP doc). Kind: scored.

| Code | Byte | Meaning |
|---|---|---|
| 1 | `$02` | a 384-sample voice at pitch $1000 had still not finished after sixteen waits, so it is consuming fewer than 24 samples per wait — half of 1:1 — or not playing at all |

### E6.02c — Pitch $2000 is +1 octave

Provenance: **Documented** (fullsnes, S-DSP pitch; anomie's DSP doc). Kind: scored.

| Code | Byte | Meaning |
|---|---|---|
| 1 | `$02` | a 384-sample voice at pitch $2000 had not finished after six waits, so it is consuming fewer than 64 samples per wait — no faster than $1000 manages in the same time |

### E6.02d — Pitch $2000 upper bound

Provenance: **Documented** (fullsnes, S-DSP pitch; anomie's DSP doc). Kind: scored.

| Code | Byte | Meaning |
|---|---|---|
| 1 | `$02` | a 384-sample voice at pitch $2000 had already finished after three waits, so it is consuming at least 128 samples per wait — far above what doubling $1000 would give |

## Group F

### F1.02 — Pad reads 17+ are 1
Expand Down
Loading
Loading