From d52de7c0355cf30f8af48ed7ef5ce4e734f9511b Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Fri, 31 Jul 2026 16:20:47 -0400 Subject: [PATCH 1/3] =?UTF-8?q?test(accuracysnes):=20C7.06=20=E2=80=94=20T?= =?UTF-8?q?ime=20Over=20reads=20set=20by=20V=20=3D=20YLOC=20+=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit C7 on-cart coverage 7 -> 8 of 16. 8x8 sprites can NEVER reach Time Over. The budget is 34 tiles per line, but range evaluation stops at the 33rd in-range sprite, so 8x8 caps at 32 -- under the limit, permanently. C7.05's forty 8x8 sprites set Range Over and leave Time Over clear, and the eval-line probe reports the same for the same reason. Twenty 16x16 sprites give 40 tiles while staying under the 32-sprite range limit. That separation is itself an assertion: Time Over must set while Range Over stays clear, so a core raising the two together fails. The bracket runs across the line boundary rather than within a line, because the asserted position is fixed and there is no index to sweep: clear on the eval line V = 100, set on V = 101. Phase B samples on line 101 deliberately -- RustySNES raises the flag at HBLANK_START_DOT of the eval line, EARLIER than the assertion requires, and pinning that exact dot would fail a core raising it at (101, 0) exactly, which the assertion permits. A low-tile control (the same 20 sprites at 8x8, 20 tiles) must read clear at phase B's sampling point; without it "set" would only have meant "sprites are present". Four injections, each failing its own code: flagging on any sprite fails code 4 (the control), never setting fails code 2, setting at dot 0 fails code 1. snes9x fails it and is recorded as an expected divergence (SNES9X_KNOWN_FAILURES 13 -> 14): it already reads Time Over set on V = 100, flagging the overflow a line early because it evaluates and paints in one pass. The control passes there, so it is the position that is wrong, not the budget. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 24 + docs/accuracysnes-coverage.md | 4 +- docs/accuracysnes-plan.md | 25 +- scripts/accuracysnes/crossval.sh | 9 +- tests/roms/AccuracySNES/ERROR_CODES.md | 11 + tests/roms/AccuracySNES/SOURCE_CATALOG.tsv | 435 ++++++++-------- tests/roms/AccuracySNES/asm/tests_group_a.s | 475 ++++++++++++++---- .../AccuracySNES/build/accuracysnes-pal.sfc | Bin 262144 -> 262144 bytes .../roms/AccuracySNES/build/accuracysnes.sfc | Bin 262144 -> 262144 bytes tests/roms/AccuracySNES/gen/src/dossier.rs | 1 + tests/roms/AccuracySNES/gen/src/tests/ppu.rs | 196 ++++++++ 11 files changed, 874 insertions(+), 306 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 127ad815..07fff215 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **AccuracySNES: `C7.06` — Time Over reads set by `V = OBJ.YLOC + 1, H = 0`.** `C7` on-cart + coverage 7 → 8 of 16. + + **8x8 sprites can never reach Time Over.** The budget is 34 tiles per line but range evaluation + stops at the 33rd in-range sprite, so 8x8 caps at 32 — under the limit, permanently. Twenty 16x16 + sprites give 40 tiles while staying under the 32-sprite range limit, which also keeps the two flags + independently observable: the row asserts Time Over sets while Range Over stays **clear**, so a + core raising them together fails. + + The bracket runs across the line boundary rather than within a line, since the asserted position is + fixed and there is no index to sweep — clear on the eval line `V = 100`, set on `V = 101`. Phase B + samples on line 101 deliberately: RustySNES raises the flag at `HBLANK_START_DOT` of the eval line, + *earlier* than the assertion requires, and pinning that dot would fail a core raising it at + `(101, 0)` exactly, which the assertion permits. + + A low-tile control (the same 20 sprites at 8x8, 20 tiles) must read clear at phase B's sampling + point, or "set" would only have meant "sprites are present". Four injections, each failing its own + code: flagging on any sprite fails code 4 (the control), never setting fails code 2, and setting at + dot 0 fails code 1. + + snes9x fails it and is recorded as an expected divergence (`SNES9X_KNOWN_FAILURES` 13 → 14): it + already reads Time Over set on `V = 100`, flagging the overflow a line early because it evaluates + and paints in one pass. The control passes there, so the position is wrong, not the budget. + - **AccuracySNES: `C7.05` — Range Over trips at the 33rd in-range sprite's evaluation cycle, `H = OAM.INDEX * 2`.** A fixed bracket would be vacuous: an H-IRQ is serviced ~22-27 dots after its `HTIME`, so one tight enough to pin dot 65 is inside the latency's own uncertainty and one loose diff --git a/docs/accuracysnes-coverage.md b/docs/accuracysnes-coverage.md index 2e3b7652..91ed4b63 100644 --- a/docs/accuracysnes-coverage.md +++ b/docs/accuracysnes-coverage.md @@ -28,7 +28,7 @@ Every sub-group of Part V is enumerated, so this is a **complete** statement of | `C4` | 5 | 0 | 5 | — | | `C5` | 15 | 0 | 12 | C5.06, C5.07, C5.15 | | `C6` | 7 | 0 | 6 | C6.07 | -| `C7` | 16 | 7 | 6 | C7.06, C7.07, C7.12 | +| `C7` | 16 | 8 | 6 | C7.07, C7.12 | | `C8` | 12 | 0 | 11 | C8.09 | | `C9` | 8 | 2 | 0 | C9.01, C9.02, C9.03, C9.06, C9.07, C9.08 | | `C10` | 5 | 0 | 3 | C10.03, C10.04 | @@ -52,7 +52,7 @@ Every sub-group of Part V is enumerated, so this is a **complete** statement of | `F1` | 22 | 13 | 0 | F1.13, F1.15, F1.16, F1.17, F1.18, F1.19, F1.20, F1.21, F1.22 | | `G1` | 18 | 15 | 0 | G1.06, G1.13, G1.18 | -**293 of 443** enumerated assertion rows covered by an on-cart test, plus **53** covered only by a rendered scene (`docs/adr/0013`) — **346 of 443** in total. +**294 of 443** enumerated assertion rows covered by an on-cart test, plus **53** covered only by a rendered scene (`docs/adr/0013`) — **347 of 443** in total. 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. diff --git a/docs/accuracysnes-plan.md b/docs/accuracysnes-plan.md index d8e71115..33f9d26d 100644 --- a/docs/accuracysnes-plan.md +++ b/docs/accuracysnes-plan.md @@ -469,7 +469,7 @@ before the H counter wraps and silently returns a plausible small number. | `A5.18` | **parked** — needs a cheaper instrument | The design is sound and the numbers came out exactly as predicted, but a `BRK` round trip is 39 dots and only 3 fit inside the budget, leaving 6 dots of signal against `TOL` 2 in a difference-of-differences. See below. | | `A6.15` | needs its own design | "all 256 opcodes defined" is a *definedness* assertion, not a timing one, and `sweep.rs` deliberately covers only the unambiguous subset — its own module docs list why control flow, `BRK`/`COP`, `STP`, `WAI` and memory-addressing modes are excluded. Citing the sweep would be coverage by restatement. | | `C7.05` | **landed** | Not a fixed bracket — the set dot has to *move with the index*. See below. | -| `C7.06` | reachable, same technique | `C7.05`'s far-IRQ shim and sprite setup are reusable; `time_over` needs >34 sprite-tiles, which 40 8x8 sprites do **not** produce (range evaluation caps at 32, so 32 tiles). Use larger sprites. | +| `C7.06` | **landed** | 8x8 sprites can *never* reach Time Over — evaluation stops at 32 sprites, so 32 tiles is the ceiling against a 34 budget. Twenty 16x16 sprites give 40 tiles while staying under the range limit, which also keeps the two flags independently observable. | | `B2.07` | gated on `B2.02` | `B2.04` covers the 262-line count, not the clock total, so the frequency is not implied by it. Getting to 60.0988 Hz needs clocks-per-frame, and the frame alternates 357,368/357,364 because of the short scanline — which *is* `B2.02`, a `T-06-A` dot-model residual scheduled for `v1.29.0`. | | `B2.09` | `v1.29.0` by its own dossier note | the picture window is "not CPU-observable directly; reachable through the framebuffer oracle once the dot-resolution compositor lands". | @@ -540,6 +540,29 @@ its Range Over does not track the index — a scanline-granularity flag. RustySN anchored to MesenCE on the *line* by `scripts/probes/eval-line-213e`; the *dot* is documentation-anchored only, because the Mesen2 headless runner times out in this environment. +#### `C7.06` — 8x8 sprites cannot reach Time Over at all + +The tile budget is 34 per line, but range evaluation **stops** at the 33rd in-range sprite, so 8x8 +sprites cap out at 32 tiles — under the limit, permanently. `C7.05`'s forty 8x8 sprites set Range +Over and leave Time Over clear, and the `eval-line-213e` probe reports the same for the same reason. +Twenty 16x16 sprites give 40 tiles while staying under the 32-sprite range limit, so Time Over trips +and Range Over must **not** — which the row asserts, since a core raising the two together fails it. + +The bracket is across the line boundary rather than within a line, because the asserted position is +fixed and there is no index to sweep: clear on the eval line `V = 100`, set on `V = 101 = YLOC + 1`. +Phase B deliberately samples on line 101 rather than late on line 100 — RustySNES raises the flag at +`HBLANK_START_DOT` of the eval line, *earlier* than the assertion requires, and pinning that exact +dot would fail a core that raises it at `(101, 0)` exactly, which the assertion permits. + +A low-tile control completes it: the same 20 sprites at 8x8 (20 tiles) must read Time Over **clear** +at phase B's sampling point. Without it, a core reporting Time Over whenever any sprite is on the +line passes both A and B. Four injections, each failing its own code — flag on any sprite fails +**code 4** (the control), never setting fails **code 2**, and setting at dot 0 fails **code 1**. + +snes9x fails this row too, and is recorded as an expected divergence: it already reads Time Over set +on `V = 100`, flagging the overflow a line early because it evaluates and paints in one pass. The +control passes there, so it is the position that is wrong, not the budget. + #### `C7.05`/`C7.06` — the probe, and the two sampling traps An earlier revision of the table above said RustySNES sets `range_over` a line late against MesenCE. diff --git a/scripts/accuracysnes/crossval.sh b/scripts/accuracysnes/crossval.sh index f5e9564b..5f8dc3d4 100755 --- a/scripts/accuracysnes/crossval.sh +++ b/scripts/accuracysnes/crossval.sh @@ -156,7 +156,14 @@ ran=0 # (range evaluation walks OAM two cycles per sprite). RustySNES's per-dot position is anchored to # MesenCE on the *line* by scripts/probes/eval-line-213e; the *dot* itself is documentation-anchored # only, because the Mesen2 headless runner times out in this environment. Region-independent. -SNES9X_KNOWN_FAILURES=13 +# snes9x, +1 test (C7.06 "TimeOver by YLOC+1", fails phase A with code 1): the same scanline-granular +# sprite model as C7.05, seen from the other side. The row requires Time Over to be clear while the +# sprites' OWN line is being evaluated (V = 100) and set once the line they paint on has begun +# (V = 101 = YLOC + 1); snes9x already reads it set on V = 100, i.e. it flags the tile-budget +# overflow a line early because it evaluates and paints in the same pass. The 20-sprite/8x8 control +# passes there, so this is the position being wrong, not the budget. Documented by nocash fullsnes +# and the SNESdev Wiki (34-tile budget, raised by the fetch phase). Region-independent. +SNES9X_KNOWN_FAILURES=14 # --- snes9x, via the libretro host -------------------------------------------------------------- if [[ -f $SNES9X ]]; then diff --git a/tests/roms/AccuracySNES/ERROR_CODES.md b/tests/roms/AccuracySNES/ERROR_CODES.md index 27e562c7..abc702bd 100644 --- a/tests/roms/AccuracySNES/ERROR_CODES.md +++ b/tests/roms/AccuracySNES/ERROR_CODES.md @@ -1274,6 +1274,17 @@ Provenance: **Documented** (fullsnes and the SNESdev Wiki: range evaluation walk | 2 | `$04` | Range Over was already set at dot ~105 with the 33rd in-range sprite at OAM index 72 (dot 145) — the set dot does not track OAM.INDEX * 2 | | 3 | `$06` | Range Over never set at all in phase B, so its clear reading at dot ~105 proved nothing | +### C7.06 — TimeOver by YLOC+1 + +Provenance: **Documented** (fullsnes and the SNESdev Wiki: the sprite tile budget is 34 per line and Time Over is raised by the fetch phase, so it reads set by the start of the line the sprites paint on). Kind: scored. + +| Code | Byte | Meaning | +|---|---|---| +| 1 | `$02` | Time Over was already set on the sprites' own line (V = 100), earlier than V = YLOC + 1 | +| 2 | `$04` | Time Over had not set by V = YLOC + 1 with 40 sprite-tiles due on the line | +| 3 | `$06` | Range Over set alongside Time Over with only 20 sprites on the line — the two flags are being raised together instead of on their own conditions | +| 4 | `$08` | Time Over set with only 20 sprite-tiles due, so phase B's reading meant 'sprites are present', not 'the tile budget was exceeded' | + ### C7.02 — Time Over is slivers Provenance: **Documented** (SNESdev Wiki, Sprites; fullsnes; anomie). Kind: scored. diff --git a/tests/roms/AccuracySNES/SOURCE_CATALOG.tsv b/tests/roms/AccuracySNES/SOURCE_CATALOG.tsv index 49815317..9f92c3a4 100644 --- a/tests/roms/AccuracySNES/SOURCE_CATALOG.tsv +++ b/tests/roms/AccuracySNES/SOURCE_CATALOG.tsv @@ -117,220 +117,221 @@ 114 C7.01 C Range Over at 32 sprites Documented SNESdev Wiki, Sprites; fullsnes Scored 0x7EF092 C7.01 115 C7.04 C Offscreen X takes a slot Documented fullsnes and the SNESdev Wiki: sprite range evaluation selects on Y alone, so a sprite at X = $100 occupies a range slot despite being entirely off-screen Scored 0x7EF093 C7.04 116 C7.05 C RangeOver dot = idx*2 Documented fullsnes and the SNESdev Wiki: range evaluation walks OAM two cycles per sprite, so the 33rd in-range sprite trips Range Over at H = OAM.INDEX * 2 on V = OBJ.YLOC Scored 0x7EF094 C7.05 -117 C7.02 C Time Over is slivers Documented SNESdev Wiki, Sprites; fullsnes; anomie Scored 0x7EF095 C7.02 -118 C7.08 C Flags ignore $212C Documented SNESdev Wiki, Sprites; fullsnes Scored 0x7EF096 C7.08 -119 C7.10 C OAM write to high table Documented fullsnes and the SNESdev Wiki (the Uniracers case): an OAM write during active display is driven to the evaluator's address and lands in the high table, not the CPU OAMADDR. Mesen2 models it (oamAddr = 0x200 | ((oamAddr & 0x1F0) >> 4)); the batch compositor and snes9x write the programmed OAMADDR and fail Scored 0x7EF097 C7.16 -120 C2.11 C VRAM locked in render Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF098 C2.11 -121 C1.08 C OAM addr in render Documented fullsnes and the SNESdev Wiki: during active display the renderer drives the OAM address, so a $2138 read returns a render-time address, not the CPU-programmed one. MesenCE models it (GetOamAddress -> _oamEvaluationIndex<<2); the batch compositor and snes9x return the programmed address and fail Scored 0x7EF099 C1.08 -122 C2.10 C Dropped write still incs Documented SNESdev Wiki, PPU registers; fullsnes; anomie Scored 0x7EF09A C2.10 -123 C1.06 C OAM addr reloads Documented SNESdev Wiki, OAM; anomie Scored 0x7EF09B C1.06 -124 C9.04 C Overscan moves vblank Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF09C C9.04,B4.02 -125 C9.05 C Mid-frame overscan lock Contested RustySNES and snes9x re-close the VRAM window on a mid-frame overscan enable and Mesen2 does not, so the references split one each way; the dossier's repro cannot break the tie because it read-modify-writes the write-only $2133 Golden 0x7EF09D C9.05 -126 C2.09 C VRAM read latch order Documented SNESdev Wiki, PPU registers: return latch, refill latch, then increment, with VMAIN bit 7 selecting which of $2139/$213A triggers it; bsnes and ares both refill before the step in sfc/ppu/io.cpp, and snes9x and Mesen2 agree Scored 0x7EF09E C2.09 -127 C3.10 C $2137 latch is gated Documented superfamicom.org registers: $2137 latches the H/V counter only if $4201 bit 7 is set, and no latching can occur while it is 0; snes9x and Mesen2 both gate it. What the read returns is a separate question, split out into C3.11 Scored 0x7EF09F C3.05 -128 C3.11 C $2137 open bus source Contested the sources say only that $2137 reads back as open bus; snes9x and RustySNES present PPU1's latch while Mesen2 presents the CPU's, and nothing available decides between two physically reasonable readings Golden 0x7EF0A0 C3.05 -129 B1.01 B MEMSEL selects FastROM Documented SNESdev Wiki, Memory map / timing; fullsnes Scored 0x7EF0A1 B1.01 -130 B1.02 B JOYSER is 12 clocks Documented SNESdev Wiki, Memory map / timing; fullsnes Scored 0x7EF0A2 B1.02 -131 B2.04 B NTSC frame is 262 lines Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A3 B2.04 -132 B4.03 B RDNMI sets at vblank Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A4 B4.03 -133 B2.01 B No dot above 339 Corroborated fullsnes' PPU H-Counter-Latch Quantities histogram, a direct hardware measurement: dots 323 and 327 latch six times, dot 340 never. bsnes, ares and Mesen2 all implement it; snes9x uses 322/326 and is the outlier Scored 0x7EF0A5 B2.01 -134 B4.16 B H-IRQ position (golden) Contested no source pins the fired dot at single-dot precision; recorded as the before/after guard for T-06-A's clock-domain comparator change Golden 0x7EF0A6 - -135 B4.17 B NMI enable is a level Documented SNESdev Wiki NMITIMEN/RDNMI [ERRATA]; fullsnes $4200/$4210 Scored 0x7EF0A7 B4.06 -136 B4.04 B RDNMI is read-to-clear Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A8 B4.04 -137 B4.05 B RDNMI auto-clears Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A9 B4.05 -138 B4.08 B V-IRQ fires at VTIME Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0AA B4.08 -139 B4.12 B $4211 read releases IRQ Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0AB B4.12 -140 B4.15 B CPU revision (golden) Documented SNESdev Wiki, Timing; fullsnes Golden 0x7EF0AC G1.09 -141 B5.01 B 8x8 unsigned multiply Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AD B5.01 -142 B5.02 B 16/8 unsigned divide Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AE B5.02 -143 B5.03 B Divide by zero Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AF - -144 B5.04 B Mul/div overlap (golden) Contested SNESdev Errata states overlapping $4203/$4206 operation is undefined Golden 0x7EF0B0 B5.03 -145 B5.05 B Mul/div power-on state Documented anomie regs.txt r1157 and nocash fullsnes, independently; implemented by bsnes/ares/Mesen2. No known hardware test ROM Scored 0x7EF0B1 B5.04 -146 B1.03 B Internal cycles are 6 Documented SNES Development Manual Bk I 21.1; SNESdev Wiki; fullsnes Scored 0x7EF0B2 B1.03 -147 B1.04 B DMA speed is uniform Documented SNES Development Manual Bk I 21.1 (DMA at 2.68MHz regardless of address) Scored 0x7EF0B3 B1.04 -148 B2.06 B Interlace line count Contested the dossier conditions the extra line on $213F.7 (the field), which this test does not control — sampling V across an uncontrolled field cannot assert a line count Golden 0x7EF0B4 B2.06 -149 B2.05 B PAL frame is 312 lines Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0B5 B2.05 -150 B4.14 B IRQ dispatch latency Documented SNESdev Wiki, Timing; fullsnes — the sub-cycle poll point is not CPU-observable, so its consequence is measured instead Golden 0x7EF0B6 B4.14 -151 B2.10 B Region bit (golden) Contested SNESdev PPU registers places the 50/60Hz bit at bit 3, which overlaps the PPU2 version field; fullsnes places it at bit 4 Golden 0x7EF0B7 B2.10 -152 B4.07 B H-IRQ position (golden) Contested the $4211 poll loop is coarser than the dot the comparator fires on, so the exact H position is not resolvable from software by polling Golden 0x7EF0B8 B4.07 -153 B4.09 B HV-IRQ needs both Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0B9 B4.09 -154 B3.01 B DRAM refresh pause Contested fullsnes and anomie put the pause at 40 clocks near line-clock 536, and RustySNES now models it there (docs/dram-refresh.md), so this loop records variant 2 (one interval ~10 dots longer); ares' own source still calls its refresh *pattern* technically wrong and only right on average, so the exact per-line position stays host-cross-validated rather than self-asserted Golden 0x7EF0BA B3.01,B3.02,B3.03 -155 B4.13 B Timer range is 9-bit Documented fullsnes $4207-$420A: HTIME is 0-339 and VTIME 0-261 (NTSC) / 0-311 (PAL), both held in nine bits Scored 0x7EF0BB B4.13 -156 B4.11 B Dot 153, last line Contested superfamicom.org's timing page states the exception and gives no mechanism; its timing text derives from fullsnes, so the two are one source, and no test ROM verifies it. ares, bsnes, Mesen2 and snes9x were each searched and none implements it Golden 0x7EF0BC B4.11 -157 D1.01 D DMA mode 0 Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0BD D1.01 -158 D1.01b D DMA mode 1 Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0BE D1.01 -159 D1.06 D DMA count hits zero Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0BF D1.06 -160 D1.07 D DMA fixed A-bus Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0C0 D1.07 -161 D1.07b D DMA decrementing A-bus Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0C1 D1.07 -162 D1.10 D DMA $43xB scratch latch Corroborated ares and bsnes both model the latch and serialize it Scored 0x7EF0C2 D1.10 -163 D1.02 D DMA 8 clocks/byte Documented SNESdev Wiki, DMA timing; fullsnes Scored 0x7EF0C3 D1.02 -164 D1.05 D DMA count 0 = 65536 Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0C4 D1.05 -165 D1.09 D WRAM->$2180 no-write Documented fullsnes: "does not cause a write to occur" Scored 0x7EF0C5 D1.09,D1.15 -166 D2.03 D HDMA line-count byte Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0C6 D2.03 -167 D2.04 D HDMA repeat flag Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0C7 D2.04 -168 D2.07 D HDMA preempts GP-DMA Documented SNESdev Wiki, HDMA; anomie's timing doc; fullsnes Scored 0x7EF0C8 D2.07 -169 D2.09 D HDMA armed mid-frame Contested fullsnes and the SNESdev Wiki record that enabling HDMA outside vblank produces erroneous writes from uninitialised A2An/NLTRn, but what those writes contain is a function of the previous frame's leftover state and is specified nowhere Golden 0x7EF0C9 D2.09 -170 D1.14 D $2180 B->A does write Documented fullsnes: $2180->WRAM writes, but the value written is invalid Scored 0x7EF0CA D1.14 -171 D1.13 D DMA count hits zero Documented fullsnes and ares: the DAS $43x5/6 byte-count register decrements as GP-DMA transfers and reads $0000 when the transfer completes Scored 0x7EF0CB D1.13 -172 D1.11 D DMA power-on state Corroborated fullsnes register table and the SNESdev DMA-registers page agree independently; ares and bsnes default every channel field to match Scored 0x7EF0CC D1.11 -173 D1.08 D Invalid A-bus (golden) Contested the errata names the ranges invalid but does not specify what is read instead; the substitute is open bus, whose content is core-specific and time-dependent Golden 0x7EF0CD D1.08 -174 D1.03 D DMA startup overhead Documented SNESdev Wiki, DMA timing; fullsnes Golden 0x7EF0CE D1.03 -175 D1.04 D DMA channel priority Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0CF D1.04 -176 D2.05 D HDMA indirect mode Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0D0 D2.05 -177 D2.06 D HDMA $4308/$430A state Documented SNESdev Wiki, HDMA registers; fullsnes Scored 0x7EF0D1 D2.06 -178 E4.11 E ARAM power-on pattern Contested the dossier records a repeating 32x$00 / 32x$FF fill and marks it chip-dependent and informational; RustySNES, snes9x and Mesen2 all boot APU RAM uniformly zero instead Golden 0x7EF0D2 E4.11 -179 E1.01 E MUL YA flags from Y Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D3 E1.01 -180 E1.02 E DIV YA,X normal branch Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D4 E1.02 -181 E1.07 E DIV valid to Q<=511 Documented SNESdev Wiki SPC700 reference and fullsnes, both flagging DIV as valid only for quotients up to 511; the values past it follow E1.03's overflow formula Scored 0x7EF0D5 E1.07 -182 E1.04 E DIV H = nibble compare Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D6 E1.04 -183 E1.05 E DIV V is quotient bit 8 Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D7 E1.05 -184 E1.06 E DIV flags from quotient Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D8 E1.06 -185 E1.13 E ADDW H = bit-11 carry Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D9 E1.13 -186 E1.15 E MOVW YA sets 16-bit N/Z Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0DA E1.15 -187 E3.01 E Timer read clears it Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0DB E3.01 -188 E3.02 E Timer enable 0->1 resets Documented SNESdev Wiki SPC700 timers and fullsnes: a 0->1 on a $F1 timer-enable bit resets that timer's stage-2 divider and stage-3 output counter Scored 0x7EF0DC E3.02 -189 E3.11 E $F2 bit 7 blocks writes Documented SNESdev Wiki, S-DSP; fullsnes Scored 0x7EF0DD E3.11 -190 E3.11b E DSP register addressing Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0DE - -191 E2.01 E Store dummy-reads target Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0DF E2.01 -192 E2.05 E DP index wraps in page Documented SNESdev Wiki, SPC700 addressing; fullsnes Scored 0x7EF0E0 E2.05 -193 E6.09 E Gaussian sum wraps Documented fullsnes and anomie's DSP doc: of the four gaussian additions the first cannot overflow, the second wraps in 16 bits and the third saturates Scored 0x7EF0E1 E6.09 -194 E6.11 E BRR waveform vectors Contested the dossier names four nibble patterns and asks what a decoder makes of each without stating an expected value for any of them; the row's content is the measurement Golden 0x7EF0E2 E6.11 -195 E10.01 E 32 SPC cycles per sample Documented fullsnes and anomie: the SPC700's 1.024 MHz clock and the DSP's 32 kHz output rate fix the ratio at 32 CPU cycles per output sample Scored 0x7EF0E3 E10.01 -196 E10.05 E Soft reset acts as $E0 Contested both sources agree FLG bit 7 makes the DSP behave as $E0 and force every voice into release, and contradict each other on what ENDX then reads: nocash says $FF, anomie says 0. The dossier marks the row [CONFLICT] and asks for a golden vector Golden 0x7EF0E4 E10.05 -197 E1.14 E XCN costs five cycles Documented the SNESdev Wiki SPC700 reference and fullsnes both give XCN as 5 cycles, against 2 for NOP Scored 0x7EF0E5 E1.14 -198 E2.04 E DBNZ dp is an RMW Documented SNESdev Wiki SPC700 reference and fullsnes: DBNZ dp,rel reads its operand, decrements it and writes it back, and $FD-$FF are read-to-clear Scored 0x7EF0E6 E2.04 -199 E3.14 E $F8/$F9 are plain RAM Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0E7 E3.14 -200 E3.11c E DSP global registers Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0E8 - -201 E9.19 E ENDX write clears it Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0E9 E9.19 -202 E9.03 E Pitch not noise rate Documented fullsnes and anomie's DSP doc: the noise generator's step rate is set by FLG bits 0-4, and a voice's pitch register does not participate Scored 0x7EF0EA E9.03 -203 E9.01 E Noise LFSR seed Documented fullsnes and anomie's DSP doc: the noise shift register resets to $4000, with taps bit0 XOR bit1 feeding bit 14 Scored 0x7EF0EB E9.01 -204 E5.07 E End+mute zeroes env Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0EC E5.07 -205 E5.08 E Loop flag without end Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0ED E5.08 -206 E5.09 E ENDX sets on end block Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0EE E5.09 -207 E5.01 E BRR header layout Documented fullsnes and anomie's DSP doc: the BRR header is ssssffle, shift in bits 7-4, and a nibble decodes as (nibble << shift) >> 1 Scored 0x7EF0EF E5.01 -208 E5.10 E Released voice decodes Documented fullsnes and anomie's DSP doc: key-off begins the release ramp and does not halt BRR decoding, which continues to follow loop points and set ENDX Scored 0x7EF0F0 E5.10 -209 E5.11 E Directory entry address Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0F1 E5.11 -210 E7.10 E Direct GAIN is envelope Documented SNESdev Wiki, S-DSP envelopes; fullsnes Scored 0x7EF0F2 E7.10 -211 E1.03 E DIV overflow branch Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0F3 E1.03 -212 E1.08 E DAA adjustments Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F4 E1.08 -213 E2.08 E TCALL vector table Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F5 E2.08 -214 E2.09 E BRK shares TCALL 0 Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F6 E2.09 -215 E3.03 E $F1 bit 5 clears port 3 Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0F7 E3.03 -216 E3.04 E Writes pass under IPL Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0F8 E3.04 -217 E3.05 E TnDIV $00 means 256 Documented SNESdev Wiki, SPC700 timers; fullsnes Scored 0x7EF0F9 E3.05 -218 E3.10 E TEST gates RAM writes Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0FA E3.10 -219 E1.09 E DAS mirrors DAA Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0FB E1.09 -220 E1.10 E TSET1 is a compare Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0FC E1.10 -221 E1.12 E CLRV clears H too Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0FD E1.12 -222 E2.02 E MOV dp,dp is exempt Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0FE E2.02 -223 E2.03 E MOVW reads the low byte Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0FF E2.03 -224 E2.06 E PSW.P selects the page Documented SNESdev Wiki, SPC700 addressing; fullsnes Scored 0x7EF100 E2.06 -225 E2.07 E CALL pushes exact addr Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF101 E2.07 -226 E4.01 E IPL ROM contents Documented the canonical 64-byte IPL listing; fullsnes, SNESdev Wiki Scored 0x7EF102 E4.01 -227 E4.02 E IPL handoff state Documented fullsnes, SNESdev Wiki, APU boot handshake Scored 0x7EF103 E4.02 -228 E4.03 E IPL zerofills $00-$EF Documented the canonical 64-byte IPL listing zero-fills $0000-$00EF as the first thing it does at $FFC0, before entering its transfer loop; fullsnes and the SNESdev Wiki both carry it Scored 0x7EF104 E4.03 -229 E4.04 E IPL ready announcement Documented fullsnes, SNESdev Wiki, APU boot handshake Scored 0x7EF105 E4.04 -230 E4.06 E IPL multi-block continue Documented fullsnes, SNESdev Wiki; canonical IPL boot ROM $FFEF-$FFFB Scored 0x7EF106 E4.06 -231 E4.08 E IPL DSP-poke via $00F2 Documented fullsnes (APU / S-DSP); canonical IPL boot ROM store loop Scored 0x7EF107 E4.08 -232 E5.12 E SRCN change source Documented fullsnes and anomie's DSP doc: a mid-playback SRCN change takes the new entry's start address, or its loop address if the voice has already passed a loop point Scored 0x7EF108 E5.12 -233 E5.02 E BRR nibbles are signed Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF109 E5.02 -234 E7.16 E OUTX is pre-volume Documented fullsnes, S-DSP envelopes; anomie's DSP doc Scored 0x7EF10A E7.16 -235 E8.04 E KOFF outranks KON Documented fullsnes, S-DSP key on/off; anomie's DSP doc Scored 0x7EF10B E8.04 -236 E8.05 E KON is edge-triggered Documented fullsnes and anomie's DSP doc: KON is write-triggered and self-clears ~63 clocks later; only KOFF and FLG bit 7 are level-sensitive Scored 0x7EF10C E8.05 -237 E9.04 E Noise voices decode BRR Documented fullsnes, S-DSP noise; anomie's DSP doc — flagged as errata Scored 0x7EF10D E9.04 -238 E9.06 E EDL 0 is a 4-byte buffer Documented fullsnes, S-DSP echo — flagged as errata; anomie's DSP doc Scored 0x7EF10E E9.06 -239 E9.12 E Echo writes are masked Documented fullsnes, S-DSP echo; anomie's DSP doc Scored 0x7EF10F E9.12 -240 E9.15 E Voice mix saturates Documented fullsnes and anomie's DSP doc: the per-voice mix clamps to 16 bits after each addition rather than accumulating and clamping once Scored 0x7EF110 E9.15 -241 E9.05 E Echo entry is 4 bytes Documented fullsnes and anomie's DSP doc: the echo buffer holds four bytes per sample, a 16-bit left sample followed by a 16-bit right one Scored 0x7EF111 E9.05 -242 E9.13 E L/R FIR independent Documented fullsnes and anomie's DSP doc: the echo FIR keeps a separate eight-sample history and accumulator per channel; only the coefficients are shared Scored 0x7EF112 E9.13 -243 E9.10 E FLG.5 stops echo writes Documented fullsnes, S-DSP echo; anomie's DSP doc Scored 0x7EF113 E9.10 -244 E9.17 E Mute is after OUTX Documented fullsnes, S-DSP FLG; anomie's DSP doc Scored 0x7EF114 E9.17 -245 E9.18 E FLG reset kills voices Documented SNESdev Wiki, S-DSP; fullsnes Scored 0x7EF115 E9.18 -246 E5.03 E BRR sample arithmetic Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF116 E5.03 -247 E5.04 E Invalid shift collapses Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF117 E5.04 -248 E5.05 E BRR filter 1 Documented fullsnes, S-DSP BRR filters; anomie's DSP doc Scored 0x7EF118 E5.05 -249 E5.13 E Released voices decode Documented fullsnes and anomie's DSP doc: the BRR decoder advances on the pitch clock regardless of the envelope; voices never actually stop decoding Scored 0x7EF119 E5.13 -250 E7.01 E Rate 0 never fires Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF11A E7.01 -251 E7.13 E GAIN bent-increase Documented fullsnes and anomie's DSP doc: GAIN mode 7 increases +32 per sample below $600 and +8 above, comparing the internal envelope latch unsigned Scored 0x7EF11B E7.13 -252 E7.17 E Lin-decrease clamps 0 Documented fullsnes and anomie's DSP doc: GAIN linear-decrease subtracts $20 per tick and clamps the envelope to zero on underflow, comparing the internal envelope unsigned Scored 0x7EF11C E7.17 -253 E7.18 E ENVX is E>>4 Documented fullsnes and anomie's DSP doc: VxENVX = envelope >> 4, a seven-bit value with bit 7 always clear Scored 0x7EF11D E7.18 -254 E7.04 E Attack $F is instant Documented fullsnes and anomie's DSP doc: attack rate $F fires every sample with a step of +1024, rather than the +32 on a counter tick every other rate uses Scored 0x7EF11E E7.04 -255 E7.09 E Release rate is fixed Documented fullsnes and anomie's DSP doc: release steps -8 per sample regardless of ADSR, which is why a custom release has to be built from GAIN Scored 0x7EF11F E7.09 -256 E7.05 E Decay index d*2+16 Documented fullsnes and anomie's DSP doc: the decay phase indexes the counter table at d*2+16, stepping E -= 1 then E -= E>>8 Scored 0x7EF120 E7.05 -257 E7.06 E Sustain index verbatim Documented fullsnes and anomie's DSP doc: the sustain phase indexes the counter table with the ADSR2 rate field directly, unlike decay's d*2+16 Scored 0x7EF121 E7.06 -258 E7.07 E Sustain boundary Documented fullsnes and anomie's DSP doc: the decay phase ends when (E >> 8) equals the ADSR2 sustain level, giving a boundary of $100 per level Scored 0x7EF122 E7.07 -259 E7.03 E Attack index a*2+1 Documented fullsnes and anomie's DSP doc: the attack phase indexes the counter table at a*2+1 and steps the envelope by +32 Scored 0x7EF123 E7.03 -260 E7.12 E GAIN sustain boundary Contested the dossier records the GAIN-sourced boundary as [ERRATA]; RustySNES takes it from ADSR2 instead, which E7.07 asserts, so which behaviour is right is not settled here Golden 0x7EF124 E7.12 -261 E7.08 E Key-off releases to zero Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF125 E7.08 -262 E8.02 E Key-on takes 5 samples Documented fullsnes and anomie's DSP doc: KON is held for five output samples while the directory and the first BRR block are fetched, before the envelope starts Scored 0x7EF126 E8.02 -263 E8.03 E KON restarts a voice Documented fullsnes and anomie's DSP doc: KON re-enters the key-on sequence unconditionally, resetting the BRR pointer and zeroing the envelope, whether or not the voice was already playing Scored 0x7EF127 E8.03 -264 E8.07 E KOFF pulse collapses Contested KON/KOFF are sampled every second output sample, so whether a short pulse is seen depends on where the poll falls inside it -- which makes the outcome phase-dependent rather than fixed, the same hedge E8.05 and E8.06 carry as "usually" Golden 0x7EF128 E8.07 -265 E8.10 E KOFF+KON cuts faster Documented fullsnes and anomie's DSP doc: KON zeroes the envelope before its attack, and KOFF outranks KON, so the pair silences immediately where KOFF alone ramps Scored 0x7EF129 E8.10 -266 E7.11 E GAIN linear increase Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF12A E7.11 -267 E7.14 E GAIN decrease clamps Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF12B E7.14 -268 E7.15 E ENVX is E >> 4 Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF12C E7.15 -269 E6.02 E Pitch $1000 is 1:1 Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12D E6.02 -270 E6.02b E Pitch $1000 does finish Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12E E6.02 -271 E6.02c E Pitch $2000 is +1 octave Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12F E6.02 -272 E6.02d E Pitch $2000 upper bound Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF130 E6.02 -273 E3.06 E T2 is eight times T0 Documented SNESdev Wiki, SPC700 timers; fullsnes Scored 0x7EF131 E3.06 -274 E3.08 E TEST bit 0 halts timers Documented fullsnes, SPC700 TEST register; ares and bsnes smp/timing Scored 0x7EF132 E3.08 -275 F1.01 F Manual pad read order Documented fullsnes and the SNESdev Wiki controller protocol: the shift register presents B, Y, Select, Start, Up, Down, Left, Right, A, X, L, R and then four zero bits Scored 0x7EF133 F1.01 -276 F1.02 F Pad reads 17+ are 1 Documented SNESdev Wiki, controller protocol; fullsnes Scored 0x7EF134 F1.02 -277 F1.03 F Shared $4016 latch Documented fullsnes and the SNESdev Wiki controller protocol: bit 0 of $4016 is the shared latch line that parallel-loads both controller ports' shift registers Scored 0x7EF135 F1.03 -278 F1.04 F $4016 bits 7-2 open bus Corroborated RustySNES, snes9x and Mesen2 all return $41 for the absolute read and $01 for the long one -- identical bytes, so bits 7-2 follow the CPU bus in all three Scored 0x7EF136 F1.04 -279 F1.07 F Auto-read needs $4200.0 Documented fullsnes and the SNESdev Wiki: bit 0 of $4200 arms the automatic joypad read, and with it clear $4218-$421F are not written Scored 0x7EF137 F1.07 -280 F1.05 F Pad signature is 0000 Documented fullsnes and the SNESdev Wiki: bits 3-0 of the auto-read result identify the device, and a standard controller reports 0000 Scored 0x7EF138 F1.05 -281 F1.06 F First bit clocked is B Documented fullsnes and the SNESdev Wiki: the auto-read result holds the sixteen shifted bits in clock order, most significant first, so B is bit 15 Scored 0x7EF139 F1.06 -282 F1.08 F Auto-read start dot Documented fullsnes: the automatic joypad read begins ~dot 32.5-95.5 of the first vblank line (RustySNES delays it 256 master clocks ~ dot 64), not at the vblank edge Golden 0x7EF13A F1.08 -283 F1.09 F Auto-read duration Documented fullsnes: the automatic joypad read is busy for exactly 4224 master cycles (~3.097 scanlines); the poll count is a CPU-cycle-proportional proxy for it Golden 0x7EF13B F1.09 -284 F1.10 F Auto-read start race Documented fullsnes: the automatic joypad read begins ~dot 32.5-95.5 of the first vblank line, not at the vblank edge, so $4212 bit 0 reads not-busy for that window and a $4212 poll at NMI entry sees the read not yet started Scored 0x7EF13C F1.10 -285 F1.11 F Latch corrupts auto-read Documented fullsnes and the SNESdev Wiki: while $4016 bit 0 is high the shift registers reload continuously rather than shifting, so an automatic read taken across it returns the same bit in every position Scored 0x7EF13D F1.11 -286 F1.12 F Auto-read result timing Contested F1.12 says results are valid by V = $E3, which does not reconcile with F1.09's 4224-cycle duration and F1.08's start window; no source says which to believe, and the cores split on whether the result appears at once or progressively Golden 0x7EF13E F1.12 -287 F1.14 F $4213 reads $4201 back Documented fullsnes: RDIO reads the WRIO output pins, which are open-collector, so with nothing driving them low the value read is the value written Scored 0x7EF13F F1.14 -288 G1.02 G Reset: $4210/$4211 clear Documented SNESdev Wiki, power-on state; fullsnes Scored 0x7EF140 G1.02 -289 G1.04 G Reset: emulation mode Documented SNESdev Wiki, power-on state; WDC 65C816 datasheet, XCE Scored 0x7EF141 G1.04 -290 G1.05 G Power-on PPU registers Documented the dossier marks the PPU power-on state indeterminate ('no boot ROM; most PPU registers start unknown') and says to report it, never assert; the readable registers ($2134-$2136, $213E/$213F) are reported and the only scored check is the self-guard that the power-on capture actually ran Scored 0x7EF142 G1.05 -291 G1.08 G Write-only read: openbus Documented SNESdev Wiki, open bus; fullsnes, memory map notes Scored 0x7EF143 G1.08 -292 G1.10 G Checksum XOR complement Documented SNESdev Wiki, cartridge header; fullsnes Scored 0x7EF144 G1.10 -293 G1.11 G Checksum over the image Documented SNESdev Wiki, cartridge header checksum; fullsnes Scored 0x7EF145 G1.11 -294 G1.12 G LoROM header location Documented SNESdev Wiki, cartridge header; fullsnes Scored 0x7EF146 G1.12 -295 G1.14 G LoROM bank decode Documented SNESdev Wiki, memory map; fullsnes Scored 0x7EF147 G1.14 -296 G1.19 G Power-on $4201/timers Documented fullsnes and the SNESdev Wiki power-on table: $4201 = $FF, HTIME and VTIME = $1FF; the other registers in the row are observed by B5.05 and B1.01 Scored 0x7EF148 G1.01 -297 G1.20 G Power-on indeterminate Contested the dossier marks the whole row [UNDEFINED] and says to report it and never assert it; half the registers it names are write-only and cannot be reported at all Golden 0x7EF149 G1.03 -298 G1.07 G WRAM power-on fill Contested the dossier marks the row [UNDEFINED] and asks for a golden vector by name: no canonical WRAM fill exists and real consoles disagree Golden 0x7EF14A G1.07 -299 A5.S01 A Sweep: CLC Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14B A5.01-08 -300 A5.S02 A Sweep: SEC Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14C A5.01-08 -301 A5.S03 A Sweep: CLV Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14D A5.01-08 -302 A5.S04 A Sweep: INX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14E A5.01-08 -303 A5.S05 A Sweep: DEX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14F A5.01-08 -304 A5.S06 A Sweep: TAX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF150 A5.01-08 -305 A5.S07 A Sweep: TXY Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF151 A5.01-08 -306 A5.S08 A Sweep: ASL A Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF152 A5.01-08 -307 A5.S09 A Sweep: XBA Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF153 A5.01-08 -308 A5.S10 A Sweep: TCD Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF154 A5.01-08 -309 A5.S11 A Sweep: LDA #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF155 A5.01-08 -310 A5.S12 A Sweep: LDX #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF156 A5.01-08 -311 A5.S13 A Sweep: CMP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF157 A5.01-08 -312 A5.S14 A Sweep: BIT #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF158 A5.01-08 -313 A5.S15 A Sweep: REP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF159 A5.01-08 -314 A5.S16 A Sweep: SEP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15A A5.01-08 -315 A5.S17 A Sweep: WDM Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15B A5.01-08 -316 A5.S18 A Sweep: PHA+PLA Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15C A5.01-08 -317 A5.S19 A Sweep: PHP+PLP Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15D A5.01-08 -318 A5.S20 A Sweep: PHB+PLB Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15E A5.01-08 -319 A5.S21 A Sweep: PHD+PLD Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15F A5.01-08 -320 A5.S22 A Sweep: PHX+PLX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF160 A5.01-08 -321 A5.S23 A Sweep: LDA dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF161 A5.01-08 -322 A5.S24 A Sweep: LDA abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF162 A5.01-08 -323 A5.S25 A Sweep: LDA long Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF163 A5.01-08 -324 A5.S26 A Sweep: STA dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF164 A5.01-08 -325 A5.S27 A Sweep: STA abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF165 A5.01-08 -326 A5.S28 A Sweep: LDA dp,X Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF166 A5.01-08 -327 A5.S29 A Sweep: LDA abs,X Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF167 A5.01-08 -328 A5.S30 A Sweep: INC dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF168 A5.01-08 -329 A5.S31 A Sweep: INC abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF169 A5.01-08 -330 A5.S32 A Sweep: ADC dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16A A5.01-08 -331 A5.S33 A Sweep: CMP abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16B A5.01-08 -332 A5.S34 A Sweep: BVS untaken Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16C A5.01-08 -333 A5.S35 A Sweep: BRL flat 4 Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16D A5.16 +117 C7.06 C TimeOver by YLOC+1 Documented fullsnes and the SNESdev Wiki: the sprite tile budget is 34 per line and Time Over is raised by the fetch phase, so it reads set by the start of the line the sprites paint on Scored 0x7EF095 C7.06 +118 C7.02 C Time Over is slivers Documented SNESdev Wiki, Sprites; fullsnes; anomie Scored 0x7EF096 C7.02 +119 C7.08 C Flags ignore $212C Documented SNESdev Wiki, Sprites; fullsnes Scored 0x7EF097 C7.08 +120 C7.10 C OAM write to high table Documented fullsnes and the SNESdev Wiki (the Uniracers case): an OAM write during active display is driven to the evaluator's address and lands in the high table, not the CPU OAMADDR. Mesen2 models it (oamAddr = 0x200 | ((oamAddr & 0x1F0) >> 4)); the batch compositor and snes9x write the programmed OAMADDR and fail Scored 0x7EF098 C7.16 +121 C2.11 C VRAM locked in render Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF099 C2.11 +122 C1.08 C OAM addr in render Documented fullsnes and the SNESdev Wiki: during active display the renderer drives the OAM address, so a $2138 read returns a render-time address, not the CPU-programmed one. MesenCE models it (GetOamAddress -> _oamEvaluationIndex<<2); the batch compositor and snes9x return the programmed address and fail Scored 0x7EF09A C1.08 +123 C2.10 C Dropped write still incs Documented SNESdev Wiki, PPU registers; fullsnes; anomie Scored 0x7EF09B C2.10 +124 C1.06 C OAM addr reloads Documented SNESdev Wiki, OAM; anomie Scored 0x7EF09C C1.06 +125 C9.04 C Overscan moves vblank Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF09D C9.04,B4.02 +126 C9.05 C Mid-frame overscan lock Contested RustySNES and snes9x re-close the VRAM window on a mid-frame overscan enable and Mesen2 does not, so the references split one each way; the dossier's repro cannot break the tie because it read-modify-writes the write-only $2133 Golden 0x7EF09E C9.05 +127 C2.09 C VRAM read latch order Documented SNESdev Wiki, PPU registers: return latch, refill latch, then increment, with VMAIN bit 7 selecting which of $2139/$213A triggers it; bsnes and ares both refill before the step in sfc/ppu/io.cpp, and snes9x and Mesen2 agree Scored 0x7EF09F C2.09 +128 C3.10 C $2137 latch is gated Documented superfamicom.org registers: $2137 latches the H/V counter only if $4201 bit 7 is set, and no latching can occur while it is 0; snes9x and Mesen2 both gate it. What the read returns is a separate question, split out into C3.11 Scored 0x7EF0A0 C3.05 +129 C3.11 C $2137 open bus source Contested the sources say only that $2137 reads back as open bus; snes9x and RustySNES present PPU1's latch while Mesen2 presents the CPU's, and nothing available decides between two physically reasonable readings Golden 0x7EF0A1 C3.05 +130 B1.01 B MEMSEL selects FastROM Documented SNESdev Wiki, Memory map / timing; fullsnes Scored 0x7EF0A2 B1.01 +131 B1.02 B JOYSER is 12 clocks Documented SNESdev Wiki, Memory map / timing; fullsnes Scored 0x7EF0A3 B1.02 +132 B2.04 B NTSC frame is 262 lines Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A4 B2.04 +133 B4.03 B RDNMI sets at vblank Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A5 B4.03 +134 B2.01 B No dot above 339 Corroborated fullsnes' PPU H-Counter-Latch Quantities histogram, a direct hardware measurement: dots 323 and 327 latch six times, dot 340 never. bsnes, ares and Mesen2 all implement it; snes9x uses 322/326 and is the outlier Scored 0x7EF0A6 B2.01 +135 B4.16 B H-IRQ position (golden) Contested no source pins the fired dot at single-dot precision; recorded as the before/after guard for T-06-A's clock-domain comparator change Golden 0x7EF0A7 - +136 B4.17 B NMI enable is a level Documented SNESdev Wiki NMITIMEN/RDNMI [ERRATA]; fullsnes $4200/$4210 Scored 0x7EF0A8 B4.06 +137 B4.04 B RDNMI is read-to-clear Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A9 B4.04 +138 B4.05 B RDNMI auto-clears Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0AA B4.05 +139 B4.08 B V-IRQ fires at VTIME Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0AB B4.08 +140 B4.12 B $4211 read releases IRQ Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0AC B4.12 +141 B4.15 B CPU revision (golden) Documented SNESdev Wiki, Timing; fullsnes Golden 0x7EF0AD G1.09 +142 B5.01 B 8x8 unsigned multiply Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AE B5.01 +143 B5.02 B 16/8 unsigned divide Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AF B5.02 +144 B5.03 B Divide by zero Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0B0 - +145 B5.04 B Mul/div overlap (golden) Contested SNESdev Errata states overlapping $4203/$4206 operation is undefined Golden 0x7EF0B1 B5.03 +146 B5.05 B Mul/div power-on state Documented anomie regs.txt r1157 and nocash fullsnes, independently; implemented by bsnes/ares/Mesen2. No known hardware test ROM Scored 0x7EF0B2 B5.04 +147 B1.03 B Internal cycles are 6 Documented SNES Development Manual Bk I 21.1; SNESdev Wiki; fullsnes Scored 0x7EF0B3 B1.03 +148 B1.04 B DMA speed is uniform Documented SNES Development Manual Bk I 21.1 (DMA at 2.68MHz regardless of address) Scored 0x7EF0B4 B1.04 +149 B2.06 B Interlace line count Contested the dossier conditions the extra line on $213F.7 (the field), which this test does not control — sampling V across an uncontrolled field cannot assert a line count Golden 0x7EF0B5 B2.06 +150 B2.05 B PAL frame is 312 lines Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0B6 B2.05 +151 B4.14 B IRQ dispatch latency Documented SNESdev Wiki, Timing; fullsnes — the sub-cycle poll point is not CPU-observable, so its consequence is measured instead Golden 0x7EF0B7 B4.14 +152 B2.10 B Region bit (golden) Contested SNESdev PPU registers places the 50/60Hz bit at bit 3, which overlaps the PPU2 version field; fullsnes places it at bit 4 Golden 0x7EF0B8 B2.10 +153 B4.07 B H-IRQ position (golden) Contested the $4211 poll loop is coarser than the dot the comparator fires on, so the exact H position is not resolvable from software by polling Golden 0x7EF0B9 B4.07 +154 B4.09 B HV-IRQ needs both Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0BA B4.09 +155 B3.01 B DRAM refresh pause Contested fullsnes and anomie put the pause at 40 clocks near line-clock 536, and RustySNES now models it there (docs/dram-refresh.md), so this loop records variant 2 (one interval ~10 dots longer); ares' own source still calls its refresh *pattern* technically wrong and only right on average, so the exact per-line position stays host-cross-validated rather than self-asserted Golden 0x7EF0BB B3.01,B3.02,B3.03 +156 B4.13 B Timer range is 9-bit Documented fullsnes $4207-$420A: HTIME is 0-339 and VTIME 0-261 (NTSC) / 0-311 (PAL), both held in nine bits Scored 0x7EF0BC B4.13 +157 B4.11 B Dot 153, last line Contested superfamicom.org's timing page states the exception and gives no mechanism; its timing text derives from fullsnes, so the two are one source, and no test ROM verifies it. ares, bsnes, Mesen2 and snes9x were each searched and none implements it Golden 0x7EF0BD B4.11 +158 D1.01 D DMA mode 0 Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0BE D1.01 +159 D1.01b D DMA mode 1 Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0BF D1.01 +160 D1.06 D DMA count hits zero Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0C0 D1.06 +161 D1.07 D DMA fixed A-bus Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0C1 D1.07 +162 D1.07b D DMA decrementing A-bus Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0C2 D1.07 +163 D1.10 D DMA $43xB scratch latch Corroborated ares and bsnes both model the latch and serialize it Scored 0x7EF0C3 D1.10 +164 D1.02 D DMA 8 clocks/byte Documented SNESdev Wiki, DMA timing; fullsnes Scored 0x7EF0C4 D1.02 +165 D1.05 D DMA count 0 = 65536 Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0C5 D1.05 +166 D1.09 D WRAM->$2180 no-write Documented fullsnes: "does not cause a write to occur" Scored 0x7EF0C6 D1.09,D1.15 +167 D2.03 D HDMA line-count byte Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0C7 D2.03 +168 D2.04 D HDMA repeat flag Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0C8 D2.04 +169 D2.07 D HDMA preempts GP-DMA Documented SNESdev Wiki, HDMA; anomie's timing doc; fullsnes Scored 0x7EF0C9 D2.07 +170 D2.09 D HDMA armed mid-frame Contested fullsnes and the SNESdev Wiki record that enabling HDMA outside vblank produces erroneous writes from uninitialised A2An/NLTRn, but what those writes contain is a function of the previous frame's leftover state and is specified nowhere Golden 0x7EF0CA D2.09 +171 D1.14 D $2180 B->A does write Documented fullsnes: $2180->WRAM writes, but the value written is invalid Scored 0x7EF0CB D1.14 +172 D1.13 D DMA count hits zero Documented fullsnes and ares: the DAS $43x5/6 byte-count register decrements as GP-DMA transfers and reads $0000 when the transfer completes Scored 0x7EF0CC D1.13 +173 D1.11 D DMA power-on state Corroborated fullsnes register table and the SNESdev DMA-registers page agree independently; ares and bsnes default every channel field to match Scored 0x7EF0CD D1.11 +174 D1.08 D Invalid A-bus (golden) Contested the errata names the ranges invalid but does not specify what is read instead; the substitute is open bus, whose content is core-specific and time-dependent Golden 0x7EF0CE D1.08 +175 D1.03 D DMA startup overhead Documented SNESdev Wiki, DMA timing; fullsnes Golden 0x7EF0CF D1.03 +176 D1.04 D DMA channel priority Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0D0 D1.04 +177 D2.05 D HDMA indirect mode Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0D1 D2.05 +178 D2.06 D HDMA $4308/$430A state Documented SNESdev Wiki, HDMA registers; fullsnes Scored 0x7EF0D2 D2.06 +179 E4.11 E ARAM power-on pattern Contested the dossier records a repeating 32x$00 / 32x$FF fill and marks it chip-dependent and informational; RustySNES, snes9x and Mesen2 all boot APU RAM uniformly zero instead Golden 0x7EF0D3 E4.11 +180 E1.01 E MUL YA flags from Y Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D4 E1.01 +181 E1.02 E DIV YA,X normal branch Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D5 E1.02 +182 E1.07 E DIV valid to Q<=511 Documented SNESdev Wiki SPC700 reference and fullsnes, both flagging DIV as valid only for quotients up to 511; the values past it follow E1.03's overflow formula Scored 0x7EF0D6 E1.07 +183 E1.04 E DIV H = nibble compare Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D7 E1.04 +184 E1.05 E DIV V is quotient bit 8 Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D8 E1.05 +185 E1.06 E DIV flags from quotient Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D9 E1.06 +186 E1.13 E ADDW H = bit-11 carry Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0DA E1.13 +187 E1.15 E MOVW YA sets 16-bit N/Z Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0DB E1.15 +188 E3.01 E Timer read clears it Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0DC E3.01 +189 E3.02 E Timer enable 0->1 resets Documented SNESdev Wiki SPC700 timers and fullsnes: a 0->1 on a $F1 timer-enable bit resets that timer's stage-2 divider and stage-3 output counter Scored 0x7EF0DD E3.02 +190 E3.11 E $F2 bit 7 blocks writes Documented SNESdev Wiki, S-DSP; fullsnes Scored 0x7EF0DE E3.11 +191 E3.11b E DSP register addressing Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0DF - +192 E2.01 E Store dummy-reads target Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0E0 E2.01 +193 E2.05 E DP index wraps in page Documented SNESdev Wiki, SPC700 addressing; fullsnes Scored 0x7EF0E1 E2.05 +194 E6.09 E Gaussian sum wraps Documented fullsnes and anomie's DSP doc: of the four gaussian additions the first cannot overflow, the second wraps in 16 bits and the third saturates Scored 0x7EF0E2 E6.09 +195 E6.11 E BRR waveform vectors Contested the dossier names four nibble patterns and asks what a decoder makes of each without stating an expected value for any of them; the row's content is the measurement Golden 0x7EF0E3 E6.11 +196 E10.01 E 32 SPC cycles per sample Documented fullsnes and anomie: the SPC700's 1.024 MHz clock and the DSP's 32 kHz output rate fix the ratio at 32 CPU cycles per output sample Scored 0x7EF0E4 E10.01 +197 E10.05 E Soft reset acts as $E0 Contested both sources agree FLG bit 7 makes the DSP behave as $E0 and force every voice into release, and contradict each other on what ENDX then reads: nocash says $FF, anomie says 0. The dossier marks the row [CONFLICT] and asks for a golden vector Golden 0x7EF0E5 E10.05 +198 E1.14 E XCN costs five cycles Documented the SNESdev Wiki SPC700 reference and fullsnes both give XCN as 5 cycles, against 2 for NOP Scored 0x7EF0E6 E1.14 +199 E2.04 E DBNZ dp is an RMW Documented SNESdev Wiki SPC700 reference and fullsnes: DBNZ dp,rel reads its operand, decrements it and writes it back, and $FD-$FF are read-to-clear Scored 0x7EF0E7 E2.04 +200 E3.14 E $F8/$F9 are plain RAM Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0E8 E3.14 +201 E3.11c E DSP global registers Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0E9 - +202 E9.19 E ENDX write clears it Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0EA E9.19 +203 E9.03 E Pitch not noise rate Documented fullsnes and anomie's DSP doc: the noise generator's step rate is set by FLG bits 0-4, and a voice's pitch register does not participate Scored 0x7EF0EB E9.03 +204 E9.01 E Noise LFSR seed Documented fullsnes and anomie's DSP doc: the noise shift register resets to $4000, with taps bit0 XOR bit1 feeding bit 14 Scored 0x7EF0EC E9.01 +205 E5.07 E End+mute zeroes env Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0ED E5.07 +206 E5.08 E Loop flag without end Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0EE E5.08 +207 E5.09 E ENDX sets on end block Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0EF E5.09 +208 E5.01 E BRR header layout Documented fullsnes and anomie's DSP doc: the BRR header is ssssffle, shift in bits 7-4, and a nibble decodes as (nibble << shift) >> 1 Scored 0x7EF0F0 E5.01 +209 E5.10 E Released voice decodes Documented fullsnes and anomie's DSP doc: key-off begins the release ramp and does not halt BRR decoding, which continues to follow loop points and set ENDX Scored 0x7EF0F1 E5.10 +210 E5.11 E Directory entry address Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0F2 E5.11 +211 E7.10 E Direct GAIN is envelope Documented SNESdev Wiki, S-DSP envelopes; fullsnes Scored 0x7EF0F3 E7.10 +212 E1.03 E DIV overflow branch Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0F4 E1.03 +213 E1.08 E DAA adjustments Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F5 E1.08 +214 E2.08 E TCALL vector table Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F6 E2.08 +215 E2.09 E BRK shares TCALL 0 Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F7 E2.09 +216 E3.03 E $F1 bit 5 clears port 3 Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0F8 E3.03 +217 E3.04 E Writes pass under IPL Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0F9 E3.04 +218 E3.05 E TnDIV $00 means 256 Documented SNESdev Wiki, SPC700 timers; fullsnes Scored 0x7EF0FA E3.05 +219 E3.10 E TEST gates RAM writes Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0FB E3.10 +220 E1.09 E DAS mirrors DAA Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0FC E1.09 +221 E1.10 E TSET1 is a compare Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0FD E1.10 +222 E1.12 E CLRV clears H too Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0FE E1.12 +223 E2.02 E MOV dp,dp is exempt Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0FF E2.02 +224 E2.03 E MOVW reads the low byte Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF100 E2.03 +225 E2.06 E PSW.P selects the page Documented SNESdev Wiki, SPC700 addressing; fullsnes Scored 0x7EF101 E2.06 +226 E2.07 E CALL pushes exact addr Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF102 E2.07 +227 E4.01 E IPL ROM contents Documented the canonical 64-byte IPL listing; fullsnes, SNESdev Wiki Scored 0x7EF103 E4.01 +228 E4.02 E IPL handoff state Documented fullsnes, SNESdev Wiki, APU boot handshake Scored 0x7EF104 E4.02 +229 E4.03 E IPL zerofills $00-$EF Documented the canonical 64-byte IPL listing zero-fills $0000-$00EF as the first thing it does at $FFC0, before entering its transfer loop; fullsnes and the SNESdev Wiki both carry it Scored 0x7EF105 E4.03 +230 E4.04 E IPL ready announcement Documented fullsnes, SNESdev Wiki, APU boot handshake Scored 0x7EF106 E4.04 +231 E4.06 E IPL multi-block continue Documented fullsnes, SNESdev Wiki; canonical IPL boot ROM $FFEF-$FFFB Scored 0x7EF107 E4.06 +232 E4.08 E IPL DSP-poke via $00F2 Documented fullsnes (APU / S-DSP); canonical IPL boot ROM store loop Scored 0x7EF108 E4.08 +233 E5.12 E SRCN change source Documented fullsnes and anomie's DSP doc: a mid-playback SRCN change takes the new entry's start address, or its loop address if the voice has already passed a loop point Scored 0x7EF109 E5.12 +234 E5.02 E BRR nibbles are signed Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF10A E5.02 +235 E7.16 E OUTX is pre-volume Documented fullsnes, S-DSP envelopes; anomie's DSP doc Scored 0x7EF10B E7.16 +236 E8.04 E KOFF outranks KON Documented fullsnes, S-DSP key on/off; anomie's DSP doc Scored 0x7EF10C E8.04 +237 E8.05 E KON is edge-triggered Documented fullsnes and anomie's DSP doc: KON is write-triggered and self-clears ~63 clocks later; only KOFF and FLG bit 7 are level-sensitive Scored 0x7EF10D E8.05 +238 E9.04 E Noise voices decode BRR Documented fullsnes, S-DSP noise; anomie's DSP doc — flagged as errata Scored 0x7EF10E E9.04 +239 E9.06 E EDL 0 is a 4-byte buffer Documented fullsnes, S-DSP echo — flagged as errata; anomie's DSP doc Scored 0x7EF10F E9.06 +240 E9.12 E Echo writes are masked Documented fullsnes, S-DSP echo; anomie's DSP doc Scored 0x7EF110 E9.12 +241 E9.15 E Voice mix saturates Documented fullsnes and anomie's DSP doc: the per-voice mix clamps to 16 bits after each addition rather than accumulating and clamping once Scored 0x7EF111 E9.15 +242 E9.05 E Echo entry is 4 bytes Documented fullsnes and anomie's DSP doc: the echo buffer holds four bytes per sample, a 16-bit left sample followed by a 16-bit right one Scored 0x7EF112 E9.05 +243 E9.13 E L/R FIR independent Documented fullsnes and anomie's DSP doc: the echo FIR keeps a separate eight-sample history and accumulator per channel; only the coefficients are shared Scored 0x7EF113 E9.13 +244 E9.10 E FLG.5 stops echo writes Documented fullsnes, S-DSP echo; anomie's DSP doc Scored 0x7EF114 E9.10 +245 E9.17 E Mute is after OUTX Documented fullsnes, S-DSP FLG; anomie's DSP doc Scored 0x7EF115 E9.17 +246 E9.18 E FLG reset kills voices Documented SNESdev Wiki, S-DSP; fullsnes Scored 0x7EF116 E9.18 +247 E5.03 E BRR sample arithmetic Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF117 E5.03 +248 E5.04 E Invalid shift collapses Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF118 E5.04 +249 E5.05 E BRR filter 1 Documented fullsnes, S-DSP BRR filters; anomie's DSP doc Scored 0x7EF119 E5.05 +250 E5.13 E Released voices decode Documented fullsnes and anomie's DSP doc: the BRR decoder advances on the pitch clock regardless of the envelope; voices never actually stop decoding Scored 0x7EF11A E5.13 +251 E7.01 E Rate 0 never fires Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF11B E7.01 +252 E7.13 E GAIN bent-increase Documented fullsnes and anomie's DSP doc: GAIN mode 7 increases +32 per sample below $600 and +8 above, comparing the internal envelope latch unsigned Scored 0x7EF11C E7.13 +253 E7.17 E Lin-decrease clamps 0 Documented fullsnes and anomie's DSP doc: GAIN linear-decrease subtracts $20 per tick and clamps the envelope to zero on underflow, comparing the internal envelope unsigned Scored 0x7EF11D E7.17 +254 E7.18 E ENVX is E>>4 Documented fullsnes and anomie's DSP doc: VxENVX = envelope >> 4, a seven-bit value with bit 7 always clear Scored 0x7EF11E E7.18 +255 E7.04 E Attack $F is instant Documented fullsnes and anomie's DSP doc: attack rate $F fires every sample with a step of +1024, rather than the +32 on a counter tick every other rate uses Scored 0x7EF11F E7.04 +256 E7.09 E Release rate is fixed Documented fullsnes and anomie's DSP doc: release steps -8 per sample regardless of ADSR, which is why a custom release has to be built from GAIN Scored 0x7EF120 E7.09 +257 E7.05 E Decay index d*2+16 Documented fullsnes and anomie's DSP doc: the decay phase indexes the counter table at d*2+16, stepping E -= 1 then E -= E>>8 Scored 0x7EF121 E7.05 +258 E7.06 E Sustain index verbatim Documented fullsnes and anomie's DSP doc: the sustain phase indexes the counter table with the ADSR2 rate field directly, unlike decay's d*2+16 Scored 0x7EF122 E7.06 +259 E7.07 E Sustain boundary Documented fullsnes and anomie's DSP doc: the decay phase ends when (E >> 8) equals the ADSR2 sustain level, giving a boundary of $100 per level Scored 0x7EF123 E7.07 +260 E7.03 E Attack index a*2+1 Documented fullsnes and anomie's DSP doc: the attack phase indexes the counter table at a*2+1 and steps the envelope by +32 Scored 0x7EF124 E7.03 +261 E7.12 E GAIN sustain boundary Contested the dossier records the GAIN-sourced boundary as [ERRATA]; RustySNES takes it from ADSR2 instead, which E7.07 asserts, so which behaviour is right is not settled here Golden 0x7EF125 E7.12 +262 E7.08 E Key-off releases to zero Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF126 E7.08 +263 E8.02 E Key-on takes 5 samples Documented fullsnes and anomie's DSP doc: KON is held for five output samples while the directory and the first BRR block are fetched, before the envelope starts Scored 0x7EF127 E8.02 +264 E8.03 E KON restarts a voice Documented fullsnes and anomie's DSP doc: KON re-enters the key-on sequence unconditionally, resetting the BRR pointer and zeroing the envelope, whether or not the voice was already playing Scored 0x7EF128 E8.03 +265 E8.07 E KOFF pulse collapses Contested KON/KOFF are sampled every second output sample, so whether a short pulse is seen depends on where the poll falls inside it -- which makes the outcome phase-dependent rather than fixed, the same hedge E8.05 and E8.06 carry as "usually" Golden 0x7EF129 E8.07 +266 E8.10 E KOFF+KON cuts faster Documented fullsnes and anomie's DSP doc: KON zeroes the envelope before its attack, and KOFF outranks KON, so the pair silences immediately where KOFF alone ramps Scored 0x7EF12A E8.10 +267 E7.11 E GAIN linear increase Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF12B E7.11 +268 E7.14 E GAIN decrease clamps Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF12C E7.14 +269 E7.15 E ENVX is E >> 4 Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF12D E7.15 +270 E6.02 E Pitch $1000 is 1:1 Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12E E6.02 +271 E6.02b E Pitch $1000 does finish Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12F E6.02 +272 E6.02c E Pitch $2000 is +1 octave Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF130 E6.02 +273 E6.02d E Pitch $2000 upper bound Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF131 E6.02 +274 E3.06 E T2 is eight times T0 Documented SNESdev Wiki, SPC700 timers; fullsnes Scored 0x7EF132 E3.06 +275 E3.08 E TEST bit 0 halts timers Documented fullsnes, SPC700 TEST register; ares and bsnes smp/timing Scored 0x7EF133 E3.08 +276 F1.01 F Manual pad read order Documented fullsnes and the SNESdev Wiki controller protocol: the shift register presents B, Y, Select, Start, Up, Down, Left, Right, A, X, L, R and then four zero bits Scored 0x7EF134 F1.01 +277 F1.02 F Pad reads 17+ are 1 Documented SNESdev Wiki, controller protocol; fullsnes Scored 0x7EF135 F1.02 +278 F1.03 F Shared $4016 latch Documented fullsnes and the SNESdev Wiki controller protocol: bit 0 of $4016 is the shared latch line that parallel-loads both controller ports' shift registers Scored 0x7EF136 F1.03 +279 F1.04 F $4016 bits 7-2 open bus Corroborated RustySNES, snes9x and Mesen2 all return $41 for the absolute read and $01 for the long one -- identical bytes, so bits 7-2 follow the CPU bus in all three Scored 0x7EF137 F1.04 +280 F1.07 F Auto-read needs $4200.0 Documented fullsnes and the SNESdev Wiki: bit 0 of $4200 arms the automatic joypad read, and with it clear $4218-$421F are not written Scored 0x7EF138 F1.07 +281 F1.05 F Pad signature is 0000 Documented fullsnes and the SNESdev Wiki: bits 3-0 of the auto-read result identify the device, and a standard controller reports 0000 Scored 0x7EF139 F1.05 +282 F1.06 F First bit clocked is B Documented fullsnes and the SNESdev Wiki: the auto-read result holds the sixteen shifted bits in clock order, most significant first, so B is bit 15 Scored 0x7EF13A F1.06 +283 F1.08 F Auto-read start dot Documented fullsnes: the automatic joypad read begins ~dot 32.5-95.5 of the first vblank line (RustySNES delays it 256 master clocks ~ dot 64), not at the vblank edge Golden 0x7EF13B F1.08 +284 F1.09 F Auto-read duration Documented fullsnes: the automatic joypad read is busy for exactly 4224 master cycles (~3.097 scanlines); the poll count is a CPU-cycle-proportional proxy for it Golden 0x7EF13C F1.09 +285 F1.10 F Auto-read start race Documented fullsnes: the automatic joypad read begins ~dot 32.5-95.5 of the first vblank line, not at the vblank edge, so $4212 bit 0 reads not-busy for that window and a $4212 poll at NMI entry sees the read not yet started Scored 0x7EF13D F1.10 +286 F1.11 F Latch corrupts auto-read Documented fullsnes and the SNESdev Wiki: while $4016 bit 0 is high the shift registers reload continuously rather than shifting, so an automatic read taken across it returns the same bit in every position Scored 0x7EF13E F1.11 +287 F1.12 F Auto-read result timing Contested F1.12 says results are valid by V = $E3, which does not reconcile with F1.09's 4224-cycle duration and F1.08's start window; no source says which to believe, and the cores split on whether the result appears at once or progressively Golden 0x7EF13F F1.12 +288 F1.14 F $4213 reads $4201 back Documented fullsnes: RDIO reads the WRIO output pins, which are open-collector, so with nothing driving them low the value read is the value written Scored 0x7EF140 F1.14 +289 G1.02 G Reset: $4210/$4211 clear Documented SNESdev Wiki, power-on state; fullsnes Scored 0x7EF141 G1.02 +290 G1.04 G Reset: emulation mode Documented SNESdev Wiki, power-on state; WDC 65C816 datasheet, XCE Scored 0x7EF142 G1.04 +291 G1.05 G Power-on PPU registers Documented the dossier marks the PPU power-on state indeterminate ('no boot ROM; most PPU registers start unknown') and says to report it, never assert; the readable registers ($2134-$2136, $213E/$213F) are reported and the only scored check is the self-guard that the power-on capture actually ran Scored 0x7EF143 G1.05 +292 G1.08 G Write-only read: openbus Documented SNESdev Wiki, open bus; fullsnes, memory map notes Scored 0x7EF144 G1.08 +293 G1.10 G Checksum XOR complement Documented SNESdev Wiki, cartridge header; fullsnes Scored 0x7EF145 G1.10 +294 G1.11 G Checksum over the image Documented SNESdev Wiki, cartridge header checksum; fullsnes Scored 0x7EF146 G1.11 +295 G1.12 G LoROM header location Documented SNESdev Wiki, cartridge header; fullsnes Scored 0x7EF147 G1.12 +296 G1.14 G LoROM bank decode Documented SNESdev Wiki, memory map; fullsnes Scored 0x7EF148 G1.14 +297 G1.19 G Power-on $4201/timers Documented fullsnes and the SNESdev Wiki power-on table: $4201 = $FF, HTIME and VTIME = $1FF; the other registers in the row are observed by B5.05 and B1.01 Scored 0x7EF149 G1.01 +298 G1.20 G Power-on indeterminate Contested the dossier marks the whole row [UNDEFINED] and says to report it and never assert it; half the registers it names are write-only and cannot be reported at all Golden 0x7EF14A G1.03 +299 G1.07 G WRAM power-on fill Contested the dossier marks the row [UNDEFINED] and asks for a golden vector by name: no canonical WRAM fill exists and real consoles disagree Golden 0x7EF14B G1.07 +300 A5.S01 A Sweep: CLC Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14C A5.01-08 +301 A5.S02 A Sweep: SEC Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14D A5.01-08 +302 A5.S03 A Sweep: CLV Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14E A5.01-08 +303 A5.S04 A Sweep: INX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14F A5.01-08 +304 A5.S05 A Sweep: DEX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF150 A5.01-08 +305 A5.S06 A Sweep: TAX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF151 A5.01-08 +306 A5.S07 A Sweep: TXY Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF152 A5.01-08 +307 A5.S08 A Sweep: ASL A Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF153 A5.01-08 +308 A5.S09 A Sweep: XBA Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF154 A5.01-08 +309 A5.S10 A Sweep: TCD Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF155 A5.01-08 +310 A5.S11 A Sweep: LDA #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF156 A5.01-08 +311 A5.S12 A Sweep: LDX #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF157 A5.01-08 +312 A5.S13 A Sweep: CMP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF158 A5.01-08 +313 A5.S14 A Sweep: BIT #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF159 A5.01-08 +314 A5.S15 A Sweep: REP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15A A5.01-08 +315 A5.S16 A Sweep: SEP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15B A5.01-08 +316 A5.S17 A Sweep: WDM Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15C A5.01-08 +317 A5.S18 A Sweep: PHA+PLA Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15D A5.01-08 +318 A5.S19 A Sweep: PHP+PLP Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15E A5.01-08 +319 A5.S20 A Sweep: PHB+PLB Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15F A5.01-08 +320 A5.S21 A Sweep: PHD+PLD Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF160 A5.01-08 +321 A5.S22 A Sweep: PHX+PLX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF161 A5.01-08 +322 A5.S23 A Sweep: LDA dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF162 A5.01-08 +323 A5.S24 A Sweep: LDA abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF163 A5.01-08 +324 A5.S25 A Sweep: LDA long Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF164 A5.01-08 +325 A5.S26 A Sweep: STA dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF165 A5.01-08 +326 A5.S27 A Sweep: STA abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF166 A5.01-08 +327 A5.S28 A Sweep: LDA dp,X Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF167 A5.01-08 +328 A5.S29 A Sweep: LDA abs,X Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF168 A5.01-08 +329 A5.S30 A Sweep: INC dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF169 A5.01-08 +330 A5.S31 A Sweep: INC abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16A A5.01-08 +331 A5.S32 A Sweep: ADC dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16B A5.01-08 +332 A5.S33 A Sweep: CMP abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16C A5.01-08 +333 A5.S34 A Sweep: BVS untaken Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16D A5.01-08 +334 A5.S35 A Sweep: BRL flat 4 Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16E A5.16 diff --git a/tests/roms/AccuracySNES/asm/tests_group_a.s b/tests/roms/AccuracySNES/asm/tests_group_a.s index 11c7d2c4..b1512402 100644 --- a/tests/roms/AccuracySNES/asm/tests_group_a.s +++ b/tests/roms/AccuracySNES/asm/tests_group_a.s @@ -25651,6 +25651,304 @@ CATALOG_IMPL = 1 jml test_restore .endproc +; C7.06 — TimeOver by YLOC+1 +; provenance: Documented (fullsnes and the SNESdev Wiki: the sprite tile budget is 34 per line and Time Over is raised by the fetch phase, so it reads set by the start of the line the sprites paint on) +.proc test_c7_06 + .a16 + .i16 + jmp @body +@handler: + php + sep #$20 + .a8 + .a8 + pha + lda $213E ; sample STAT77 at the IRQ dot + sta f:$7E0156 + lda $4211 ; acknowledge so the line drops + pla + plp + rti +@body: + rep #$30 + .a16 + .i16 + phk + plb + ; Group C is outside bank $00, so route through the bank-$00 far-IRQ shim. + rep #$20 + .a16 + lda #.loword(@handler) + sta a:V_IRQ_VEC_FAR + sep #$20 + .a8 + lda #^@handler + sta a:V_IRQ_VEC_FAR + 2 + rep #$20 + .a16 + lda #irq_far_shim + sta a:V_IRQ_VEC + ; --- phase A: 20 16x16 sprites (40 tiles), sampled on the eval line V = 100 --- + sep #$20 + .a8 + stz $2101 ; OBJSEL pair 0: 8x8 small, 16x16 large, name base 0 + stz $2102 + stz $2103 + rep #$10 + .i16 + ldx #$0000 +@fill_a: + ; X = index * 8, so the sprites tile across the line rather than stacking at one column. + txa + asl + asl + asl + sta $2104 ; X + cpx #$0014 ; 20 in range + bcs @off_a + lda #100 + bra @sety_a +@off_a: + lda #$F0 ; below the visible area in 224-line mode +@sety_a: + sta $2104 ; Y + stz $2104 ; tile + stz $2104 ; attr + inx + cpx #$0080 + bne @fill_a + ; High table: bit 1 of each 2-bit field is the size select for that sprite. + stz $2102 + lda #$01 + sta $2103 ; OAMADDR = word $100 + ldx #$0000 +@hi_a: + lda #$AA + sta $2104 + inx + cpx #$0020 + bne @hi_a + sep #$20 + .a8 + lda #$FF + sta f:$7E0156 ; poison: a handler that never runs cannot look like either verdict + stz $4207 + stz $4208 ; HTIME = 0 + lda #100 + sta $4209 + stz $420A ; VTIME + lda $4211 ; clear any stale latch + lda #$0F + sta $2100 ; display on, full brightness + lda #$10 + sta $212C ; OBJ on the main screen + cli + lda #$30 + sta $4200 ; HV-IRQ + rep #$30 + .a16 + .i16 + jsl wait_vblank_far ; span one complete active period + sep #$20 + .a8 + sei + stz $4200 ; disarm before reading anything back + lda $4211 + lda f:$7E0156 + and #$80 + cmp #$00 + beq :+ + jmp @fail1 + : + ; --- phase B: same sprites, sampled on the line they paint on, V = 101 --- + sep #$20 + .a8 + stz $2101 ; OBJSEL pair 0: 8x8 small, 16x16 large, name base 0 + stz $2102 + stz $2103 + rep #$10 + .i16 + ldx #$0000 +@fill_b: + ; X = index * 8, so the sprites tile across the line rather than stacking at one column. + txa + asl + asl + asl + sta $2104 ; X + cpx #$0014 ; 20 in range + bcs @off_b + lda #100 + bra @sety_b +@off_b: + lda #$F0 ; below the visible area in 224-line mode +@sety_b: + sta $2104 ; Y + stz $2104 ; tile + stz $2104 ; attr + inx + cpx #$0080 + bne @fill_b + ; High table: bit 1 of each 2-bit field is the size select for that sprite. + stz $2102 + lda #$01 + sta $2103 ; OAMADDR = word $100 + ldx #$0000 +@hi_b: + lda #$AA + sta $2104 + inx + cpx #$0020 + bne @hi_b + sep #$20 + .a8 + lda #$FF + sta f:$7E0156 ; poison: a handler that never runs cannot look like either verdict + stz $4207 + stz $4208 ; HTIME = 0 + lda #101 + sta $4209 + stz $420A ; VTIME + lda $4211 ; clear any stale latch + lda #$0F + sta $2100 ; display on, full brightness + lda #$10 + sta $212C ; OBJ on the main screen + cli + lda #$30 + sta $4200 ; HV-IRQ + rep #$30 + .a16 + .i16 + jsl wait_vblank_far ; span one complete active period + sep #$20 + .a8 + sei + stz $4200 ; disarm before reading anything back + lda $4211 + lda f:$7E0156 + and #$80 + cmp #$80 + beq :+ + jmp @fail2 + : + ; ...and Range Over must still be clear: 20 sprites is well under the 32-sprite limit. + lda f:$7E0156 + and #$40 + cmp #$00 + beq :+ + jmp @fail3 + : + ; --- control: the same 20 sprites at 8x8 is 20 tiles, under the 34 budget --- + sep #$20 + .a8 + stz $2101 ; OBJSEL pair 0: 8x8 small, 16x16 large, name base 0 + stz $2102 + stz $2103 + rep #$10 + .i16 + ldx #$0000 +@fill_c: + ; X = index * 8, so the sprites tile across the line rather than stacking at one column. + txa + asl + asl + asl + sta $2104 ; X + cpx #$0014 ; 20 in range + bcs @off_c + lda #100 + bra @sety_c +@off_c: + lda #$F0 ; below the visible area in 224-line mode +@sety_c: + sta $2104 ; Y + stz $2104 ; tile + stz $2104 ; attr + inx + cpx #$0080 + bne @fill_c + ; High table: bit 1 of each 2-bit field is the size select for that sprite. + stz $2102 + lda #$01 + sta $2103 ; OAMADDR = word $100 + ldx #$0000 +@hi_c: + lda #$00 + sta $2104 + inx + cpx #$0020 + bne @hi_c + sep #$20 + .a8 + lda #$FF + sta f:$7E0156 ; poison: a handler that never runs cannot look like either verdict + stz $4207 + stz $4208 ; HTIME = 0 + lda #101 + sta $4209 + stz $420A ; VTIME + lda $4211 ; clear any stale latch + lda #$0F + sta $2100 ; display on, full brightness + lda #$10 + sta $212C ; OBJ on the main screen + cli + lda #$30 + sta $4200 ; HV-IRQ + rep #$30 + .a16 + .i16 + jsl wait_vblank_far ; span one complete active period + sep #$20 + .a8 + sei + stz $4200 ; disarm before reading anything back + lda $4211 + lda f:$7E0156 + and #$80 + cmp #$00 + beq :+ + jmp @fail4 + : + lda #$8F + sta $2100 ; forced blank again, as the rest of the battery expects + stz $212C + sep #$20 + .a8 + lda #$01 + sta f:$7EE010 + jml test_restore +@fail1: + ; Time Over was already set on the sprites' own line (V = 100), earlier than V = YLOC + 1 + sep #$20 + .a8 + lda #$02 + sta f:$7EE010 + jml test_restore +@fail2: + ; Time Over had not set by V = YLOC + 1 with 40 sprite-tiles due on the line + sep #$20 + .a8 + lda #$04 + sta f:$7EE010 + jml test_restore +@fail3: + ; Range Over set alongside Time Over with only 20 sprites on the line — the two flags are being raised together instead of on their own conditions + sep #$20 + .a8 + lda #$06 + sta f:$7EE010 + jml test_restore +@fail4: + ; Time Over set with only 20 sprite-tiles due, so phase B's reading meant 'sprites are present', not 'the tile budget was exceeded' + sep #$20 + .a8 + lda #$08 + sta f:$7EE010 + jml test_restore +.endproc + ; C7.02 — Time Over is slivers ; provenance: Documented (SNESdev Wiki, Sprites; fullsnes; anomie) .proc test_c7_02 @@ -33237,7 +33535,7 @@ apu_prog_112: .export _test_flags _test_count: - .word 334 + .word 335 ; Entry points, 24-bit: test bodies no longer all live in bank $00. _test_entries: @@ -33358,6 +33656,7 @@ _test_entries: .faraddr test_c7_01 .faraddr test_c7_04 .faraddr test_c7_05 + .faraddr test_c7_06 .faraddr test_c7_02 .faraddr test_c7_08 .faraddr test_c7_10 @@ -33695,6 +33994,7 @@ _test_flags: .byte $01 ; C7.01 .byte $01 ; C7.04 .byte $01 ; C7.05 + .byte $01 ; C7.06 .byte $01 ; C7.02 .byte $01 ; C7.08 .byte $01 ; C7.10 @@ -34032,6 +34332,7 @@ _test_names: .addr @n_c7_01 .addr @n_c7_04 .addr @n_c7_05 + .addr @n_c7_06 .addr @n_c7_02 .addr @n_c7_08 .addr @n_c7_10 @@ -34600,6 +34901,9 @@ _test_names: @n_c7_05: .byte 21 .byte "RangeOver dot = idx*2" +@n_c7_06: + .byte 18 + .byte "TimeOver by YLOC+1" @n_c7_02: .byte 20 .byte "Time Over is slivers" @@ -35484,7 +35788,7 @@ _page_len: .byte 7 .byte 4 .byte 9 - .byte 7 + .byte 8 .byte 10 .byte 10 .byte 1 @@ -35538,41 +35842,41 @@ _page_off: .word 111 .word 115 .word 124 - .word 131 - .word 141 - .word 151 + .word 132 + .word 142 .word 152 - .word 155 - .word 158 - .word 162 - .word 164 - .word 168 - .word 173 - .word 183 - .word 186 - .word 191 + .word 153 + .word 156 + .word 159 + .word 163 + .word 165 + .word 169 + .word 174 + .word 184 + .word 187 .word 192 - .word 202 - .word 207 - .word 213 - .word 220 - .word 230 - .word 234 - .word 244 - .word 246 - .word 255 - .word 261 - .word 263 - .word 273 - .word 275 - .word 285 - .word 287 - .word 297 - .word 304 - .word 310 - .word 320 - .word 323 - .word 333 + .word 193 + .word 203 + .word 208 + .word 214 + .word 221 + .word 231 + .word 235 + .word 245 + .word 247 + .word 256 + .word 262 + .word 264 + .word 274 + .word 276 + .word 286 + .word 288 + .word 298 + .word 305 + .word 311 + .word 321 + .word 324 + .word 334 _page_tests: .word 0 @@ -35625,7 +35929,6 @@ _page_tests: .word 69 .word 70 .word 71 - .word 299 .word 300 .word 301 .word 302 @@ -35660,6 +35963,7 @@ _page_tests: .word 331 .word 332 .word 333 + .word 334 .word 30 .word 31 .word 32 @@ -35697,8 +36001,8 @@ _page_tests: .word 84 .word 85 .word 86 - .word 121 - .word 123 + .word 122 + .word 124 .word 87 .word 114 .word 115 @@ -35706,6 +36010,7 @@ _page_tests: .word 117 .word 118 .word 119 + .word 120 .word 88 .word 89 .word 90 @@ -35713,9 +36018,9 @@ _page_tests: .word 92 .word 93 .word 94 - .word 120 - .word 122 - .word 126 + .word 121 + .word 123 + .word 127 .word 95 .word 96 .word 97 @@ -35725,8 +36030,8 @@ _page_tests: .word 101 .word 102 .word 103 - .word 127 .word 128 + .word 129 .word 104 .word 105 .word 106 @@ -35737,37 +36042,36 @@ _page_tests: .word 111 .word 112 .word 113 - .word 124 .word 125 - .word 129 + .word 126 .word 130 - .word 146 - .word 147 .word 131 - .word 133 + .word 147 .word 148 - .word 149 - .word 151 .word 132 .word 134 + .word 149 + .word 150 + .word 152 + .word 133 .word 135 .word 136 .word 137 .word 138 .word 139 .word 140 - .word 150 - .word 152 + .word 141 + .word 151 .word 153 - .word 155 + .word 154 .word 156 - .word 141 + .word 157 .word 142 .word 143 .word 144 .word 145 - .word 154 - .word 157 + .word 146 + .word 155 .word 158 .word 159 .word 160 @@ -35776,26 +36080,26 @@ _page_tests: .word 163 .word 164 .word 165 - .word 170 + .word 166 .word 171 .word 172 .word 173 .word 174 .word 175 - .word 166 + .word 176 .word 167 .word 168 .word 169 - .word 176 + .word 170 .word 177 .word 178 - .word 226 + .word 179 .word 227 .word 228 .word 229 .word 230 .word 231 - .word 179 + .word 232 .word 180 .word 181 .word 182 @@ -35803,45 +36107,45 @@ _page_tests: .word 184 .word 185 .word 186 - .word 197 - .word 211 + .word 187 + .word 198 .word 212 - .word 219 + .word 213 .word 220 .word 221 - .word 187 + .word 222 .word 188 .word 189 .word 190 - .word 199 + .word 191 .word 200 - .word 215 + .word 201 .word 216 .word 217 .word 218 - .word 273 + .word 219 .word 274 - .word 191 + .word 275 .word 192 - .word 198 - .word 213 + .word 193 + .word 199 .word 214 - .word 222 + .word 215 .word 223 .word 224 .word 225 - .word 193 + .word 226 .word 194 - .word 269 + .word 195 .word 270 .word 271 .word 272 - .word 195 + .word 273 .word 196 - .word 201 + .word 197 .word 202 .word 203 - .word 237 + .word 204 .word 238 .word 239 .word 240 @@ -35850,21 +36154,21 @@ _page_tests: .word 243 .word 244 .word 245 - .word 204 + .word 246 .word 205 .word 206 .word 207 .word 208 .word 209 - .word 232 + .word 210 .word 233 - .word 246 + .word 234 .word 247 .word 248 .word 249 - .word 210 - .word 234 .word 250 + .word 211 + .word 235 .word 251 .word 252 .word 253 @@ -35876,16 +36180,16 @@ _page_tests: .word 259 .word 260 .word 261 - .word 266 + .word 262 .word 267 .word 268 - .word 235 + .word 269 .word 236 - .word 262 + .word 237 .word 263 .word 264 .word 265 - .word 275 + .word 266 .word 276 .word 277 .word 278 @@ -35909,3 +36213,4 @@ _page_tests: .word 296 .word 297 .word 298 + .word 299 diff --git a/tests/roms/AccuracySNES/build/accuracysnes-pal.sfc b/tests/roms/AccuracySNES/build/accuracysnes-pal.sfc index 3f0cd5dbd22d34ffe1234288df15c52394086ec0..0700bf68a5bf40ff9af012e0f09f0c5186a46212 100644 GIT binary patch delta 2256 zcmbtUc~nzp7Qf$nR4`Q`qK;aN39SoYTj*FB=ZuP>(=!&7I%-|2;|3KswAQhUOcKhL zeS1k>mY3wcBrhbP7%Fub7i2`Nrk$zaR(CU0tGHsTwo+Q=18qo5C>1ZXhJuOn%l zP*OZGrzJD5wnY?*vGghrO3hIT1OjcGI5rIG<3fdUBGiR5rvJGXlSA7jh7gC4mK1vd zLSg*vT1*6O_d7(P-oBX(J%6h?5ECXYfFncwd15HU!M+Za;Zl7!p-Ux;`KlOPEd z(Mwn~G5;}=93x%u_9r?0u$}({EZycp!;^lN@=Kgyzfg4F8`5h*%uJ$%0W^-P&rG4e zWKq>1-dCLV2>c@--Cl(>D8o(r0*-E|?RHe%F`J z2{sN6pcCks$>B&fl13+Yk@-2{hysU0ahM;DC?DT03R7ii(`iW%sZvvFqlJClDYX$D zlInMMNTWykh^E&@e~0jWdTl?^#!(@3a6T&7g^1PY8RR3Id}O|#oXnH$JQ)s<1vTX1 z8gfaHREEgHFgc~Ru>uiS2)a0qI3_z^eXE*O3|FKnrYYtt)+wY4vm&h6uQ;jbRP-o> z%4d~Rl(Upem7A57N}IA?*{VFHys0ErPpKxUB&vm~5>=(jrrM!8tct1bsR*@DJy!jq zI#<0wy;l9MTCL{P4eC$T9qNbbAsUh9CC#gv<(h38rH0egYmR8n%QUw&y_#X#=d`bA zmuWX^%e6LbowiwfQhQDNy;i83p!PG0(^fUDf^y~C; zy-mMEe@K5$e^(!87;X5iVU}Tq;T?m{;5Y0sv>PrP9vB2hvGMoDdB(NIQlru6H|{eY zGhQ+NXiPMXGs&iz=9!93Qj^6LG956TGF>(Gnub%8sOi)^YCTm((bR|3Ug`vOgF@yJ z<_xpMywJSfTyD0QgXSi4oB5piD|5G5U=drUTJkK*En6*0i^CGJv|7$uZd;Ibr1d$g z#Ja$`!TN#KYOS*#u%5PdSrh0KdJ3IK7s}{T+DzBct@Ic4P5KGj7~6}smu*XIn`{*} zyDemEvK_ZwxAoYP?HTr5`$GE~`}=mSowx6`AGKex-?t|?MmwH&yy95q*y^Zs(2hDs ztK)*>o+I9w;>>d9Iu|?Na;lu1bC>fo=OyR2P5~okvY7&ADf2c{#n>5HgxSZmGgp~z zM(9d)z2wSs6}q;&3@+Za$92?o+4YTUfIG#V?JjT^x=Yf4o22 zU*KQkm--EU#^2!o#DBqm$B+1AejGoU&*f+H%lR#QCGX%P`~kk5zr^3;2L^r<7#Elt zm>pOa*b=A?*cy+f5^W^@v#{fd+&@-;4s2it20Y*c9zw7K{sDX8pKt&^gElw?7vL(~ zfcx+*02+W2&`>lCr63VXM_DKv%|tJwIrPK{L=ml@K=j4OCJ=eD_n{OP!WMWNw!&Xw z8@vPW!Xj7bfnpL4Rq*tjp?62#y?&;c!Q5Qd;cl!Sz+6%N6t z*xG-=ZMXwna2LLYZ{Qv__yPPIK8K^&^maG~op2pn{vAAoZa9t?@ICav5AY-O;x!;d vAQHu)C-6d^M1#;^bQ5ktJYLQ~B*5z#PCL?w43yE>nns9UuaXl1IVt}yEP}hc delta 1908 zcmY+Fc~n&A8OERQ&afj4ps5Lp;}US7X|0D4W241`&1oXR*0io&R3s&+L{l4$%ghiG znSHx6b7!BqGxrVyNTHHApp7W5Q;mX&CN8lERY^h=n`n`W_L8JMC;xo+``+`u&v~BT z`^Qx(B}%2FbxZuj!CJA58cOeywn&ELPZ))xPpbS`=OC?%ze=j;^$9yjC4DoYM{*=A zHWu~DTc}=nW1qCsM4wD7ATs*vL@60;n~)Sw$lSI`$*ah?V-4cg;!9n#G|7#fC)(al z8KDFPMOqax(VwT)3is;tT;a5(PfHavC%%SWxzH)2kEAQUS7KKDhE;G;omst6d~m}8y0%vl?NyLbDbrm*#7ta>s8Va#N?CCX zL!weW%k=Cf1b zkro4y8~66fV<*Rq#y7;~5TPF5@Qu;vye4ww*KLQ^%%DiwdxR<{QYR~#85BuEqUMlj zHzz9KMIl~P6c$y~i;5$n%!X(kp{`TRMlsc+xKMYqZnQ34HC^?Ls!X*`wM}JEajJIJ zSyjL4o+?G1qn@QMQ7=_jsTFFAx>0>l-L1Z%9#v1!ihI}^(ls_h9bj@hSi2Q3_3&5aKLcRaMN(#ATdri z{?xeGxX!rUXfZ~NoyPOVFN_b3<4sSQN=z$Fn@uW{&-AY8lfC5wKNo=Y#GD``1R(?R+G zeU2WWN9jzf%sSt?#9C$DX*F6oYt-6h{lI$7ddHe*liFt6p0};CZL-za?6xM`VcU7z zfbEfOyj^C0&i=CfRr@Zx)gG~T+CR46w#PA(nc2)Drd+}7V603NbCCIv8DvC`#~lTZ z1&*bT^$w-ODlN}dh8xW z$kXmQ3soTyRX}K z!#C>7@Xzo+>tEup^4IuX{#O49f3JVoFA3xZ<^;+D6@hJm+JGV83~+&#!2ZCoz`4Mu zfq}qqAd#KO&SdAXzhGZx*Gk#VtdcddK6Vd#fIY=tWCz$`HYxanU~aH5SQ1JQxxfg8u=a7A1hSHW%NYB?9z z%pK(}a0A>3m&#}J`FtV&Jimh9z}N70KEk*2$N9_r5dV-*4NJp?;f3K9;p(s=Ixvmu z7Lf#|BbO@qc`fu{;D8%G1i>MKW<;?coj8QQ<1EhO3U1(Ye2sf}ggBBwGDsGgOr{VS znMI07F)1N0F$?mj3aWrG;XG;~GnhxcsCW}Qune!`PuPM#;|*-Za;(5gtio^cJNzEo zupM&z0jseFYf+AMsK6`uBNR}g60f2P>#+eFQH|HI37fGKyHJC-U;&LA%4TsT%aU_kT6A3wjqlgJ)PvJ}4!fg!U4*rF&1ip818Xw?{z`Pqh z7{F%&@qc3&_i;|(KZ5_@0Y>pq*a8S46cG_Ii6hA*g`|=%Fo<|zH;E)k*v@$2r_v^) W3ZlI-O1hLjv?hDEk_szD%Krk*4mMK& diff --git a/tests/roms/AccuracySNES/build/accuracysnes.sfc b/tests/roms/AccuracySNES/build/accuracysnes.sfc index 702b96b21312e41b22d9f1360e085edf4cd94b7d..18ac6ce394514217193bb23fdc9e1a3e21280203 100644 GIT binary patch delta 2256 zcmbtUc~nzp7Qf$nlqFRlppIIL39TYvTj*FB=ZuP>(=!$XJ8E63;|6s{YaP4DB%z38 z-(HfJ3-y!pxd z1fC#EUWr1~)#Mr+SEh`{rzItG0G>H?EySd_QbHX_k^SnTxiobm8d>d)Gms_sjgdhQ z%YI^kvqvn#!sjuIQ$jOlQJGe`CNA>%^!%EbtR*HxKZu?#xy>eaiT>0$NvwYzNgIWd z z9*f45JVugZq;uYfN#R4-?*9Rn?r@>OsXt5kCC;E9rtc#?rz78bvkbWzb); zXv+ZJw>PFmIRS1Gov9|-rt?k5YZv2 zep{C`+TKqzry=?~gzs}29ujRC7D5MRqO$FXSc;xOK5~zbEb)`$c(Rix!vV6ao;*}f z&I^*t5Lq53$2YWWM#Lq8ULHprk)5r(QAa8UE3y?66*Cnp6;g#+5mxL~99MKJ9w>y$ zXO-iXQy))ho3csSraYm%t|V1Ysm7`#s@bYaRjtaV+NwIFim7^4gj%Q`seVyi zq@JZ-p?+7bR&(lR^{47C^*wd6Mx=R3^QvZ{W}`-_;WSN}cFkFt=BB1kGf4ZK_7&{{ z?K*9Z)~0RL?$aLEUeSK973#8df6z_Wy{W6xnRG$je%(pk4PBpZh(23CML$cwQZLur z^jq}@^=I_A^>K#bhTj^d8WtJeG3X3_!%jn|;iBQLL0}Xce{Y;&Tw$y-8jXJAZsQT- zCF76A6w@e^Y@%s~X}L*ivY0}q{iYM9%ceflU}`KiiJC#JqN*vH`jFa19iy&M$UMZH zYnGU2n^&1@%ocOdyvN*OK4bpMeBUgvh%FN=#g>JZ4Hl)vVToAUET=6uEyz05`kYl_ zon>8Z{lIFqHd^;vPg;AdiF5`%o-U@#Wpov7rW@%t`V0Cx{e*3V?M2(mwt2R-w#_!X zEo9qcJ8HXXdtghq=h}~4cIRi#3(jwy0!GXfFlEeq=51yRV`pR$W;fHxTxRYw zLRY5iC0DVl+_lMNaPh94uEVa2u5VoN?hJQNofqf3yD+|2h9HKjPE*QT#Z*h@ZwUw96ps>78XAN$kO<|Vd{ls@pqEi8Jtm8&p!Hcqe|#j1D3-ksRWKXY!`rX{{t6r6 z9e5Y!z+9LIZ@_$50Dpn^U=zq-AuNK$umqMuIaI)#ungo-4a;E#tb|pt8rDE1tc7*( z7W@sQ@B!2S1!mX`TfhQVOuQEAKmkfnff_WR1sY3n03!nz)};pn7{LTiaAS2WaNxxn z{a9rH8sI}{#9AZJ1oaTay2J1hY=dU_5-!3e_zJ7O0w2S6_&c;)*moa_)b?hCj8G58O3K`R`9WR!wZkr1`PLHHC~ z`!Bc&x1a}Z!`JW)^kReW!oT5jIE+p2gd@-mSFz>a!9BPSNAUu_hX?Qj{0Mz`4G0m4 uL~-Z|ypSi+05lL?hZ~T9my?79cs+w@M>dg*a$DN63GwS&Z&4c3We)KEs3G%6XAKVcM(KB@AjorAP4@hYjN*C*{DRrJlI9?6lg z*jU^vk5awzraoz>i9VTJNM!U^$x<@dHZdiUka=yBQ&*93#~Q`0C6~HpYf_s!Pqe+2 zHbMysit*LNM1MBEPPo@(s63ZDbrm@#7shnsM2fMDp^S! zL!vT0%k=Cfh0I(ZnaH$y$y3aDj?5&J>DuWzOe;fDm}MN-PrU< zBx)dXSRT;f+9&s)DjZy z=0t_OD8!43!=lOtQAtFU)flTM)OCv4D5iQ87wT`;kJe|XW~iQ4m8;gNwy6v%PSvhD ztLj(XQ>CeM)w9*5>ZR&xwL)!CH>nS*yVW<;qw0y8Jk7J3Uumi|3XMe*(Hz!X(%jNK z(4=V}*Zx2|U%Oaap?yvJw$`m}(H_-a(SE5-);*#7k#3=Gg>I9sPUqG&>AG|m6uJT3 zecgC{zP?1iOkb_vrMKuC^d0)M`ab<#eVSpKq1f<(VYT6PgU%2%959?S+%()bNQ^U# zKQS>}|{oT8t56r}4b;bK^tf1k;nIQqxM)W|PX~GreOvWx8q_GKtMO<|6Zp<_hyR zv(_9kcbHF`ubc0g(=9VCig}jBmP*S`i^&qPyk|LMxnX%=$)>+g&!d;nRkWO@=^%Z8 zK1UDGqjZ*4W?f)iVy(9Bv>L6PHD>LyzHhx|y<<(bNo{j%&)ZhnHrZ-zc3ZRUuRmYEO)MPZgJ|ILFYc_Y3CK^Z6|R}bUo=>=vw94 z;F7znu4dO!*Jal&SAu(zyTD!IUhdxPR=GXyz3$`gtL{7QWKXWA*i-6R>DlN}dh8xW z$kXmQ3soTyRX}K z!#C>7^w0D^<6q*h_SgDd{#O49f3JVoFA3xY<_5|Gm4R)6x_}|z3~+&HV1M9P;9TI7 zz(8O)kjzeEXR&kHpR+HqYo+XFR>>M!AG?P=z@B0+vIFcen-ct9FfUjXEDbJ=T~4A5 zDY2cIR8nSTcd$QrH<%R44Lubq3sr=+hs+^9)Dh|l^@nbUz>VW_xni!ItK_zFb)1W9 z;f`__xB+g2OXqX=0=|fUo?pRl;A?q1AK}~i#+eFQG-{p37fGKyHJZaVF8UgybUXCg73Gm z8}(2@4Gpx=!7e0m3Rye~A(jC~m|%trULl+x0kA?u0V8-cp&5IGsDD8eVKfM_8_|lt zVlVp8kALF25c($C@DAR^J^@HOIs_=k@Bu!=WdX@2IDo(5JsiXdoWw`?7#DE~nIwyh zfjbVxd0fC%`~$tXhQsK>I5M7O5D7VgqlgP+PvHyP!fg!U4*rEN1-^H28t>zbz`Pqh z7{I3j@qc3&_i;|(KZ5_@0Y>pq*a8S46cG_INg$~tjii&$F^EKAH_0SL*v Vec { c7_01(), c7_04(), c7_05(), + c7_06(), c7_02(), c7_08(), c7_10(), @@ -2440,6 +2441,201 @@ fn c7_05() -> Test { ) } +/// Time Over is observable by the start of the line the sprites paint on — `V = OBJ.YLOC + 1, +/// H = 0` (`C7.06`). +/// +/// # 8x8 sprites cannot reach Time Over at all +/// +/// The tile budget is 34 per line, but range evaluation *stops* at the 33rd in-range sprite. So the +/// most tiles 8x8 sprites can ever contribute is 32 — under the limit, permanently. `C7.05`'s forty +/// 8x8 sprites set Range Over and leave Time Over clear, and the `eval-line-213e` probe reports the +/// same thing for the same reason. +/// +/// This uses **20 sprites of 16x16**: two tiles each, so 40 tiles trips the budget, while 20 sprites +/// stays well under the 32-sprite range limit. That separation is deliberate and is itself an +/// assertion — Time Over must set while Range Over stays **clear**, so a core that raises the two +/// together, or raises whichever it computes first, fails. +/// +/// # The bracket is across the line boundary, not within a line +/// +/// Unlike `C7.05` there is no index to sweep: the asserted position is fixed. So this brackets it — +/// clear while the sprites' own line is still being evaluated, set once the line they paint on has +/// begun: +/// +/// | phase | sampled on | Time Over | Range Over | +/// |---|---|---|---| +/// | A | `V = 100` (the eval line) | **clear** | clear | +/// | B | `V = 101` (`YLOC + 1`) | **set** | clear | +/// +/// Phase B samples at `V = 101` rather than late on line 100 on purpose. RustySNES raises the flag +/// at `HBLANK_START_DOT` of the eval line, which is *earlier* than the dossier requires; asserting +/// that exact dot would fail a core that raises it at `(101, 0)` exactly, which the assertion +/// permits. Sampling on line 101 accepts both and still rejects anything later. +/// +/// # The low-tile control +/// +/// Phase A alone cannot tell "not yet" from "never", and phase B alone cannot tell "correct" from +/// "always set". So a third run keeps the same 20 sprites at 8x8 — 20 tiles, under the budget — and +/// requires Time Over **clear** at phase B's sampling point. Without it a core that reports Time +/// Over whenever any sprite is on the line passes both A and B. +fn c7_06() -> Test { + let mut a = Asm::new(); + a.l("jmp @body"); + a.label("handler"); + a.l("php"); + a.l("sep #$20"); + a.l(".a8"); + a.l("pha"); + a.l("lda $213E ; sample STAT77 at the IRQ dot"); + a.l("sta f:$7E0156"); + a.l("lda $4211 ; acknowledge so the line drops"); + a.l("pla"); + a.l("plp"); + a.l("rti"); + a.label("body"); + a.l("rep #$30"); + a.l("phk"); + a.l("plb"); + a.c("Group C is outside bank $00, so route through the bank-$00 far-IRQ shim."); + a.l("rep #$20"); + a.l("lda #.loword(@handler)"); + a.l("sta a:V_IRQ_VEC_FAR"); + a.l("sep #$20"); + a.l("lda #^@handler"); + a.l("sta a:V_IRQ_VEC_FAR + 2"); + a.l("rep #$20"); + a.l("lda #irq_far_shim"); + a.l("sta a:V_IRQ_VEC"); + + a.c("--- phase A: 20 16x16 sprites (40 tiles), sampled on the eval line V = 100 ---"); + setup_tile_budget_sprites(&mut a, "a", true); + arm_over_irq_on_line(&mut a, 100); + a.l("lda f:$7E0156"); + a.l("and #$80"); + a.assert_a8( + 0x00, + "Time Over was already set on the sprites' own line (V = 100), earlier than V = YLOC + 1", + ); + + a.c("--- phase B: same sprites, sampled on the line they paint on, V = 101 ---"); + setup_tile_budget_sprites(&mut a, "b", true); + arm_over_irq_on_line(&mut a, 101); + a.l("lda f:$7E0156"); + a.l("and #$80"); + a.assert_a8( + 0x80, + "Time Over had not set by V = YLOC + 1 with 40 sprite-tiles due on the line", + ); + a.c("...and Range Over must still be clear: 20 sprites is well under the 32-sprite limit."); + a.l("lda f:$7E0156"); + a.l("and #$40"); + a.assert_a8( + 0x00, + "Range Over set alongside Time Over with only 20 sprites on the line — the two flags are \ + being raised together instead of on their own conditions", + ); + + a.c("--- control: the same 20 sprites at 8x8 is 20 tiles, under the 34 budget ---"); + setup_tile_budget_sprites(&mut a, "c", false); + arm_over_irq_on_line(&mut a, 101); + a.l("lda f:$7E0156"); + a.l("and #$80"); + a.assert_a8( + 0x00, + "Time Over set with only 20 sprite-tiles due, so phase B's reading meant 'sprites are \ + present', not 'the tile budget was exceeded'", + ); + a.l("lda #$8F"); + a.l("sta $2100 ; forced blank again, as the rest of the battery expects"); + a.l("stz $212C"); + a.finish( + "C7.06", + 'C', + "TimeOver by YLOC+1", + Provenance::Documented( + "fullsnes and the SNESdev Wiki: the sprite tile budget is 34 per line and Time Over is \ + raised by the fetch phase, so it reads set by the start of the line the sprites paint on", + ), + Kind::Scored, + None, + ) +} + +/// 20 sprites at Y = 100, `large` selecting 16x16 (2 tiles each, 40 total — over the 34-tile +/// budget) or 8x8 (20 tiles — under it). Everything else is parked below the visible area. +/// +/// Twenty is chosen to sit **under** the 32-sprite range limit, so Range Over cannot trip and the +/// two flags stay independently observable. +fn setup_tile_budget_sprites(a: &mut Asm, tag: &str, large: bool) { + a.l("sep #$20"); + a.l("stz $2101 ; OBJSEL pair 0: 8x8 small, 16x16 large, name base 0"); + a.l("stz $2102"); + a.l("stz $2103"); + a.l("rep #$10"); + a.l("ldx #$0000"); + a.label(&format!("fill_{tag}")); + a.c("X = index * 8, so the sprites tile across the line rather than stacking at one column."); + a.l("txa"); + a.l("asl"); + a.l("asl"); + a.l("asl"); + a.l("sta $2104 ; X"); + a.l("cpx #$0014 ; 20 in range"); + a.l(&format!("bcs @off_{tag}")); + a.l("lda #100"); + a.l(&format!("bra @sety_{tag}")); + a.label(&format!("off_{tag}")); + a.l("lda #$F0 ; below the visible area in 224-line mode"); + a.label(&format!("sety_{tag}")); + a.l("sta $2104 ; Y"); + a.l("stz $2104 ; tile"); + a.l("stz $2104 ; attr"); + a.l("inx"); + a.l("cpx #$0080"); + a.l(&format!("bne @fill_{tag}")); + a.c("High table: bit 1 of each 2-bit field is the size select for that sprite."); + a.l("stz $2102"); + a.l("lda #$01"); + a.l("sta $2103 ; OAMADDR = word $100"); + a.l("ldx #$0000"); + a.label(&format!("hi_{tag}")); + a.l(&format!("lda #${:02X}", if large { 0xAA } else { 0x00 })); + a.l("sta $2104"); + a.l("inx"); + a.l("cpx #$0020"); + a.l(&format!("bne @hi_{tag}")); +} + +/// Arm an HV-IRQ at `H = 0` of scanline `line` and run one frame with objects on the main screen. +/// +/// `HTIME = 0` with the measured ~93-dot IRQ-to-sample latency puts the read near dot 93 of that +/// line — past `H = 0` (so a core raising Time Over exactly there is accepted) and well before the +/// line ends. +fn arm_over_irq_on_line(a: &mut Asm, line: u16) { + a.l("sep #$20"); + a.l("lda #$FF"); + a.l("sta f:$7E0156 ; poison: a handler that never runs cannot look like either verdict"); + a.l("stz $4207"); + a.l("stz $4208 ; HTIME = 0"); + a.l(&format!("lda #{line}")); + a.l("sta $4209"); + a.l("stz $420A ; VTIME"); + a.l("lda $4211 ; clear any stale latch"); + a.l("lda #$0F"); + a.l("sta $2100 ; display on, full brightness"); + a.l("lda #$10"); + a.l("sta $212C ; OBJ on the main screen"); + a.l("cli"); + a.l("lda #$30"); + a.l("sta $4200 ; HV-IRQ"); + a.l("rep #$30"); + a.l("jsl wait_vblank_far ; span one complete active period"); + a.l("sep #$20"); + a.l("sei"); + a.l("stz $4200 ; disarm before reading anything back"); + a.l("lda $4211"); +} + /// 40 8x8 sprites in range on line 100 starting at OAM index `first`; everything else parked below /// the visible area. `first` is what moves the 33rd in-range sprite's index, and with it the dot at /// which Range Over trips. From d12fe2e46b3af609feba417f3b3098e5d0f8d4a1 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Fri, 31 Jul 2026 16:39:48 -0400 Subject: [PATCH 2/3] docs(accuracysnes): record why the far-IRQ shim does not unblock C7.07/C7.12 C7.12 is a scene needing known-field publication (v1.29.0, shared with C9.03/C9.06). C7.07 is reachable as a test but would fail: RustySNES skips a fully-offscreen sprite before either counter, so no false Time Over can arise, and the row needs a PPU behaviour change first. That change should not be made on the available evidence -- a single errata line, with snes9x the only cross-check, and snes9x is already scanline-granular here and fails both C7.05 and C7.06 for that reason. The $F8/$F9 retraction is what this caution exists for. Co-Authored-By: Claude Opus 5 (1M context) --- docs/accuracysnes-plan.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/accuracysnes-plan.md b/docs/accuracysnes-plan.md index 33f9d26d..fb8017c0 100644 --- a/docs/accuracysnes-plan.md +++ b/docs/accuracysnes-plan.md @@ -540,6 +540,29 @@ its Range Over does not track the index — a scanline-granularity flag. RustySN anchored to MesenCE on the *line* by `scripts/probes/eval-line-213e`; the *dot* is documentation-anchored only, because the Mesen2 headless runner times out in this environment. +#### The two `C7` rows still uncovered, and why the new machinery does not reach them + +`C7.05`/`C7.06` landed on the far-IRQ shim, so it is worth saying what that shim does **not** +unblock. + +- **`C7.12`** (16x32 under OBJ interlace renders as 16x16) is a **scene**, and needs the cart to + publish a scene only on a *known field* — it was written once and produced three different hashes + on three emulators because it asked a question whose answer alternates every frame. That is + `v1.29.0` machinery (`run_scenes`, not a scene), shared with `C9.03`/`C9.06`. + +- **`C7.07`** (Time Over false positive: first sprite 16x16+ at `X = 0-255` with others at negative + X) is reachable *as a test* with `C7.06`'s setup, but it would fail — because RustySNES does not + model the errata. `compute_over_flag_dots` skips a fully-offscreen sprite outright + (`obj.x > 256 && obj.x + w - 1 < 512`), before either counter, so no false Time Over can arise. + The row therefore needs a **PPU behaviour change first**, not a cart test. + + That change should not be made on this evidence. It rests on a single errata line, and the only + available cross-check is snes9x — which is already known to be scanline-granular here and fails + both `C7.05` and `C7.06` for that reason, making it a poor arbiter for a third sprite-flag quirk. + Mesen2 cannot arbitrate while its headless runner times out in this environment. Encoding an + unvalidated behaviour attribution is what the `$F8`/`$F9` retraction in the CHANGELOG exists to + warn against. Get a working Mesen2 (or another oracle) first. + #### `C7.06` — 8x8 sprites cannot reach Time Over at all The tile budget is 34 per line, but range evaluation **stops** at the 33rd in-range sprite, so 8x8 From 88619d25471683341a50346c239fb992fa506821 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Fri, 31 Jul 2026 16:58:01 -0400 Subject: [PATCH 3/3] test(accuracysnes): give C7.06 a never-fired-IRQ sentinel Raised in review. With the poison $FF still in the slot, $FF satisfies or violates the flag tests by accident: phase A reads bit 7 set and blames 'Time Over already set', phase B passes bit 7 and then blames 'Range Over set alongside'. Both name a cause that did not happen, and ERROR_CODES.md is meant to be the complete account of failure bytes. Each phase now asserts the sampled byte is not the poison first, with its own code. Verified by suppressing the arm ($4200 = $00): the row now fails code 1, 'the H/V IRQ never fired', instead of a flag assertion. Codes shifted, so the injection matrix was re-run and the docs corrected: flag-on-any-sprite now fails code 7, never-setting code 5, set-at-dot-0 code 2; 1/3/4 are the sentinels. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 4 +- docs/accuracysnes-plan.md | 11 +++- tests/roms/AccuracySNES/ERROR_CODES.md | 11 ++-- tests/roms/AccuracySNES/asm/tests_group_a.s | 58 +++++++++++++++--- .../AccuracySNES/build/accuracysnes-pal.sfc | Bin 262144 -> 262144 bytes .../roms/AccuracySNES/build/accuracysnes.sfc | Bin 262144 -> 262144 bytes tests/roms/AccuracySNES/gen/src/tests/ppu.rs | 17 +++++ 7 files changed, 85 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07fff215..a9300e3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,8 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 A low-tile control (the same 20 sprites at 8x8, 20 tiles) must read clear at phase B's sampling point, or "set" would only have meant "sprites are present". Four injections, each failing its own - code: flagging on any sprite fails code 4 (the control), never setting fails code 2, and setting at - dot 0 fails code 1. + code: flagging on any sprite fails code 7 (the control), never setting fails code 5, and setting at + dot 0 fails code 2. snes9x fails it and is recorded as an expected divergence (`SNES9X_KNOWN_FAILURES` 13 → 14): it already reads Time Over set on `V = 100`, flagging the overflow a line early because it evaluates diff --git a/docs/accuracysnes-plan.md b/docs/accuracysnes-plan.md index fb8017c0..9015c1ff 100644 --- a/docs/accuracysnes-plan.md +++ b/docs/accuracysnes-plan.md @@ -579,8 +579,15 @@ dot would fail a core that raises it at `(101, 0)` exactly, which the assertion A low-tile control completes it: the same 20 sprites at 8x8 (20 tiles) must read Time Over **clear** at phase B's sampling point. Without it, a core reporting Time Over whenever any sprite is on the -line passes both A and B. Four injections, each failing its own code — flag on any sprite fails -**code 4** (the control), never setting fails **code 2**, and setting at dot 0 fails **code 1**. +line passes both A and B. Four injections, each failing its own code — flag on any sprite fails **code 7** (the control), +never setting fails **code 5**, and setting at dot 0 fails **code 2**. Codes 1, 3 and 4 are the +per-phase sentinels below. + +Each phase also carries a **sentinel** on the sampled byte (codes 1, 3, 4). Without it a never-fired +IRQ leaves the poison `$FF` in the slot, and `$FF` satisfies or violates the flag tests by accident — +phase A would blame "Time Over already set", phase B would pass bit 7 and then blame "Range Over set +alongside". Both name a cause that did not happen, and `ERROR_CODES.md` is supposed to be the +complete account of failure bytes. snes9x fails this row too, and is recorded as an expected divergence: it already reads Time Over set on `V = 100`, flagging the overflow a line early because it evaluates and paints in one pass. The diff --git a/tests/roms/AccuracySNES/ERROR_CODES.md b/tests/roms/AccuracySNES/ERROR_CODES.md index abc702bd..73b50055 100644 --- a/tests/roms/AccuracySNES/ERROR_CODES.md +++ b/tests/roms/AccuracySNES/ERROR_CODES.md @@ -1280,10 +1280,13 @@ Provenance: **Documented** (fullsnes and the SNESdev Wiki: the sprite tile budge | Code | Byte | Meaning | |---|---|---| -| 1 | `$02` | Time Over was already set on the sprites' own line (V = 100), earlier than V = YLOC + 1 | -| 2 | `$04` | Time Over had not set by V = YLOC + 1 with 40 sprite-tiles due on the line | -| 3 | `$06` | Range Over set alongside Time Over with only 20 sprites on the line — the two flags are being raised together instead of on their own conditions | -| 4 | `$08` | Time Over set with only 20 sprite-tiles due, so phase B's reading meant 'sprites are present', not 'the tile budget was exceeded' | +| 1 | `$02` | the H/V IRQ never fired, so STAT77 was never sampled at all | +| 2 | `$04` | Time Over was already set on the sprites' own line (V = 100), earlier than V = YLOC + 1 | +| 3 | `$06` | the H/V IRQ never fired, so STAT77 was never sampled at all | +| 4 | `$08` | the H/V IRQ never fired, so STAT77 was never sampled at all | +| 5 | `$0A` | Time Over had not set by V = YLOC + 1 with 40 sprite-tiles due on the line | +| 6 | `$0C` | Range Over set alongside Time Over with only 20 sprites on the line — the two flags are being raised together instead of on their own conditions | +| 7 | `$0E` | Time Over set with only 20 sprite-tiles due, so phase B's reading meant 'sprites are present', not 'the tile budget was exceeded' | ### C7.02 — Time Over is slivers diff --git a/tests/roms/AccuracySNES/asm/tests_group_a.s b/tests/roms/AccuracySNES/asm/tests_group_a.s index b1512402..d83f39d6 100644 --- a/tests/roms/AccuracySNES/asm/tests_group_a.s +++ b/tests/roms/AccuracySNES/asm/tests_group_a.s @@ -25754,11 +25754,18 @@ CATALOG_IMPL = 1 sei stz $4200 ; disarm before reading anything back lda $4211 + sep #$20 + .a8 + lda f:$7E0156 + cmp #$FF + bne :+ + jmp @fail1 + : lda f:$7E0156 and #$80 cmp #$00 beq :+ - jmp @fail1 + jmp @fail2 : ; --- phase B: same sprites, sampled on the line they paint on, V = 101 --- sep #$20 @@ -25826,18 +25833,32 @@ CATALOG_IMPL = 1 sei stz $4200 ; disarm before reading anything back lda $4211 + sep #$20 + .a8 + lda f:$7E0156 + cmp #$FF + bne :+ + jmp @fail3 + : + sep #$20 + .a8 + lda f:$7E0156 + cmp #$FF + bne :+ + jmp @fail4 + : lda f:$7E0156 and #$80 cmp #$80 beq :+ - jmp @fail2 + jmp @fail5 : ; ...and Range Over must still be clear: 20 sprites is well under the 32-sprite limit. lda f:$7E0156 and #$40 cmp #$00 beq :+ - jmp @fail3 + jmp @fail6 : ; --- control: the same 20 sprites at 8x8 is 20 tiles, under the 34 budget --- sep #$20 @@ -25909,7 +25930,7 @@ CATALOG_IMPL = 1 and #$80 cmp #$00 beq :+ - jmp @fail4 + jmp @fail7 : lda #$8F sta $2100 ; forced blank again, as the rest of the battery expects @@ -25920,33 +25941,54 @@ CATALOG_IMPL = 1 sta f:$7EE010 jml test_restore @fail1: - ; Time Over was already set on the sprites' own line (V = 100), earlier than V = YLOC + 1 + ; the H/V IRQ never fired, so STAT77 was never sampled at all sep #$20 .a8 lda #$02 sta f:$7EE010 jml test_restore @fail2: - ; Time Over had not set by V = YLOC + 1 with 40 sprite-tiles due on the line + ; Time Over was already set on the sprites' own line (V = 100), earlier than V = YLOC + 1 sep #$20 .a8 lda #$04 sta f:$7EE010 jml test_restore @fail3: - ; Range Over set alongside Time Over with only 20 sprites on the line — the two flags are being raised together instead of on their own conditions + ; the H/V IRQ never fired, so STAT77 was never sampled at all sep #$20 .a8 lda #$06 sta f:$7EE010 jml test_restore @fail4: - ; Time Over set with only 20 sprite-tiles due, so phase B's reading meant 'sprites are present', not 'the tile budget was exceeded' + ; the H/V IRQ never fired, so STAT77 was never sampled at all sep #$20 .a8 lda #$08 sta f:$7EE010 jml test_restore +@fail5: + ; Time Over had not set by V = YLOC + 1 with 40 sprite-tiles due on the line + sep #$20 + .a8 + lda #$0A + sta f:$7EE010 + jml test_restore +@fail6: + ; Range Over set alongside Time Over with only 20 sprites on the line — the two flags are being raised together instead of on their own conditions + sep #$20 + .a8 + lda #$0C + sta f:$7EE010 + jml test_restore +@fail7: + ; Time Over set with only 20 sprite-tiles due, so phase B's reading meant 'sprites are present', not 'the tile budget was exceeded' + sep #$20 + .a8 + lda #$0E + sta f:$7EE010 + jml test_restore .endproc ; C7.02 — Time Over is slivers diff --git a/tests/roms/AccuracySNES/build/accuracysnes-pal.sfc b/tests/roms/AccuracySNES/build/accuracysnes-pal.sfc index 0700bf68a5bf40ff9af012e0f09f0c5186a46212..8b1cbeca76eef0df06187fa8c0ec040e2054b8cf 100644 GIT binary patch delta 510 zcmY+AO-LJ26ou!`3$1}DQ>B8{f~kTXb)k?_yHEsOR2Z;@`m=E1LbR^MjfiwYrKxsgB-6hxg#M0EAext#O8`<^Uwvdm@wN}=rk z!N`1G%H<_+qi+h0-Y|&t?vMyqY9M);GDRYUH0)HN8wWB_hZh;(V^5Nt_QW)34>j4Z zy)T>I5+hb(q}W~!_f+UtKDS4>LgP+kh0BX${}PO^tMFQd^+-|s&X+qk8XhH}4ZF7C zHDA(giZ6tg?Xh$(gg z$7IO2DDn#XyMH3%dKw0BTM1U_Xh}@XbmQF;oVIo;mWSqvVH@--xURx5E+`Q9ohuUf zi+<*Kj3Jbh=SIOP9YVNYg<|K9dL43f>^DV#sv>S*B?7f=SNx!Y=!E)cf>^vD{HsycFP%7 z?hlO{?zJZdNjTyj(<%bLb^6AWJ11MvB%@&CLDT&~Eo;Ksf_Sw&dqyW905ZCi j5m!QC1=ST~sAx_@w;Ec~QLc;<1{%qvfv%+it$+Ohq9m|S diff --git a/tests/roms/AccuracySNES/build/accuracysnes.sfc b/tests/roms/AccuracySNES/build/accuracysnes.sfc index 18ac6ce394514217193bb23fdc9e1a3e21280203..7120a66bbb7af1c0ac364b52daccfa8fbb718ae6 100644 GIT binary patch delta 510 zcmZo@5NK!+*l?$wW#eZ*p5_Pj+aJ_3u2z(Kq_94WvF_ym3(P)07J-AJ0?QyuZY*V-EPZ#`Ap?+R&HoH185uS(`@CFsvVma&h=v=)K7GeB#suM}<%We*o1V6cu}d*?^~n^F=FHXW`xxu` zH$AF5c>?6j($yy|7=hxY+XYuME(zJfl)|XwvzVD<3G?(N%=Jr|WtK62UB*0XIkV6T a=A|o`udHNVzlyoBy^WD+dmAHj)jt4si_$Cr delta 439 zcmY*VJxIe~5Y6RtsZ=Z$Du@#u1r zm^O0(xDZdMCSn^Rb|1q6J{2M2xWn_%zzCVp zI8I2ga#!i_BS Test { ) } +/// The sampled byte must not still be the poison value. +/// +/// Without this an IRQ that never fires leaves `$FF` in the slot, and `$FF` satisfies or violates +/// the flag tests *by accident*: phase A reads bit 7 set and blames "Time Over already set", phase B +/// passes bit 7 and then blames "Range Over set alongside Time Over". Both name a cause that did not +/// happen. `ERROR_CODES.md` is meant to be the complete account of failure bytes, so a distinct code +/// for "the handler never ran" is the difference between a diagnosis and a red herring. +fn assert_handler_ran(a: &mut Asm) { + a.l("sep #$20"); + a.l("lda f:$7E0156"); + a.l("cmp #$FF"); + a.fail_if_eq("the H/V IRQ never fired, so STAT77 was never sampled at all"); +} + /// Time Over is observable by the start of the line the sprites paint on — `V = OBJ.YLOC + 1, /// H = 0` (`C7.06`). /// @@ -2510,6 +2524,7 @@ fn c7_06() -> Test { a.c("--- phase A: 20 16x16 sprites (40 tiles), sampled on the eval line V = 100 ---"); setup_tile_budget_sprites(&mut a, "a", true); arm_over_irq_on_line(&mut a, 100); + assert_handler_ran(&mut a); a.l("lda f:$7E0156"); a.l("and #$80"); a.assert_a8( @@ -2520,6 +2535,8 @@ fn c7_06() -> Test { a.c("--- phase B: same sprites, sampled on the line they paint on, V = 101 ---"); setup_tile_budget_sprites(&mut a, "b", true); arm_over_irq_on_line(&mut a, 101); + assert_handler_ran(&mut a); + assert_handler_ran(&mut a); a.l("lda f:$7E0156"); a.l("and #$80"); a.assert_a8(