From 48f34d68a4351e099b98ba1c3437d7a1a02a1d55 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Fri, 31 Jul 2026 15:34:19 -0400 Subject: [PATCH 1/2] =?UTF-8?q?test(accuracysnes):=20C7.05=20=E2=80=94=20R?= =?UTF-8?q?ange=20Over=20trips=20at=20OAM.INDEX=20*=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A fixed bracket would be vacuous. An H-IRQ is serviced ~22-27 dots after its HTIME, so a bracket tight enough to pin dot 65 sits inside the latency's own uncertainty, and one loose enough to be safe passes for any set dot across most of the line -- the F1.09 shape. What pins H = OAM.INDEX * 2 is that the set dot must MOVE with the index. Both phases sample the same dot and change only which OAM entry is the 33rd in-range sprite: index 32 (set dot 65, reads set) against index 72 (set dot 145, reads clear). Phase B also re-reads $213E in the same frame's vblank, because "clear at dot 105" is otherwise exactly what a core that never sets the flag would report; the flags clear at frame start, not on read, so the later read sees the same frame. Three injections, each failing its own code: - a fixed set dot -> code 2 - never setting the flag -> code 1 - correct for a low index, never for high -> code 3, the guard phase A cannot cover New runtime machinery, which no v1.28.0 row was supposed to need. irq_trampoline is a bank-local jmp (V_IRQ_VEC), so only bank-$00 groups could install an IRQ handler at all; Groups C-G are relocated out of bank $00 and none ever had one. irq_far_shim (bank $00) plus the 24-bit V_IRQ_VEC_FAR at $0058 fixes that for every relocated group: jml pushes nothing, so a far handler still ends in a plain rti. Opt-in, and the far vector defaults to irq_stub, so default behaviour is unchanged. snes9x fails the row and is recorded as an expected divergence (SNES9X_KNOWN_FAILURES 12 -> 13): it reads set in both phases, so its Range Over is scanline-granular rather than per-sprite. RustySNES's position is anchored to MesenCE on the LINE by the eval-line probe; the DOT is documentation-anchored only, because the Mesen2 headless runner times out in this environment. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 22 + docs/accuracysnes-coverage.md | 4 +- docs/accuracysnes-plan.md | 39 +- scripts/accuracysnes/crossval.sh | 11 +- tests/roms/AccuracySNES/ERROR_CODES.md | 10 + tests/roms/AccuracySNES/SOURCE_CATALOG.tsv | 435 +++++++++--------- tests/roms/AccuracySNES/asm/runtime.inc | 21 +- tests/roms/AccuracySNES/asm/runtime.s | 20 + tests/roms/AccuracySNES/asm/tests_group_a.s | 397 ++++++++++++---- .../build/accuracysnes-exhirom.sfc | Bin 4259840 -> 4259840 bytes .../AccuracySNES/build/accuracysnes-hirom.sfc | Bin 65536 -> 65536 bytes .../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 | 180 ++++++++ 15 files changed, 831 insertions(+), 309 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ad88560..37157d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **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 + enough to be safe passes for any set dot. Instead both phases sample the **same** dot and change + only which OAM entry is 33rd in range — index 32 (set dot 65, reads set) against index 72 (set dot + 145, reads clear) — so the flag has to *move with the index*. Phase B also re-reads in the same + frame's vblank, since "clear" is otherwise what a core that never sets the flag reports. + + Three injections, each failing its own code: a fixed set dot fails code 2, never setting fails + code 1, and setting correctly for a low index but never a high one fails code 3 — the guard phase A + cannot cover. + + This needed **new runtime machinery**, which no `v1.28.0` row was supposed to. `irq_trampoline` is + a bank-local `jmp (V_IRQ_VEC)`, so only bank-`$00` groups could install an IRQ handler; Groups + C-G, relocated out of bank `$00`, could not, and none ever had. New `irq_far_shim` plus the 24-bit + `V_IRQ_VEC_FAR` at `$0058` fixes it for every relocated group — `jml` pushes nothing, so a far + handler still ends in a plain `rti`. Default behaviour is unchanged: the shim is opt-in and the far + vector defaults to `irq_stub`. + + snes9x fails the row and is recorded as an expected divergence (`SNES9X_KNOWN_FAILURES` 12 → 13): + it reads set in both phases, so its Range Over is scanline-granular rather than per-sprite. + - **AccuracySNES: recorded what each remaining `v1.28.0` row actually needs.** Several rows filed as "needs no new machinery" need rather more; `A5.18` is parked, `A6.15` needs its own design, `C7.05`/`C7.06` are reachable via an H-IRQ bracket, and `B2.07`/`B2.09` are `v1.29.0`. The diff --git a/docs/accuracysnes-coverage.md b/docs/accuracysnes-coverage.md index dfb2d45d..2e3b7652 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 | 6 | 6 | C7.05, C7.06, C7.07, C7.12 | +| `C7` | 16 | 7 | 6 | C7.06, 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 | -**292 of 443** enumerated assertion rows covered by an on-cart test, plus **53** covered only by a rendered scene (`docs/adr/0013`) — **345 of 443** in total. +**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. 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 31eacb67..0d0f4f6e 100644 --- a/docs/accuracysnes-plan.md +++ b/docs/accuracysnes-plan.md @@ -468,7 +468,8 @@ before the H counter wraps and silently returns a plausible small number. | `A5.19` | **landed** | see below | | `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`, `C7.06` | **reachable — sample with an H-IRQ bracket** | Not an accuracy gap: re-measured, `range_over` first reads set at scanline **100**, MesenCE's value. What remains is sampling *resolution*. See below. | +| `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. | | `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". | @@ -495,7 +496,41 @@ Injecting at `op_brk`'s PBR push moved **no** timing at all — it broke `A6.13` — so RustySNES's `BRK` cycle cost does not come from that bus access. Trace where it does come from before designing this row's injection test. -#### `C7.05`/`C7.06` — why the bracket, and the two traps +#### `C7.05` — a fixed bracket is vacuous; move the index instead + +An H-IRQ is serviced ~22-27 dots after its `HTIME`, so a bracket tight enough to pin dot 65 sits +inside the latency's own uncertainty, while one loose enough to be safe passes for any set dot across +most of the line. That is the `F1.09` shape. + +What pins `H = OAM.INDEX * 2` is that the set dot must **move with the index**. Both phases sample +the *same* dot and change only which OAM entry is the 33rd in-range sprite: + +| phase | in-range sprites | 33rd is index | set dot `2i + 1` | read at ~dot 105 | +|---|---|---:|---:|---| +| A | `0..39` | 32 | 65 | **set** | +| B | `40..79` | 72 | 145 | **clear** | + +Phase B additionally re-reads `$213E` in the vblank of the *same* frame and requires it set by then — +without that, "clear at dot 105" is also what a core that never sets the flag would report. The flags +clear at frame start, not on read (`Ppu::advance`), so the later read sees the same frame. + +Three injections, each failing its own code: a fixed set dot fails **code 2**, never setting the flag +fails **code 1**, and setting correctly for a low index but never for a high one — which phase A +cannot catch — fails **code 3**, the guard. + +**Installing an IRQ handler needed new runtime machinery.** `irq_trampoline` is a bank-local +`jmp (V_IRQ_VEC)`, so that pointer can only name a bank-`$00` handler — Groups C-G, whose bodies are +relocated out of bank `$00`, could not install one at all, and no test outside `cpu.rs`/`bus.rs` ever +had. `irq_far_shim` (bank `$00`) plus the 24-bit `V_IRQ_VEC_FAR` at `$0058` fixes that for every +relocated group: point `V_IRQ_VEC` at the shim, put a 24-bit handler address in the far vector, and +the shim's `jml` reaches it while pushing nothing, so the handler still ends in a plain `rti`. + +**snes9x fails this row** and is recorded as an expected divergence: it reads SET in both phases, so +its Range Over does not track the index — a scanline-granularity flag. RustySNES's position is +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.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. **It does not.** Re-measured on `main`, `range_over` first reads set at **scanline 100**, which is diff --git a/scripts/accuracysnes/crossval.sh b/scripts/accuracysnes/crossval.sh index b7d0fafd..f5e9564b 100755 --- a/scripts/accuracysnes/crossval.sh +++ b/scripts/accuracysnes/crossval.sh @@ -147,7 +147,16 @@ ran=0 # as F1.10, on the same nocash-documented behaviour. (F1.09, the busy *duration* golden, still PASSES on # snes9x: its latch both sets and clears within the window at a slightly shorter count — 30 vs 33 — a # recorded golden difference, not a failing test.) Region-independent. -SNES9X_KNOWN_FAILURES=12 +# snes9x, +1 test (C7.05 "RangeOver dot = idx*2", fails phase B with code 2): the row asserts that +# Range Over trips at the *evaluation cycle of the 33rd in-range sprite*, H = OAM.INDEX * 2, by +# sampling the same dot with the 33rd sprite first at index 32 (set dot 65, must read SET) and then +# at index 72 (set dot 145, must read CLEAR). snes9x reads SET in both, i.e. its Range Over does not +# move with the index — a scanline-granularity flag rather than a per-sprite one, which is the +# modelling difference the row exists to detect. Documented by nocash fullsnes and the SNESdev Wiki +# (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, 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 9ad43dcf..27e562c7 100644 --- a/tests/roms/AccuracySNES/ERROR_CODES.md +++ b/tests/roms/AccuracySNES/ERROR_CODES.md @@ -1264,6 +1264,16 @@ Provenance: **Documented** (fullsnes and the SNESdev Wiki: sprite range evaluati | 1 | `$02` | Range Over was already set with only 2 sprites on the scanline, so the flag is stuck and phase 2 below would report it as a count | | 2 | `$04` | Range Over did not set with 40 sprites on one scanline at X = $100, so evaluation is skipping sprites it judges off-screen — a driver parking unused sprites to the left would see more of them survive than hardware allows | +### C7.05 — RangeOver dot = idx*2 + +Provenance: **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). Kind: scored. + +| Code | Byte | Meaning | +|---|---|---| +| 1 | `$02` | Range Over had not set by dot ~105 with the 33rd in-range sprite at OAM index 32 (dot 65) — the flag is later than OAM.INDEX * 2 | +| 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.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 23b5f8fe..49815317 100644 --- a/tests/roms/AccuracySNES/SOURCE_CATALOG.tsv +++ b/tests/roms/AccuracySNES/SOURCE_CATALOG.tsv @@ -116,220 +116,221 @@ 113 C11.08 C MPY busy during render Contested fullsnes and the SNESdev Wiki agree the Mode 7 multiplier is the renderer's and is busy during active display, but neither states which intermediate it holds at a given moment, and that is the only thing a cart can observe Golden 0x7EF091 C11.08 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.02 C Time Over is slivers Documented SNESdev Wiki, Sprites; fullsnes; anomie Scored 0x7EF094 C7.02 -117 C7.08 C Flags ignore $212C Documented SNESdev Wiki, Sprites; fullsnes Scored 0x7EF095 C7.08 -118 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 0x7EF096 C7.16 -119 C2.11 C VRAM locked in render Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF097 C2.11 -120 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 0x7EF098 C1.08 -121 C2.10 C Dropped write still incs Documented SNESdev Wiki, PPU registers; fullsnes; anomie Scored 0x7EF099 C2.10 -122 C1.06 C OAM addr reloads Documented SNESdev Wiki, OAM; anomie Scored 0x7EF09A C1.06 -123 C9.04 C Overscan moves vblank Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF09B C9.04,B4.02 -124 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 0x7EF09C C9.05 -125 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 0x7EF09D C2.09 -126 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 0x7EF09E C3.05 -127 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 0x7EF09F C3.05 -128 B1.01 B MEMSEL selects FastROM Documented SNESdev Wiki, Memory map / timing; fullsnes Scored 0x7EF0A0 B1.01 -129 B1.02 B JOYSER is 12 clocks Documented SNESdev Wiki, Memory map / timing; fullsnes Scored 0x7EF0A1 B1.02 -130 B2.04 B NTSC frame is 262 lines Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A2 B2.04 -131 B4.03 B RDNMI sets at vblank Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A3 B4.03 -132 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 0x7EF0A4 B2.01 -133 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 0x7EF0A5 - -134 B4.17 B NMI enable is a level Documented SNESdev Wiki NMITIMEN/RDNMI [ERRATA]; fullsnes $4200/$4210 Scored 0x7EF0A6 B4.06 -135 B4.04 B RDNMI is read-to-clear Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A7 B4.04 -136 B4.05 B RDNMI auto-clears Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A8 B4.05 -137 B4.08 B V-IRQ fires at VTIME Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A9 B4.08 -138 B4.12 B $4211 read releases IRQ Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0AA B4.12 -139 B4.15 B CPU revision (golden) Documented SNESdev Wiki, Timing; fullsnes Golden 0x7EF0AB G1.09 -140 B5.01 B 8x8 unsigned multiply Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AC B5.01 -141 B5.02 B 16/8 unsigned divide Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AD B5.02 -142 B5.03 B Divide by zero Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AE - -143 B5.04 B Mul/div overlap (golden) Contested SNESdev Errata states overlapping $4203/$4206 operation is undefined Golden 0x7EF0AF B5.03 -144 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 0x7EF0B0 B5.04 -145 B1.03 B Internal cycles are 6 Documented SNES Development Manual Bk I 21.1; SNESdev Wiki; fullsnes Scored 0x7EF0B1 B1.03 -146 B1.04 B DMA speed is uniform Documented SNES Development Manual Bk I 21.1 (DMA at 2.68MHz regardless of address) Scored 0x7EF0B2 B1.04 -147 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 0x7EF0B3 B2.06 -148 B2.05 B PAL frame is 312 lines Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0B4 B2.05 -149 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 0x7EF0B5 B4.14 -150 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 0x7EF0B6 B2.10 -151 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 0x7EF0B7 B4.07 -152 B4.09 B HV-IRQ needs both Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0B8 B4.09 -153 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 0x7EF0B9 B3.01,B3.02,B3.03 -154 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 0x7EF0BA B4.13 -155 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 0x7EF0BB B4.11 -156 D1.01 D DMA mode 0 Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0BC D1.01 -157 D1.01b D DMA mode 1 Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0BD D1.01 -158 D1.06 D DMA count hits zero Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0BE D1.06 -159 D1.07 D DMA fixed A-bus Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0BF D1.07 -160 D1.07b D DMA decrementing A-bus Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0C0 D1.07 -161 D1.10 D DMA $43xB scratch latch Corroborated ares and bsnes both model the latch and serialize it Scored 0x7EF0C1 D1.10 -162 D1.02 D DMA 8 clocks/byte Documented SNESdev Wiki, DMA timing; fullsnes Scored 0x7EF0C2 D1.02 -163 D1.05 D DMA count 0 = 65536 Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0C3 D1.05 -164 D1.09 D WRAM->$2180 no-write Documented fullsnes: "does not cause a write to occur" Scored 0x7EF0C4 D1.09,D1.15 -165 D2.03 D HDMA line-count byte Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0C5 D2.03 -166 D2.04 D HDMA repeat flag Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0C6 D2.04 -167 D2.07 D HDMA preempts GP-DMA Documented SNESdev Wiki, HDMA; anomie's timing doc; fullsnes Scored 0x7EF0C7 D2.07 -168 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 0x7EF0C8 D2.09 -169 D1.14 D $2180 B->A does write Documented fullsnes: $2180->WRAM writes, but the value written is invalid Scored 0x7EF0C9 D1.14 -170 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 0x7EF0CA D1.13 -171 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 0x7EF0CB D1.11 -172 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 0x7EF0CC D1.08 -173 D1.03 D DMA startup overhead Documented SNESdev Wiki, DMA timing; fullsnes Golden 0x7EF0CD D1.03 -174 D1.04 D DMA channel priority Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0CE D1.04 -175 D2.05 D HDMA indirect mode Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0CF D2.05 -176 D2.06 D HDMA $4308/$430A state Documented SNESdev Wiki, HDMA registers; fullsnes Scored 0x7EF0D0 D2.06 -177 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 0x7EF0D1 E4.11 -178 E1.01 E MUL YA flags from Y Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D2 E1.01 -179 E1.02 E DIV YA,X normal branch Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D3 E1.02 -180 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 0x7EF0D4 E1.07 -181 E1.04 E DIV H = nibble compare Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D5 E1.04 -182 E1.05 E DIV V is quotient bit 8 Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D6 E1.05 -183 E1.06 E DIV flags from quotient Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D7 E1.06 -184 E1.13 E ADDW H = bit-11 carry Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D8 E1.13 -185 E1.15 E MOVW YA sets 16-bit N/Z Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D9 E1.15 -186 E3.01 E Timer read clears it Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0DA E3.01 -187 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 0x7EF0DB E3.02 -188 E3.11 E $F2 bit 7 blocks writes Documented SNESdev Wiki, S-DSP; fullsnes Scored 0x7EF0DC E3.11 -189 E3.11b E DSP register addressing Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0DD - -190 E2.01 E Store dummy-reads target Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0DE E2.01 -191 E2.05 E DP index wraps in page Documented SNESdev Wiki, SPC700 addressing; fullsnes Scored 0x7EF0DF E2.05 -192 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 0x7EF0E0 E6.09 -193 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 0x7EF0E1 E6.11 -194 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 0x7EF0E2 E10.01 -195 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 0x7EF0E3 E10.05 -196 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 0x7EF0E4 E1.14 -197 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 0x7EF0E5 E2.04 -198 E3.14 E $F8/$F9 are plain RAM Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0E6 E3.14 -199 E3.11c E DSP global registers Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0E7 - -200 E9.19 E ENDX write clears it Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0E8 E9.19 -201 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 0x7EF0E9 E9.03 -202 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 0x7EF0EA E9.01 -203 E5.07 E End+mute zeroes env Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0EB E5.07 -204 E5.08 E Loop flag without end Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0EC E5.08 -205 E5.09 E ENDX sets on end block Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0ED E5.09 -206 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 0x7EF0EE E5.01 -207 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 0x7EF0EF E5.10 -208 E5.11 E Directory entry address Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0F0 E5.11 -209 E7.10 E Direct GAIN is envelope Documented SNESdev Wiki, S-DSP envelopes; fullsnes Scored 0x7EF0F1 E7.10 -210 E1.03 E DIV overflow branch Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0F2 E1.03 -211 E1.08 E DAA adjustments Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F3 E1.08 -212 E2.08 E TCALL vector table Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F4 E2.08 -213 E2.09 E BRK shares TCALL 0 Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F5 E2.09 -214 E3.03 E $F1 bit 5 clears port 3 Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0F6 E3.03 -215 E3.04 E Writes pass under IPL Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0F7 E3.04 -216 E3.05 E TnDIV $00 means 256 Documented SNESdev Wiki, SPC700 timers; fullsnes Scored 0x7EF0F8 E3.05 -217 E3.10 E TEST gates RAM writes Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0F9 E3.10 -218 E1.09 E DAS mirrors DAA Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0FA E1.09 -219 E1.10 E TSET1 is a compare Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0FB E1.10 -220 E1.12 E CLRV clears H too Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0FC E1.12 -221 E2.02 E MOV dp,dp is exempt Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0FD E2.02 -222 E2.03 E MOVW reads the low byte Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0FE E2.03 -223 E2.06 E PSW.P selects the page Documented SNESdev Wiki, SPC700 addressing; fullsnes Scored 0x7EF0FF E2.06 -224 E2.07 E CALL pushes exact addr Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF100 E2.07 -225 E4.01 E IPL ROM contents Documented the canonical 64-byte IPL listing; fullsnes, SNESdev Wiki Scored 0x7EF101 E4.01 -226 E4.02 E IPL handoff state Documented fullsnes, SNESdev Wiki, APU boot handshake Scored 0x7EF102 E4.02 -227 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 0x7EF103 E4.03 -228 E4.04 E IPL ready announcement Documented fullsnes, SNESdev Wiki, APU boot handshake Scored 0x7EF104 E4.04 -229 E4.06 E IPL multi-block continue Documented fullsnes, SNESdev Wiki; canonical IPL boot ROM $FFEF-$FFFB Scored 0x7EF105 E4.06 -230 E4.08 E IPL DSP-poke via $00F2 Documented fullsnes (APU / S-DSP); canonical IPL boot ROM store loop Scored 0x7EF106 E4.08 -231 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 0x7EF107 E5.12 -232 E5.02 E BRR nibbles are signed Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF108 E5.02 -233 E7.16 E OUTX is pre-volume Documented fullsnes, S-DSP envelopes; anomie's DSP doc Scored 0x7EF109 E7.16 -234 E8.04 E KOFF outranks KON Documented fullsnes, S-DSP key on/off; anomie's DSP doc Scored 0x7EF10A E8.04 -235 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 0x7EF10B E8.05 -236 E9.04 E Noise voices decode BRR Documented fullsnes, S-DSP noise; anomie's DSP doc — flagged as errata Scored 0x7EF10C E9.04 -237 E9.06 E EDL 0 is a 4-byte buffer Documented fullsnes, S-DSP echo — flagged as errata; anomie's DSP doc Scored 0x7EF10D E9.06 -238 E9.12 E Echo writes are masked Documented fullsnes, S-DSP echo; anomie's DSP doc Scored 0x7EF10E E9.12 -239 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 0x7EF10F E9.15 -240 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 0x7EF110 E9.05 -241 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 0x7EF111 E9.13 -242 E9.10 E FLG.5 stops echo writes Documented fullsnes, S-DSP echo; anomie's DSP doc Scored 0x7EF112 E9.10 -243 E9.17 E Mute is after OUTX Documented fullsnes, S-DSP FLG; anomie's DSP doc Scored 0x7EF113 E9.17 -244 E9.18 E FLG reset kills voices Documented SNESdev Wiki, S-DSP; fullsnes Scored 0x7EF114 E9.18 -245 E5.03 E BRR sample arithmetic Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF115 E5.03 -246 E5.04 E Invalid shift collapses Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF116 E5.04 -247 E5.05 E BRR filter 1 Documented fullsnes, S-DSP BRR filters; anomie's DSP doc Scored 0x7EF117 E5.05 -248 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 0x7EF118 E5.13 -249 E7.01 E Rate 0 never fires Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF119 E7.01 -250 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 0x7EF11A E7.13 -251 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 0x7EF11B E7.17 -252 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 0x7EF11C E7.18 -253 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 0x7EF11D E7.04 -254 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 0x7EF11E E7.09 -255 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 0x7EF11F E7.05 -256 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 0x7EF120 E7.06 -257 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 0x7EF121 E7.07 -258 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 0x7EF122 E7.03 -259 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 0x7EF123 E7.12 -260 E7.08 E Key-off releases to zero Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF124 E7.08 -261 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 0x7EF125 E8.02 -262 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 0x7EF126 E8.03 -263 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 0x7EF127 E8.07 -264 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 0x7EF128 E8.10 -265 E7.11 E GAIN linear increase Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF129 E7.11 -266 E7.14 E GAIN decrease clamps Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF12A E7.14 -267 E7.15 E ENVX is E >> 4 Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF12B E7.15 -268 E6.02 E Pitch $1000 is 1:1 Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12C E6.02 -269 E6.02b E Pitch $1000 does finish Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12D E6.02 -270 E6.02c E Pitch $2000 is +1 octave Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12E E6.02 -271 E6.02d E Pitch $2000 upper bound Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12F E6.02 -272 E3.06 E T2 is eight times T0 Documented SNESdev Wiki, SPC700 timers; fullsnes Scored 0x7EF130 E3.06 -273 E3.08 E TEST bit 0 halts timers Documented fullsnes, SPC700 TEST register; ares and bsnes smp/timing Scored 0x7EF131 E3.08 -274 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 0x7EF132 F1.01 -275 F1.02 F Pad reads 17+ are 1 Documented SNESdev Wiki, controller protocol; fullsnes Scored 0x7EF133 F1.02 -276 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 0x7EF134 F1.03 -277 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 0x7EF135 F1.04 -278 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 0x7EF136 F1.07 -279 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 0x7EF137 F1.05 -280 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 0x7EF138 F1.06 -281 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 0x7EF139 F1.08 -282 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 0x7EF13A F1.09 -283 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 0x7EF13B F1.10 -284 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 0x7EF13C F1.11 -285 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 0x7EF13D F1.12 -286 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 0x7EF13E F1.14 -287 G1.02 G Reset: $4210/$4211 clear Documented SNESdev Wiki, power-on state; fullsnes Scored 0x7EF13F G1.02 -288 G1.04 G Reset: emulation mode Documented SNESdev Wiki, power-on state; WDC 65C816 datasheet, XCE Scored 0x7EF140 G1.04 -289 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 0x7EF141 G1.05 -290 G1.08 G Write-only read: openbus Documented SNESdev Wiki, open bus; fullsnes, memory map notes Scored 0x7EF142 G1.08 -291 G1.10 G Checksum XOR complement Documented SNESdev Wiki, cartridge header; fullsnes Scored 0x7EF143 G1.10 -292 G1.11 G Checksum over the image Documented SNESdev Wiki, cartridge header checksum; fullsnes Scored 0x7EF144 G1.11 -293 G1.12 G LoROM header location Documented SNESdev Wiki, cartridge header; fullsnes Scored 0x7EF145 G1.12 -294 G1.14 G LoROM bank decode Documented SNESdev Wiki, memory map; fullsnes Scored 0x7EF146 G1.14 -295 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 0x7EF147 G1.01 -296 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 0x7EF148 G1.03 -297 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 0x7EF149 G1.07 -298 A5.S01 A Sweep: CLC Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14A A5.01-08 -299 A5.S02 A Sweep: SEC Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14B A5.01-08 -300 A5.S03 A Sweep: CLV Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14C A5.01-08 -301 A5.S04 A Sweep: INX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14D A5.01-08 -302 A5.S05 A Sweep: DEX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14E A5.01-08 -303 A5.S06 A Sweep: TAX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14F A5.01-08 -304 A5.S07 A Sweep: TXY Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF150 A5.01-08 -305 A5.S08 A Sweep: ASL A Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF151 A5.01-08 -306 A5.S09 A Sweep: XBA Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF152 A5.01-08 -307 A5.S10 A Sweep: TCD Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF153 A5.01-08 -308 A5.S11 A Sweep: LDA #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF154 A5.01-08 -309 A5.S12 A Sweep: LDX #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF155 A5.01-08 -310 A5.S13 A Sweep: CMP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF156 A5.01-08 -311 A5.S14 A Sweep: BIT #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF157 A5.01-08 -312 A5.S15 A Sweep: REP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF158 A5.01-08 -313 A5.S16 A Sweep: SEP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF159 A5.01-08 -314 A5.S17 A Sweep: WDM Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15A A5.01-08 -315 A5.S18 A Sweep: PHA+PLA Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15B A5.01-08 -316 A5.S19 A Sweep: PHP+PLP Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15C A5.01-08 -317 A5.S20 A Sweep: PHB+PLB Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15D A5.01-08 -318 A5.S21 A Sweep: PHD+PLD Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15E A5.01-08 -319 A5.S22 A Sweep: PHX+PLX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15F A5.01-08 -320 A5.S23 A Sweep: LDA dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF160 A5.01-08 -321 A5.S24 A Sweep: LDA abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF161 A5.01-08 -322 A5.S25 A Sweep: LDA long Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF162 A5.01-08 -323 A5.S26 A Sweep: STA dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF163 A5.01-08 -324 A5.S27 A Sweep: STA abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF164 A5.01-08 -325 A5.S28 A Sweep: LDA dp,X Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF165 A5.01-08 -326 A5.S29 A Sweep: LDA abs,X Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF166 A5.01-08 -327 A5.S30 A Sweep: INC dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF167 A5.01-08 -328 A5.S31 A Sweep: INC abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF168 A5.01-08 -329 A5.S32 A Sweep: ADC dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF169 A5.01-08 -330 A5.S33 A Sweep: CMP abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16A A5.01-08 -331 A5.S34 A Sweep: BVS untaken Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16B A5.01-08 -332 A5.S35 A Sweep: BRL flat 4 Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16C A5.16 +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 diff --git a/tests/roms/AccuracySNES/asm/runtime.inc b/tests/roms/AccuracySNES/asm/runtime.inc index ec040e91..87ec4cda 100644 --- a/tests/roms/AccuracySNES/asm/runtime.inc +++ b/tests/roms/AccuracySNES/asm/runtime.inc @@ -314,11 +314,27 @@ V_COP_VEC_E = $0054 ; so this is unreachable until a test opts in; it is initialised to `irq_stub` at reset so a test ; that enables NMI without installing a handler is harmless rather than jumping into RAM. ; -; $0056 is the next free word: the low-RAM map is $0040 PAD_X/V_DISPATCH_TMP, $0042 V_BRK_VEC, +; $005B is the next free byte: the low-RAM map is $0040 PAD_X/V_DISPATCH_TMP, $0042 V_BRK_VEC, ; $0044 V_COP_VEC, $0046 V_H0, $0048 V_H1, $004A V_IRQ_VEC, $004C V_DISPATCH, $0050-$0052 -; V_APU_PTR (24-bit, owns three bytes), $0054 V_COP_VEC_E, $0080 PAD_A. +; V_APU_PTR (24-bit, owns three bytes), $0054 V_COP_VEC_E, $0056 V_NMI_VEC, +; $0058-$005A V_IRQ_VEC_FAR (24-bit, owns three bytes), $0080 PAD_A. V_NMI_VEC = $0056 +; A 24-bit IRQ handler pointer, for tests that do NOT live in bank $00. +; +; `irq_trampoline` is a bank-LOCAL `jmp (V_IRQ_VEC)`, so the pointer it follows can only name a +; handler in bank $00 — which means Groups C-G, whose bodies are relocated out of bank $00, could +; not install an IRQ handler at all. A test outside bank $00 therefore points `V_IRQ_VEC` at +; `irq_far_shim` (which IS in bank $00, so the existing trampoline reaches it) and puts its own +; 24-bit handler address here; the shim does `jml [V_IRQ_VEC_FAR]`. +; +; `jml` pushes nothing, so the interrupt's own return frame is untouched and the far handler still +; ends with a plain `rti`. Owns three bytes, $0058-$005A; $005B is the next free byte. Initialised +; at reset to `irq_stub` in bank $00, for the same reason every other vector here is: a test that +; enables the shim without filling this in returns harmlessly instead of jumping into whatever RAM +; happened to hold. +V_IRQ_VEC_FAR = $0058 + ; Bank-probe result and return vector, for the `(a,X)` indirect-jump rows (A4.04/A4.05). ; ; Those tests must tell "the pointer address wrapped inside the program bank" from "it carried into @@ -432,6 +448,7 @@ PAD_X = $0040 .ifndef RUNTIME_IMPL .import test_restore .import irq_stub + .import irq_far_shim .import hv_begin .import hv_end .import hv_begin_wide diff --git a/tests/roms/AccuracySNES/asm/runtime.s b/tests/roms/AccuracySNES/asm/runtime.s index bf972650..b93a6bdd 100644 --- a/tests/roms/AccuracySNES/asm/runtime.s +++ b/tests/roms/AccuracySNES/asm/runtime.s @@ -106,6 +106,8 @@ RUNTIME_IMPL = 1 ; suppress runtime.inc's imports of what we defi rep #$20 .a16 lda #irq_stub + sta a:V_IRQ_VEC_FAR ; the far vector's low word; its bank byte is set below, so a test + ; that enables the shim without filling this in returns harmlessly sta a:V_IRQ_VEC ; default: the IRQ trampoline behaves exactly like the old stub sta a:V_COP_VEC_E ; and the emulation COP pointer, so a COP in a test that has ; installed no handler returns instead of jumping into RAM @@ -114,6 +116,8 @@ RUNTIME_IMPL = 1 ; suppress runtime.inc's imports of what we defi ; enables NMI without installing a handler must not jump into RAM sep #$20 .a8 + lda #$00 + sta a:V_IRQ_VEC_FAR + 2 ; bank $00, completing the far vector's default lda #$00 ; clear the capture-complete marker BEFORE capturing (G1.05 asserts sta f:V_PO_READY ; it) — STZ has no long-addressing form, so LDA/STA @@ -3807,6 +3811,22 @@ test_restore := test_restore_impl rti .endproc +; The far-IRQ shim: how a test outside bank $00 installs an interrupt handler. +; +; `irq_trampoline` below is a bank-local `jmp (V_IRQ_VEC)`, so that pointer can only name a bank-$00 +; handler — which left Groups C-G, whose bodies are relocated out of bank $00, unable to install one +; at all. Such a test points `V_IRQ_VEC` here (this proc IS in bank $00, so the trampoline reaches +; it) and puts its own 24-bit handler address in `V_IRQ_VEC_FAR`. +; +; `jml` pushes nothing, so the interrupt's return frame is untouched and the far handler ends with a +; plain `rti`, exactly as a bank-$00 handler does. Nothing is clobbered on the way through: no +; register is touched and no flag is disturbed, so the handler sees the CPU state the interrupt +; produced. +.export irq_far_shim +.proc irq_far_shim + jml [V_IRQ_VEC_FAR] +.endproc + ; The IRQ trampoline, for tests that need to observe dispatch rather than the comparator flag. .export irq_trampoline .proc irq_trampoline diff --git a/tests/roms/AccuracySNES/asm/tests_group_a.s b/tests/roms/AccuracySNES/asm/tests_group_a.s index e7381920..679035bf 100644 --- a/tests/roms/AccuracySNES/asm/tests_group_a.s +++ b/tests/roms/AccuracySNES/asm/tests_group_a.s @@ -25424,6 +25424,226 @@ CATALOG_IMPL = 1 jml test_restore .endproc +; C7.05 — RangeOver dot = idx*2 +; provenance: 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) +.proc test_c7_05 + .a16 + .i16 + jmp @body +@handler: + sep #$20 + .a8 + .a8 + lda $213E ; sample STAT77 at the IRQ dot + sta f:$7E0150 + lda $4211 ; acknowledge so the line drops + rti +@body: + rep #$30 + .a16 + .i16 + phk + plb + ; Group C lives outside bank $00, and irq_trampoline is a bank-LOCAL jmp (V_IRQ_VEC) — + ; so the pointer it follows cannot name this handler. Route through the bank-$00 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: sprites 0..39 in range, so the 33rd is index 32 and the dot is ~65 --- + sep #$20 + .a8 + stz $2101 ; OBJSEL: 8x8/16x16 pair, name base 0 + stz $2102 + stz $2103 + rep #$10 + .i16 + ldx #$0000 +@fill_a: + lda #$00 + sta $2104 ; X = 0 + cpx #$0000 + bcc @off_a ; below the window: park it + cpx #$0028 + bcs @off_a ; at or above the window's end: park it + 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 all zero: every X bit 8 clear and the small size of the pair. + stz $2102 + lda #$01 + sta $2103 ; OAMADDR = word $100 + ldx #$0000 +@hi_a: + stz $2104 + inx + cpx #$0020 + bne @hi_a + sep #$20 + .a8 + lda #$FF + sta f:$7E0150 ; poison: a handler that never runs cannot look like either verdict + lda #78 + sta $4207 + stz $4208 ; HTIME = 78 + lda #100 + sta $4209 + stz $420A ; VTIME = 100 — the line the sprites are on + 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: fire at H = 78 of V = 100 + 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:$7E0150 + and #$40 + cmp #$40 + beq :+ + jmp @fail1 + : + ; --- phase B: sprites 40..79, so the 33rd is index 72 and the dot moves to ~145 --- + sep #$20 + .a8 + stz $2101 ; OBJSEL: 8x8/16x16 pair, name base 0 + stz $2102 + stz $2103 + rep #$10 + .i16 + ldx #$0000 +@fill_b: + lda #$00 + sta $2104 ; X = 0 + cpx #$0028 + bcc @off_b ; below the window: park it + cpx #$0050 + bcs @off_b ; at or above the window's end: park it + 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 all zero: every X bit 8 clear and the small size of the pair. + stz $2102 + lda #$01 + sta $2103 ; OAMADDR = word $100 + ldx #$0000 +@hi_b: + stz $2104 + inx + cpx #$0020 + bne @hi_b + sep #$20 + .a8 + lda #$FF + sta f:$7E0150 ; poison: a handler that never runs cannot look like either verdict + lda #78 + sta $4207 + stz $4208 ; HTIME = 78 + lda #100 + sta $4209 + stz $420A ; VTIME = 100 — the line the sprites are on + 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: fire at H = 78 of V = 100 + 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 + sep #$20 + .a8 + lda f:$7E0150 + and #$40 + cmp #$00 + beq :+ + jmp @fail2 + : + ; ...and it must still set later in the SAME frame, or 'clear' meant 'never sets'. + rep #$30 + .a16 + .i16 + jsl wait_vblank_far + sep #$20 + .a8 + lda $213E + and #$40 + cmp #$40 + beq :+ + jmp @fail3 + : + 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: + ; Range Over had not set by dot ~105 with the 33rd in-range sprite at OAM index 32 (dot 65) — the flag is later than OAM.INDEX * 2 + sep #$20 + .a8 + lda #$02 + sta f:$7EE010 + jml test_restore +@fail2: + ; 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 + sep #$20 + .a8 + lda #$04 + sta f:$7EE010 + jml test_restore +@fail3: + ; Range Over never set at all in phase B, so its clear reading at dot ~105 proved nothing + sep #$20 + .a8 + lda #$06 + sta f:$7EE010 + jml test_restore +.endproc + ; C7.02 — Time Over is slivers ; provenance: Documented (SNESdev Wiki, Sprites; fullsnes; anomie) .proc test_c7_02 @@ -33010,7 +33230,7 @@ apu_prog_112: .export _test_flags _test_count: - .word 333 + .word 334 ; Entry points, 24-bit: test bodies no longer all live in bank $00. _test_entries: @@ -33130,6 +33350,7 @@ _test_entries: .faraddr test_c11_08 .faraddr test_c7_01 .faraddr test_c7_04 + .faraddr test_c7_05 .faraddr test_c7_02 .faraddr test_c7_08 .faraddr test_c7_10 @@ -33466,6 +33687,7 @@ _test_flags: .byte $02 ; C11.08 .byte $01 ; C7.01 .byte $01 ; C7.04 + .byte $01 ; C7.05 .byte $01 ; C7.02 .byte $01 ; C7.08 .byte $01 ; C7.10 @@ -33802,6 +34024,7 @@ _test_names: .addr @n_c11_08 .addr @n_c7_01 .addr @n_c7_04 + .addr @n_c7_05 .addr @n_c7_02 .addr @n_c7_08 .addr @n_c7_10 @@ -34367,6 +34590,9 @@ _test_names: @n_c7_04: .byte 24 .byte "Offscreen X takes a slot" +@n_c7_05: + .byte 21 + .byte "RangeOver dot = idx*2" @n_c7_02: .byte 20 .byte "Time Over is slivers" @@ -35251,7 +35477,7 @@ _page_len: .byte 7 .byte 4 .byte 9 - .byte 6 + .byte 7 .byte 10 .byte 10 .byte 1 @@ -35305,41 +35531,41 @@ _page_off: .word 111 .word 115 .word 124 - .word 130 - .word 140 - .word 150 + .word 131 + .word 141 .word 151 - .word 154 - .word 157 - .word 161 - .word 163 - .word 167 - .word 172 - .word 182 - .word 185 - .word 190 + .word 152 + .word 155 + .word 158 + .word 162 + .word 164 + .word 168 + .word 173 + .word 183 + .word 186 .word 191 - .word 201 - .word 206 - .word 212 - .word 219 - .word 229 - .word 233 - .word 243 - .word 245 - .word 254 - .word 260 - .word 262 - .word 272 - .word 274 - .word 284 - .word 286 - .word 296 - .word 303 - .word 309 - .word 319 - .word 322 - .word 332 + .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 _page_tests: .word 0 @@ -35392,7 +35618,6 @@ _page_tests: .word 69 .word 70 .word 71 - .word 298 .word 299 .word 300 .word 301 @@ -35427,6 +35652,7 @@ _page_tests: .word 330 .word 331 .word 332 + .word 333 .word 30 .word 31 .word 32 @@ -35464,14 +35690,15 @@ _page_tests: .word 84 .word 85 .word 86 - .word 120 - .word 122 + .word 121 + .word 123 .word 87 .word 114 .word 115 .word 116 .word 117 .word 118 + .word 119 .word 88 .word 89 .word 90 @@ -35479,9 +35706,9 @@ _page_tests: .word 92 .word 93 .word 94 - .word 119 - .word 121 - .word 125 + .word 120 + .word 122 + .word 126 .word 95 .word 96 .word 97 @@ -35491,8 +35718,8 @@ _page_tests: .word 101 .word 102 .word 103 - .word 126 .word 127 + .word 128 .word 104 .word 105 .word 106 @@ -35503,37 +35730,36 @@ _page_tests: .word 111 .word 112 .word 113 - .word 123 .word 124 - .word 128 + .word 125 .word 129 - .word 145 - .word 146 .word 130 - .word 132 + .word 146 .word 147 - .word 148 - .word 150 .word 131 .word 133 + .word 148 + .word 149 + .word 151 + .word 132 .word 134 .word 135 .word 136 .word 137 .word 138 .word 139 - .word 149 - .word 151 + .word 140 + .word 150 .word 152 - .word 154 + .word 153 .word 155 - .word 140 + .word 156 .word 141 .word 142 .word 143 .word 144 - .word 153 - .word 156 + .word 145 + .word 154 .word 157 .word 158 .word 159 @@ -35542,26 +35768,26 @@ _page_tests: .word 162 .word 163 .word 164 - .word 169 + .word 165 .word 170 .word 171 .word 172 .word 173 .word 174 - .word 165 + .word 175 .word 166 .word 167 .word 168 - .word 175 + .word 169 .word 176 .word 177 - .word 225 + .word 178 .word 226 .word 227 .word 228 .word 229 .word 230 - .word 178 + .word 231 .word 179 .word 180 .word 181 @@ -35569,45 +35795,45 @@ _page_tests: .word 183 .word 184 .word 185 - .word 196 - .word 210 + .word 186 + .word 197 .word 211 - .word 218 + .word 212 .word 219 .word 220 - .word 186 + .word 221 .word 187 .word 188 .word 189 - .word 198 + .word 190 .word 199 - .word 214 + .word 200 .word 215 .word 216 .word 217 - .word 272 + .word 218 .word 273 - .word 190 + .word 274 .word 191 - .word 197 - .word 212 + .word 192 + .word 198 .word 213 - .word 221 + .word 214 .word 222 .word 223 .word 224 - .word 192 + .word 225 .word 193 - .word 268 + .word 194 .word 269 .word 270 .word 271 - .word 194 + .word 272 .word 195 - .word 200 + .word 196 .word 201 .word 202 - .word 236 + .word 203 .word 237 .word 238 .word 239 @@ -35616,21 +35842,21 @@ _page_tests: .word 242 .word 243 .word 244 - .word 203 + .word 245 .word 204 .word 205 .word 206 .word 207 .word 208 - .word 231 + .word 209 .word 232 - .word 245 + .word 233 .word 246 .word 247 .word 248 - .word 209 - .word 233 .word 249 + .word 210 + .word 234 .word 250 .word 251 .word 252 @@ -35642,16 +35868,16 @@ _page_tests: .word 258 .word 259 .word 260 - .word 265 + .word 261 .word 266 .word 267 - .word 234 + .word 268 .word 235 - .word 261 + .word 236 .word 262 .word 263 .word 264 - .word 274 + .word 265 .word 275 .word 276 .word 277 @@ -35675,3 +35901,4 @@ _page_tests: .word 295 .word 296 .word 297 + .word 298 diff --git a/tests/roms/AccuracySNES/build/accuracysnes-exhirom.sfc b/tests/roms/AccuracySNES/build/accuracysnes-exhirom.sfc index 259e77f39a345bbbdefb25368d7691b4d97d2296..3d8c9d0efe216d251dee8d1fe00ef6c44cb3154c 100644 GIT binary patch delta 2343 zcmZ{kUrbY19LLXX?+_SDRYb%IM?`8-w16Ta;9+wFv`#aPaZC-Exj{PT_TZw%m5V@I zpzQ@pd*U8u2g?$0OUa`0v@rSxZbG&tn&_4a*=AU>G>gNzJ#^o5TP7^7$*1Rce)s(O zo!>u2Y11iSxIi|B?PQt`>QqD6c04A_3*>}(xwS!dG{_Rr0X-1&=Rg8j0un(INCwY? z6tEO51F66OmV*^wB}fCSKss0rGQb*;3D$xaz&elxvH=C_K@Qjea=}KB2aI47FoArq z8EgRsU@O=L%%Bhyfnu;7>;NTTCnyEGKpEH#%0UIF9BGjET>abouDtX*OH?EaPg2&`Pt~ zC~Ko{w9$b!dZC?`az$aeo2wmkMGJkc1xKlFvOZ*{{`RNx!aV^#sj$4XgPkFBP!8MF z1YCK-h254Kp47tflP3H`O^Dcn6TG~IZFte!Nwu|!VOs|=OjQOoK^M`}1T`j0ki(wf zim=TTC6U|WH7X{9O3aYzk&j!Lte1vIK5b)3k~G1G+u61wxTafg`QENANB+Q^6qs^Z zjVy&KDpDt8wv^j%T)0Plf1>wYNcs@%g%D7a{im09nKzZ>nCzR z(Non)Pdey;gMQ|$hh@63EqQ44CtmMhJLFjheax>o*v6b$M?mq@T+AI}NhZy92JG{S z89$-fJnUdcB`e?QWLYUzXK-2AmeFJ;)XE#2Yy&>DJJ}|~W4OEouf!R`;yM1MldUr+ zbOfQyIJ;fD{0Y{i!9hzpf>30t__N&6jDuEtCr|ERriMOoKRlSI76}h3rve|vH6*4P zj7C+Mq0|OCpZ&ocxDx-6vUtbpfQk>iuY(nsc6D}UG)ZKL3U4^XyT+r@$V#f|6EwY0 z(u;hygXLvU!TRGw%XIZ5LC9hY6XOT)ZJq2r83~@nKj~!NW^dFs8CMn-_S4?(L>y-L#{7-pYlZ8HjtPKTUJgwRuOCY1z7{ zAJ92B+QZ^Qs+I`%4$tpOj@V)y#2EKC6d7kzG%YqR^m~@@`#JtiH!EyLqY;xH;eEV$ zzL{XAH4{GxnGt8T6tQ`~M;w9xh)7IAgN;o+u{HyZ@E8hJ)a{Qw{|3ua*7G!46N~+T zJNXAatUAlyJ6I)ay=q9SdwVY|7E-J=J?LRvw)fJX`K4aASGvW=dRc{ZgGYN=A%;RP zE5>lz%Sv=Nygb#%R_o|j{HB*x=!`zUmzgCa&+Ee?Bd_aYr5L<@>?I6$`VfC3U*=LR=WCi?J$Qgt6%`j$RuvUV0qem2p_hofvuZspCfT7utc-znc# SU#*XjB7l?dFXjg2lf z{^vX2blshygR|4r^8O|^zy3CV?_g4k|z; zm<*?pgb-MSSgcJ)sld7mFR$PXXng>GK! z<{RDoh@0;@#2vz`)5Ixt9-iCCS2bc5!^g(Q9Nd5Cv7J8Vu=r7@vVvtTF-Js=Yep89 zT-%1xP7=c#X>v|9#Eh(j7Kw@_jWnUWwS}9p*>SCz*{;?_Od}(a$shSswgS6f_#3H6 zsXO1(NNT2X;`|vmt_xBSVV#Xm9wvK}Uax@zLUzht?@u#X>I? z=WX>yo{wwx9S+8~iXC2>fDb+|RcyKod24V=%prGtCxkOqCYezi*KjnW8lS)wbRpRz zZv?US-SA*f_b{{ox+T3bq)5g(ub6v=*F3%$(M*+v&2n21HruF@!s=VX0eHl!W*Vh7 zSk}lAN7!%0<)z|!l1T%5)<=ikGU&r*npQcprNzEOVZB@m;gLt`OC}Q|x#`a`{nE+z zia(mEtmG@~a3f3Q@*4~%O9f_I6Nou2v`0lA7m8CY6liE|c^GqCzWTt$2eWKeT!R=_ zGo5d?_9?RczSTLTEtO~4$g^!$&Z)2uTlQ1AnDXl^kr!d94QKN4Mrsm0&Dp?DSzcQr z)49i5hGXi3ZO$oHSI%CYmgzZ`jUoIeUusgGn4CUDZuB#2xZLP9%XYv%-WC}#c*d8h z)qD|f^ysNqyS5FUeA9MPE3(lNMCElJmsCXM6Wd*Uq|dbF)UT}A^wKl-)DiMm|e^pRcZ7Y}`S4EIE_ zpC+SO;is|69kI<%mC9;i`0+khi;w+OgUH){Dnny4s5B4H#=aZ3DW8F;GpKURvnzvc z(1Iri^cl3hA?@sZemnoPoj0~alc#hzZ0XQlV9F96lx41YZ%;gYe$R(G@Z=$+)@_~`|dpH714GYrR);d+i7M4N(eOG)gi^jAqUHZ^|I5;1b@3j-VOAX zLR*z$b4RLDz++Vk`7D3v`2Q}PRUMBnBlhIMv8FiGK}(CZ&Ynf8S#EmGXRUSOY|=_q zm)jjARIQW$BHW#{B;#VIhz4kW#_53gH$V>A&gh~l<+RuppqjC#1NwclUK*5#07XezJl!$)g zJ9%knC$9l6u`EdYl>OrKAWcH^TaaEIcpx~zI8JEy&ha2EDP2J87l{jZNjhY+w}Fp{{TKap@9GZ diff --git a/tests/roms/AccuracySNES/build/accuracysnes-hirom.sfc b/tests/roms/AccuracySNES/build/accuracysnes-hirom.sfc index f5e87b3336c0710e1bca7faffd7f89a43ed5c27f..dc294c4bd85ee08c2027f7f580c22957c15dbf61 100644 GIT binary patch delta 2056 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

{1`%67;iavNiOR~8v&1^k~ZvV@;u zo-tauk;DH`0+X+_YSI%W8D|AFIh%~K(tI?Vj4(6UE(gy&PcZ{?NLRC#sW~{!oMO)B zkhl@2^06oO4)?to2V-;pH8?ABNwOKD15JjTRZaVMRXI&2m{C2Z z$xBwuUXn)&tTcP(ld;3}VbW$#vC}VFKf*6auEdLE{(5I{U-3Gb|0#yaC*fA)|0myS zphOmscq`lm1tda05`?Q}tVaIz%y$K(3pvmHUO@cJ9MX#+;bv8f7&6SPY7axA`H}Ps zL)O_C_d5!$j$CmOnPJ78wMC?~9m68Fn0Q8F9|?Wo>g3pZMUk(?_@wY@?bfGj4EK&R ziefUO9iW~iWCeefu+6{_0^E2Ab7lUyJmzExNweaOS4v3?-vgDUWIj*XY5)T8z)VOv z8OLN+5NBpp8FAuyQp(61d~u$Ncrni9#MufO%9tx~dNFn7B-xm~CyLk4Wd>Nzmevcj zNiX3KGhh}_PBwW?ETIB_-49h23?v#q~I=n*crv||4`;8Pd{0}9yun3xo3j4THhd9k5g=1W)6MH*8cadKs z!OQ(KHK8~o)Q7UUyoq~`0GqYQ6%?zB#SC>+g);~7D`gIq6L%S^F!}$F3iy1vUI{i$ zo>S=+oT$&8dd(VC+>aKP_V+_WTO%0D&kQkrlxt#{edWZ1nO{L(9M4UI`G9?rV34{j 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

uru(YIi@M3;w2F> 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 Vec { // --- C7: sprite evaluation flags (the only Group C tests that render) --- c7_01(), c7_04(), + c7_05(), c7_02(), c7_08(), c7_10(), @@ -2326,6 +2327,185 @@ fn setup_and_render( a.l("pla"); } +/// Range Over is set at the evaluation cycle of the **33rd in-range sprite** — `H = OAM.INDEX * 2` +/// on `V = OBJ.YLOC` (`C7.05`). +/// +/// # A fixed bracket would be vacuous, so this moves the index instead +/// +/// The obvious test — sample `$213E` early on the line expecting clear, late expecting set — proves +/// almost nothing. An H-IRQ is serviced ~22-27 dots after its `HTIME` (`scripts/probes/` +/// `eval-line-213e/README.md` records the same latency biting its V-counter latch), so a bracket +/// tight enough to pin dot 65 is inside the latency's own uncertainty, and a bracket loose enough to +/// be safe passes for *any* set dot across most of the line. That is the `F1.09` shape: a sampler +/// too coarse for its subject agrees with whatever the subject does. +/// +/// What actually pins `INDEX * 2` is that the set dot must **move with the index**. So both phases +/// sample at the *same* dot and change only which OAM entry is the 33rd in-range sprite: +/// +/// | phase | in-range sprites | 33rd is index | set dot `2i + 1` | read at ~dot 105 | +/// |---|---|---:|---:|---| +/// | A | `0..39` | 32 | 65 | **set** — the flag has already tripped | +/// | B | `40..79` | 72 | 145 | **clear** — it has not tripped yet | +/// +/// A core that sets Range Over at a fixed dot, or at line start, or on the count alone, gives the +/// same answer in both phases and fails. The ~40 dots of margin on each side of the sample also +/// absorbs the IRQ latency completely, so the test does not depend on knowing it. +/// +/// # Phase B needs the "and it does set, later" guard +/// +/// "Clear at dot 105" is also what a core that never sets the flag at all would report, which would +/// make phase B pass for the wrong reason — and phase A cannot cover for it, since the two use +/// different sprite layouts. So phase B additionally reads `$213E` in the vblank of the **same +/// frame** and requires it set by then. The flags clear at the start of a frame, not on read +/// (`Ppu::advance`), so that later read sees the same frame's result. +fn c7_05() -> Test { + let mut a = Asm::new(); + a.l("jmp @body"); + a.label("handler"); + a.l("sep #$20"); + a.l(".a8"); + a.l("lda $213E ; sample STAT77 at the IRQ dot"); + a.l("sta f:$7E0150"); + a.l("lda $4211 ; acknowledge so the line drops"); + a.l("rti"); + a.label("body"); + a.l("rep #$30"); + a.l("phk"); + a.l("plb"); + a.c("Group C lives outside bank $00, and irq_trampoline is a bank-LOCAL jmp (V_IRQ_VEC) —"); + a.c("so the pointer it follows cannot name this handler. Route through the bank-$00 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: sprites 0..39 in range, so the 33rd is index 32 and the dot is ~65 ---"); + setup_over_flag_sprites(&mut a, "a", 0); + arm_hv_irq_and_run(&mut a, "a"); + a.l("lda f:$7E0150"); + a.l("and #$40"); + a.assert_a8( + 0x40, + "Range Over had not set by dot ~105 with the 33rd in-range sprite at OAM index 32 (dot 65) \ + — the flag is later than OAM.INDEX * 2", + ); + + a.c("--- phase B: sprites 40..79, so the 33rd is index 72 and the dot moves to ~145 ---"); + setup_over_flag_sprites(&mut a, "b", 40); + arm_hv_irq_and_run(&mut a, "b"); + a.l("sep #$20"); + a.l("lda f:$7E0150"); + a.l("and #$40"); + a.assert_a8( + 0x00, + "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", + ); + a.c("...and it must still set later in the SAME frame, or 'clear' meant 'never sets'."); + a.l("rep #$30"); + a.l("jsl wait_vblank_far"); + a.l("sep #$20"); + a.l("lda $213E"); + a.l("and #$40"); + a.assert_a8( + 0x40, + "Range Over never set at all in phase B, so its clear reading at dot ~105 proved nothing", + ); + 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.05", + 'C', + "RangeOver dot = idx*2", + Provenance::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", + ), + Kind::Scored, + None, + ) +} + +/// 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. +fn setup_over_flag_sprites(a: &mut Asm, tag: &str, first: u16) { + a.l("sep #$20"); + a.l("stz $2101 ; OBJSEL: 8x8/16x16 pair, 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.l("lda #$00"); + a.l("sta $2104 ; X = 0"); + a.l(&format!("cpx #${first:04X}")); + a.l(&format!("bcc @off_{tag} ; below the window: park it")); + a.l(&format!("cpx #${:04X}", first + 40)); + a.l(&format!( + "bcs @off_{tag} ; at or above the window's end: park it" + )); + 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 all zero: every X bit 8 clear and the small size of the pair."); + 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("stz $2104"); + a.l("inx"); + a.l("cpx #$0020"); + a.l(&format!("bne @hi_{tag}")); +} + +/// Arm an HV-IRQ at `V = 100, H = 78` and run one frame with objects on the main screen. +/// +/// `HTIME = 78` puts the handler's `$213E` read near dot 105 once the ~22-27 dot IRQ-service +/// latency is added — ~40 dots clear of both candidate set dots (65 and 145), so the sample lands +/// unambiguously between them without the test having to know the latency. +fn arm_hv_irq_and_run(a: &mut Asm, tag: &str) { + a.l("sep #$20"); + a.l("lda #$FF"); + a.l("sta f:$7E0150 ; poison: a handler that never runs cannot look like either verdict"); + a.l("lda #78"); + a.l("sta $4207"); + a.l("stz $4208 ; HTIME = 78"); + a.l("lda #100"); + a.l("sta $4209"); + a.l("stz $420A ; VTIME = 100 — the line the sprites are on"); + 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: fire at H = 78 of V = 100"); + 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"); + let _ = tag; +} + /// A sprite at `X = $100` is entirely off-screen and still consumes its range slot. /// /// The X field is nine bits signed, so `$100` is `-256`: an 8x8 sprite there sits a full screen From 172854bc065e68829367650012453279fbc07f63 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Fri, 31 Jul 2026 16:04:42 -0400 Subject: [PATCH 2/2] fix(accuracysnes): preserve A in C7.05's handler, and measure the sample dot Two review findings, both real, and the first exposed the second. The handler read $213E and $4211 without saving A, while firing inside wait_vblank_far's own poll loop. It now brackets the body with php/pha and pla/plp. That fix broke the row -- which is how the second problem surfaced. The HTIME was chosen from a ~22-27 dot IRQ latency estimate borrowed from a different measurement, putting the sample at ~170: PAST phase B's set dot of 145. It had been passing only because the handler was three instructions shorter and landed just under it. Adding the three instructions pushed it over. Measured the sample dot properly, by temporarily latching $213C inside the handler: the real IRQ-to-sample latency is ~93 dots, because the interrupt is only taken at an instruction boundary in the poll loop and the trampoline, far shim and handler prologue all precede the read. HTIME 78 -> 12 puts the sample at 95-99, ~30 dots clear of both 65 and 145. All three injections re-verified at the corrected position and still fail their own codes (2, 1, 3). Also from review: arm_hv_irq_and_run's unused `tag` parameter is gone, and setup_over_flag_sprites no longer emits an unsatisfiable cpx #$0000 / bcc when the window starts at index 0. Declined: the claim that irq_far_shim's `jml [V_IRQ_VEC_FAR]` is direct-page indirect and depends on D. The 65816 has no JMP [dp] mode; the shim assembles to DC 58 00, absolute indirect long, which reads the pointer from bank $00 address $0058 with no D involvement. Confirmed by disassembling the built ROM. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 4 ++ docs/accuracysnes-plan.md | 16 ++++++-- tests/roms/AccuracySNES/asm/tests_group_a.s | 19 ++++++--- .../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 | 38 +++++++++++++----- 6 files changed, 57 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37157d90..127ad815 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 code 1, and setting correctly for a low index but never a high one fails code 3 — the guard phase A cannot cover. + The sample dot is **measured, not inferred**: an H-IRQ handler runs ~93 dots after its `HTIME`, not + the ~22-27 a raw trigger latency suggests, because the interrupt is only taken at an instruction + boundary and the trampoline, shim and prologue all precede the read. + This needed **new runtime machinery**, which no `v1.28.0` row was supposed to. `irq_trampoline` is a bank-local `jmp (V_IRQ_VEC)`, so only bank-`$00` groups could install an IRQ handler; Groups C-G, relocated out of bank `$00`, could not, and none ever had. New `irq_far_shim` plus the 24-bit diff --git a/docs/accuracysnes-plan.md b/docs/accuracysnes-plan.md index 0d0f4f6e..d8e71115 100644 --- a/docs/accuracysnes-plan.md +++ b/docs/accuracysnes-plan.md @@ -498,9 +498,12 @@ before designing this row's injection test. #### `C7.05` — a fixed bracket is vacuous; move the index instead -An H-IRQ is serviced ~22-27 dots after its `HTIME`, so a bracket tight enough to pin dot 65 sits -inside the latency's own uncertainty, while one loose enough to be safe passes for any set dot across -most of the line. That is the `F1.09` shape. +An H-IRQ handler does not run where its `HTIME` says. **Measured** — by temporarily latching +`$213C` inside the handler — the read lands ~**93 dots** later: the interrupt is only taken at an +instruction boundary in `wait_vblank_far`'s poll loop, and the trampoline, the far shim and the +handler prologue all precede it. So a bracket tight enough to pin dot 65 is inside the latency's own +uncertainty, and one loose enough to be safe passes for any set dot across most of the line — the +`F1.09` shape. What pins `H = OAM.INDEX * 2` is that the set dot must **move with the index**. Both phases sample the *same* dot and change only which OAM entry is the 33rd in-range sprite: @@ -514,6 +517,13 @@ Phase B additionally re-reads `$213E` in the vblank of the *same* frame and requ without that, "clear at dot 105" is also what a core that never sets the flag would report. The flags clear at frame start, not on read (`Ppu::advance`), so the later read sees the same frame. +**Measure the sample dot; do not infer it.** The first revision used `HTIME = 78` on a ~22-27 dot +estimate borrowed from a different measurement. That sampled at ~170 — *past* phase B's set dot of +145 — and still passed, because the handler was then three instructions shorter and landed just +under it. Adding the `php`/`pha` that preserves the interrupted context pushed it over and the row +failed. It had been passing by a hair, and only the failure exposed it. `HTIME = 12` now samples at +95-99, ~30 dots clear on each side. + Three injections, each failing its own code: a fixed set dot fails **code 2**, never setting the flag fails **code 1**, and setting correctly for a low index but never for a high one — which phase A cannot catch — fails **code 3**, the guard. diff --git a/tests/roms/AccuracySNES/asm/tests_group_a.s b/tests/roms/AccuracySNES/asm/tests_group_a.s index 679035bf..11c7d2c4 100644 --- a/tests/roms/AccuracySNES/asm/tests_group_a.s +++ b/tests/roms/AccuracySNES/asm/tests_group_a.s @@ -25431,12 +25431,19 @@ CATALOG_IMPL = 1 .i16 jmp @body @handler: + ; Save A and the width: this fires inside wait_vblank_far's $4212 poll, whose own A the + ; handler would otherwise clobber. `rti` restores P, but only after `plp` has put back + ; the width `pla` needs, so both are explicit. + php sep #$20 .a8 .a8 + pha lda $213E ; sample STAT77 at the IRQ dot sta f:$7E0150 lda $4211 ; acknowledge so the line drops + pla + plp rti @body: rep #$30 @@ -25470,8 +25477,6 @@ CATALOG_IMPL = 1 @fill_a: lda #$00 sta $2104 ; X = 0 - cpx #$0000 - bcc @off_a ; below the window: park it cpx #$0028 bcs @off_a ; at or above the window's end: park it lda #100 @@ -25499,9 +25504,9 @@ CATALOG_IMPL = 1 .a8 lda #$FF sta f:$7E0150 ; poison: a handler that never runs cannot look like either verdict - lda #78 + lda #12 sta $4207 - stz $4208 ; HTIME = 78 + stz $4208 ; HTIME = 12 lda #100 sta $4209 stz $420A ; VTIME = 100 — the line the sprites are on @@ -25522,6 +25527,8 @@ CATALOG_IMPL = 1 sei stz $4200 ; disarm before reading anything back lda $4211 + sep #$20 + .a8 lda f:$7E0150 and #$40 cmp #$40 @@ -25569,9 +25576,9 @@ CATALOG_IMPL = 1 .a8 lda #$FF sta f:$7E0150 ; poison: a handler that never runs cannot look like either verdict - lda #78 + lda #12 sta $4207 - stz $4208 ; HTIME = 78 + stz $4208 ; HTIME = 12 lda #100 sta $4209 stz $420A ; VTIME = 100 — the line the sprites are on diff --git a/tests/roms/AccuracySNES/build/accuracysnes-pal.sfc b/tests/roms/AccuracySNES/build/accuracysnes-pal.sfc index 8b235f2582d86506ee1baab354e0ab9a2f78d38e..3f0cd5dbd22d34ffe1234288df15c52394086ec0 100644 GIT binary patch delta 501 zcmY+AO=uHQ5Xax_3&CIt+XRDXlV&4mK0Jg{szofRCkr{WAX0A@RCUKHY9)LUr_w5Vhd zGS;$W|96n}PD0L$uV+4Ld*$5wEfQq;qC$m*eFvJ? zX%VS=SyERn&S9WIGhWx%d6n>LOUHvIl(4762282-+<6=z#Bj!dHxoyO9}{Hun)gLa zT(kJOX%kkHTc!c2c1r5cON@k5bQ32XDC1ib+UGW`y>iGEP6z`spw-6~Q^*VM@va4G zGHDy)fAqAi>Q{7)lQu0{vZ-m)IfvfgrWKbaHX9%e8$h4@`3uT2!f5~i delta 480 zcmY+9KWGzi6vuzx{H%BoZIVJkW2_gEc&S6Q7VT0?Hz7z7)UHYqL3DTM)=j$@@7gS#-h1Eo`8<9=%v&eS(&DedDlS#@OSr-h}YVc(6ASbJvE_a%&z zW#a*|W;LIsEJWIQLe&pzRq_QfKd^Dv)y<(bthIT9WUO3s(R zV=qTPJd7)vFaO4)7AAdc;MA}PmZ8fF+q|3=!Q-gSL~TI?PX_6PFss@$%?dot?+CdN z%R3^dLvOs_(&g9qZ9{E&2~j@O-u+~Fw2fak!hRpOk>9lVYnvAwKJ4-}mp^LpqQ@PN VPx^e}Ex#;ydbx%~SOZ`D_aF6xyJ`Rc diff --git a/tests/roms/AccuracySNES/build/accuracysnes.sfc b/tests/roms/AccuracySNES/build/accuracysnes.sfc index a777302c906b1753516b4c551c84d82a037e7783..702b96b21312e41b22d9f1360e085edf4cd94b7d 100644 GIT binary patch delta 501 zcmY+AO=uHQ5Xax_3&CIu+XRDXn`Tqce0T_@RExBvo)mJhAky9}Xz4+MXTi;eK&nC# zVJU+U$$ocN3>!pG)drOpw0WTawzhLGDf5*v#UcF3e*86^!RQ5sBNuP$7FiJA&jvlS&35 zBP~t#j)Jsz8gkwku4u3jEgjPG2Mr%}A&KWSxDF$IG4)y7EoMG$lOW9(;;V>Nw~R|&5+b=+@20lPX>U{bARE@B@chO-8|89z4sm>{#)yf0$n zy2a0(GGR5bZ5ohlC8gei#7H_bgBo z3EL3=qo-|EznaIQ4XXm$YVFn(?$~fSmU7tdkW1k=8w#akDu1+jLMII)pliTQ}`uylb;Kz4yNF^LhM!hLJLiRA;Mv;M-ko zz1l5@-7;>E9J$umKG~jD>vPcC=~KmnDpo zWn&Rpvy#tJhoI1egN?(#oj^(G@^!>OUr>tMJxthT3A$>LL1A9?G- zJa(_SS&U_^$0S&Xm+m*E!PcTmH=8hrGMtR0e+%qeeb0j Test { let mut a = Asm::new(); a.l("jmp @body"); a.label("handler"); + a.c("Save A and the width: this fires inside wait_vblank_far's $4212 poll, whose own A the"); + a.c("handler would otherwise clobber. `rti` restores P, but only after `plp` has put back"); + a.c("the width `pla` needs, so both are explicit."); + 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:$7E0150"); 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"); @@ -2386,7 +2393,8 @@ fn c7_05() -> Test { a.c("--- phase A: sprites 0..39 in range, so the 33rd is index 32 and the dot is ~65 ---"); setup_over_flag_sprites(&mut a, "a", 0); - arm_hv_irq_and_run(&mut a, "a"); + arm_hv_irq_and_run(&mut a); + a.l("sep #$20"); a.l("lda f:$7E0150"); a.l("and #$40"); a.assert_a8( @@ -2397,7 +2405,7 @@ fn c7_05() -> Test { a.c("--- phase B: sprites 40..79, so the 33rd is index 72 and the dot moves to ~145 ---"); setup_over_flag_sprites(&mut a, "b", 40); - arm_hv_irq_and_run(&mut a, "b"); + arm_hv_irq_and_run(&mut a); a.l("sep #$20"); a.l("lda f:$7E0150"); a.l("and #$40"); @@ -2445,8 +2453,10 @@ fn setup_over_flag_sprites(a: &mut Asm, tag: &str, first: u16) { a.label(&format!("fill_{tag}")); a.l("lda #$00"); a.l("sta $2104 ; X = 0"); - a.l(&format!("cpx #${first:04X}")); - a.l(&format!("bcc @off_{tag} ; below the window: park it")); + if first > 0 { + a.l(&format!("cpx #${first:04X}")); + a.l(&format!("bcc @off_{tag} ; below the window: park it")); + } a.l(&format!("cpx #${:04X}", first + 40)); a.l(&format!( "bcs @off_{tag} ; at or above the window's end: park it" @@ -2476,16 +2486,23 @@ fn setup_over_flag_sprites(a: &mut Asm, tag: &str, first: u16) { /// Arm an HV-IRQ at `V = 100, H = 78` and run one frame with objects on the main screen. /// -/// `HTIME = 78` puts the handler's `$213E` read near dot 105 once the ~22-27 dot IRQ-service -/// latency is added — ~40 dots clear of both candidate set dots (65 and 145), so the sample lands -/// unambiguously between them without the test having to know the latency. -fn arm_hv_irq_and_run(a: &mut Asm, tag: &str) { +/// `HTIME = 12` puts the handler's `$213E` read at dot **95-99**, which was **measured, not +/// assumed** — by temporarily latching `$213C` inside the handler and recording it. The +/// IRQ-to-sample latency is ~**93 dots**, not the ~22-27 an H-IRQ's raw trigger latency suggests: +/// the interrupt can only be taken at an instruction boundary in `wait_vblank_far`'s poll loop, and +/// the trampoline, the far shim and the handler prologue all sit in front of the read. +/// +/// Getting this wrong is not academic. An earlier revision used `HTIME = 78`, sampling at ~170 — +/// *past* phase B's set dot of 145 — and it still passed, because at that point the handler was +/// three instructions shorter and landed just under 145. Adding the `php`/`pha` that preserves the +/// interrupted context pushed it over and the row failed. It had been passing by a hair. +fn arm_hv_irq_and_run(a: &mut Asm) { a.l("sep #$20"); a.l("lda #$FF"); a.l("sta f:$7E0150 ; poison: a handler that never runs cannot look like either verdict"); - a.l("lda #78"); + a.l("lda #12"); a.l("sta $4207"); - a.l("stz $4208 ; HTIME = 78"); + a.l("stz $4208 ; HTIME = 12"); a.l("lda #100"); a.l("sta $4209"); a.l("stz $420A ; VTIME = 100 — the line the sprites are on"); @@ -2503,7 +2520,6 @@ fn arm_hv_irq_and_run(a: &mut Asm, tag: &str) { a.l("sei"); a.l("stz $4200 ; disarm before reading anything back"); a.l("lda $4211"); - let _ = tag; } /// A sprite at `X = $100` is entirely off-screen and still consumes its range slot.