Skip to content

Commit c034f1d

Browse files
doublegateclaude
andcommitted
test(accuracysnes): C7.06 — Time Over reads set by V = YLOC + 1
C7 on-cart coverage 7 -> 8 of 16. 8x8 sprites can NEVER reach Time Over. The budget is 34 tiles per line, but range evaluation stops at the 33rd in-range sprite, so 8x8 caps at 32 -- under the limit, permanently. C7.05's forty 8x8 sprites set Range Over and leave Time Over clear, and the eval-line probe reports the same for the same reason. Twenty 16x16 sprites give 40 tiles while staying under the 32-sprite range limit. That separation is itself an assertion: Time Over must set while Range Over stays clear, so a core raising the two together fails. The bracket runs across the line boundary rather than within a line, because the asserted position is fixed and there is no index to sweep: clear on the eval line V = 100, set on V = 101. Phase B samples on line 101 deliberately -- RustySNES raises the flag at HBLANK_START_DOT of the eval line, EARLIER than the assertion requires, and pinning that exact dot would fail a core raising it at (101, 0) exactly, which the assertion permits. A low-tile control (the same 20 sprites at 8x8, 20 tiles) must read clear at phase B's sampling point; without it "set" would only have meant "sprites are present". Four injections, each failing its own code: flagging on any sprite fails code 4 (the control), never setting fails code 2, setting at dot 0 fails code 1. snes9x fails it and is recorded as an expected divergence (SNES9X_KNOWN_FAILURES 13 -> 14): it already reads Time Over set on V = 100, flagging the overflow a line early because it evaluates and paints in one pass. The control passes there, so it is the position that is wrong, not the budget. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 172854b commit c034f1d

11 files changed

Lines changed: 874 additions & 306 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
### Added
1313

14+
- **AccuracySNES: `C7.06` — Time Over reads set by `V = OBJ.YLOC + 1, H = 0`.** `C7` on-cart
15+
coverage 7 → 8 of 16.
16+
17+
**8x8 sprites can never reach Time Over.** The budget is 34 tiles per line but range evaluation
18+
stops at the 33rd in-range sprite, so 8x8 caps at 32 — under the limit, permanently. Twenty 16x16
19+
sprites give 40 tiles while staying under the 32-sprite range limit, which also keeps the two flags
20+
independently observable: the row asserts Time Over sets while Range Over stays **clear**, so a
21+
core raising them together fails.
22+
23+
The bracket runs across the line boundary rather than within a line, since the asserted position is
24+
fixed and there is no index to sweep — clear on the eval line `V = 100`, set on `V = 101`. Phase B
25+
samples on line 101 deliberately: RustySNES raises the flag at `HBLANK_START_DOT` of the eval line,
26+
*earlier* than the assertion requires, and pinning that dot would fail a core raising it at
27+
`(101, 0)` exactly, which the assertion permits.
28+
29+
A low-tile control (the same 20 sprites at 8x8, 20 tiles) must read clear at phase B's sampling
30+
point, or "set" would only have meant "sprites are present". Four injections, each failing its own
31+
code: flagging on any sprite fails code 4 (the control), never setting fails code 2, and setting at
32+
dot 0 fails code 1.
33+
34+
snes9x fails it and is recorded as an expected divergence (`SNES9X_KNOWN_FAILURES` 13 → 14): it
35+
already reads Time Over set on `V = 100`, flagging the overflow a line early because it evaluates
36+
and paints in one pass. The control passes there, so the position is wrong, not the budget.
37+
1438
- **AccuracySNES: `C7.05` — Range Over trips at the 33rd in-range sprite's evaluation cycle,
1539
`H = OAM.INDEX * 2`.** A fixed bracket would be vacuous: an H-IRQ is serviced ~22-27 dots after its
1640
`HTIME`, so one tight enough to pin dot 65 is inside the latency's own uncertainty and one loose

‎docs/accuracysnes-coverage.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Every sub-group of Part V is enumerated, so this is a **complete** statement of
2828
| `C4` | 5 | 0 | 5 | — |
2929
| `C5` | 15 | 0 | 12 | C5.06, C5.07, C5.15 |
3030
| `C6` | 7 | 0 | 6 | C6.07 |
31-
| `C7` | 16 | 7 | 6 | C7.06, C7.07, C7.12 |
31+
| `C7` | 16 | 8 | 6 | C7.07, C7.12 |
3232
| `C8` | 12 | 0 | 11 | C8.09 |
3333
| `C9` | 8 | 2 | 0 | C9.01, C9.02, C9.03, C9.06, C9.07, C9.08 |
3434
| `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
5252
| `F1` | 22 | 13 | 0 | F1.13, F1.15, F1.16, F1.17, F1.18, F1.19, F1.20, F1.21, F1.22 |
5353
| `G1` | 18 | 15 | 0 | G1.06, G1.13, G1.18 |
5454

55-
**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.
55+
**294 of 443** enumerated assertion rows covered by an on-cart test, plus **53** covered only by a rendered scene (`docs/adr/0013`) — **347 of 443** in total.
5656

5757
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.
5858

‎docs/accuracysnes-plan.md‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ before the H counter wraps and silently returns a plausible small number.
469469
| `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. |
470470
| `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. |
471471
| `C7.05` | **landed** | Not a fixed bracket — the set dot has to *move with the index*. See below. |
472-
| `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. |
472+
| `C7.06` | **landed** | 8x8 sprites can *never* reach Time Over — evaluation stops at 32 sprites, so 32 tiles is the ceiling against a 34 budget. Twenty 16x16 sprites give 40 tiles while staying under the range limit, which also keeps the two flags independently observable. |
473473
| `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`. |
474474
| `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". |
475475

@@ -540,6 +540,29 @@ its Range Over does not track the index — a scanline-granularity flag. RustySN
540540
anchored to MesenCE on the *line* by `scripts/probes/eval-line-213e`; the *dot* is
541541
documentation-anchored only, because the Mesen2 headless runner times out in this environment.
542542

543+
#### `C7.06` — 8x8 sprites cannot reach Time Over at all
544+
545+
The tile budget is 34 per line, but range evaluation **stops** at the 33rd in-range sprite, so 8x8
546+
sprites cap out at 32 tiles — under the limit, permanently. `C7.05`'s forty 8x8 sprites set Range
547+
Over and leave Time Over clear, and the `eval-line-213e` probe reports the same for the same reason.
548+
Twenty 16x16 sprites give 40 tiles while staying under the 32-sprite range limit, so Time Over trips
549+
and Range Over must **not** — which the row asserts, since a core raising the two together fails it.
550+
551+
The bracket is across the line boundary rather than within a line, because the asserted position is
552+
fixed and there is no index to sweep: clear on the eval line `V = 100`, set on `V = 101 = YLOC + 1`.
553+
Phase B deliberately samples on line 101 rather than late on line 100 — RustySNES raises the flag at
554+
`HBLANK_START_DOT` of the eval line, *earlier* than the assertion requires, and pinning that exact
555+
dot would fail a core that raises it at `(101, 0)` exactly, which the assertion permits.
556+
557+
A low-tile control completes it: the same 20 sprites at 8x8 (20 tiles) must read Time Over **clear**
558+
at phase B's sampling point. Without it, a core reporting Time Over whenever any sprite is on the
559+
line passes both A and B. Four injections, each failing its own code — flag on any sprite fails
560+
**code 4** (the control), never setting fails **code 2**, and setting at dot 0 fails **code 1**.
561+
562+
snes9x fails this row too, and is recorded as an expected divergence: it already reads Time Over set
563+
on `V = 100`, flagging the overflow a line early because it evaluates and paints in one pass. The
564+
control passes there, so it is the position that is wrong, not the budget.
565+
543566
#### `C7.05`/`C7.06` — the probe, and the two sampling traps
544567

545568
An earlier revision of the table above said RustySNES sets `range_over` a line late against MesenCE.

‎scripts/accuracysnes/crossval.sh‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,14 @@ ran=0
156156
# (range evaluation walks OAM two cycles per sprite). RustySNES's per-dot position is anchored to
157157
# MesenCE on the *line* by scripts/probes/eval-line-213e; the *dot* itself is documentation-anchored
158158
# only, because the Mesen2 headless runner times out in this environment. Region-independent.
159-
SNES9X_KNOWN_FAILURES=13
159+
# snes9x, +1 test (C7.06 "TimeOver by YLOC+1", fails phase A with code 1): the same scanline-granular
160+
# sprite model as C7.05, seen from the other side. The row requires Time Over to be clear while the
161+
# sprites' OWN line is being evaluated (V = 100) and set once the line they paint on has begun
162+
# (V = 101 = YLOC + 1); snes9x already reads it set on V = 100, i.e. it flags the tile-budget
163+
# overflow a line early because it evaluates and paints in the same pass. The 20-sprite/8x8 control
164+
# passes there, so this is the position being wrong, not the budget. Documented by nocash fullsnes
165+
# and the SNESdev Wiki (34-tile budget, raised by the fetch phase). Region-independent.
166+
SNES9X_KNOWN_FAILURES=14
160167

161168
# --- snes9x, via the libretro host --------------------------------------------------------------
162169
if [[ -f $SNES9X ]]; then

‎tests/roms/AccuracySNES/ERROR_CODES.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,17 @@ Provenance: **Documented** (fullsnes and the SNESdev Wiki: range evaluation walk
12741274
| 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 |
12751275
| 3 | `$06` | Range Over never set at all in phase B, so its clear reading at dot ~105 proved nothing |
12761276

1277+
### C7.06 — TimeOver by YLOC+1
1278+
1279+
Provenance: **Documented** (fullsnes and the SNESdev Wiki: the sprite tile budget is 34 per line and Time Over is raised by the fetch phase, so it reads set by the start of the line the sprites paint on). Kind: scored.
1280+
1281+
| Code | Byte | Meaning |
1282+
|---|---|---|
1283+
| 1 | `$02` | Time Over was already set on the sprites' own line (V = 100), earlier than V = YLOC + 1 |
1284+
| 2 | `$04` | Time Over had not set by V = YLOC + 1 with 40 sprite-tiles due on the line |
1285+
| 3 | `$06` | Range Over set alongside Time Over with only 20 sprites on the line — the two flags are being raised together instead of on their own conditions |
1286+
| 4 | `$08` | Time Over set with only 20 sprite-tiles due, so phase B's reading meant 'sprites are present', not 'the tile budget was exceeded' |
1287+
12771288
### C7.02 — Time Over is slivers
12781289

12791290
Provenance: **Documented** (SNESdev Wiki, Sprites; fullsnes; anomie). Kind: scored.

0 commit comments

Comments
 (0)