Skip to content

Commit 121f666

Browse files
doublegateclaude
andauthored
docs(accuracysnes): AccuracySNES found an ares bug — $F1's timer-2 reset is inverted (#308)
E3.06 and E8.02 were the two ares divergences not attributable to a known snes9x failure, and both read 0 from every timer-2 slot they record. One mechanism explains both, and it is visible in ares/sfc/smp/io.cpp's $F1 handler: if(timer0.enable.raise(data.bit(0))) { timer0.stage2 = 0; ... } if(timer1.enable.raise(data.bit(1))) { timer1.stage2 = 0; ... } if(!timer2.enable.raise(data.bit(2))) { timer2.stage2 = 0; ... } // ^ negated, and only here raise() is true on a 0->1 transition, so timers 0 and 1 reset their counters ON a raise -- the documented behaviour. Timer 2 resets on anything EXCEPT a raise, so every later $F1 write clears it, including the write that stops the timer. Both rows enable timer 2, run an interval, write $F1 again to stop it, then read $FF; in ares that stopping write has already zeroed T2OUT. ares is internally inconsistent here, which is the strongest evidence available that this is a stray `!` rather than intent: its own timers 0 and 1 do the un-negated version, and RustySNES, snes9x and Mesen2 all treat the three identically (rustysnes-apu/src/lib.rs's 0x01 arm resets stage2/stage3 only when `raised`). This is the first bug in a reference emulator AccuracySNES has found, and it took the third opinion to see: with only snes9x and Mesen2 both rows passed everywhere and there was nothing to investigate. E3.06's own weakness stands separately -- its 8..15 band still ends one tick short of TnOUT's 4-bit wrap, which is why the previous change made it record its counts. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 122ef84 commit 121f666

2 files changed

Lines changed: 59 additions & 3 deletions

File tree

‎CHANGELOG.md‎

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

1212
### Added
1313

14+
- **AccuracySNES found a bug in ares — the first time it has found one in a reference emulator.**
15+
`E3.06` and `E8.02` were the two divergences the third opinion could not attribute to a known
16+
snes9x failure, and both read **0** from every timer-2 slot they record. One mechanism explains
17+
both, and it is visible in `ares/sfc/smp/io.cpp`'s `$F1` (CONTROL) handler:
18+
19+
```cpp
20+
if(timer0.enable.raise(data.bit(0))) { timer0.stage2 = 0; timer0.stage3 = 0; }
21+
if(timer1.enable.raise(data.bit(1))) { timer1.stage2 = 0; timer1.stage3 = 0; }
22+
if(!timer2.enable.raise(data.bit(2))) { timer2.stage2 = 0; timer2.stage3 = 0; }
23+
// ^ negated, and only here
24+
```
25+
26+
`raise()` is true on a 0→1 transition, so timers 0 and 1 reset their counters **on** a raise — the
27+
documented behaviour. Timer 2 resets on **anything except** a raise, so every later `$F1` write
28+
clears it, *including the write that stops the timer*. Both rows enable timer 2, run an interval,
29+
write `$F1` again to stop it, then read `$FF` — and in ares that stopping write has already zeroed
30+
`T2OUT`.
31+
32+
**ares is internally inconsistent**, which is the strongest evidence available that this is a stray
33+
`!` rather than intent: its own timers 0 and 1 do the un-negated version, and RustySNES, snes9x and
34+
Mesen2 all treat the three identically (`rustysnes-apu/src/lib.rs`'s `0x01` arm resets
35+
`stage2`/`stage3` only when `raised`).
36+
37+
It took the third opinion to see. With only snes9x and Mesen2 both rows passed everywhere and there
38+
was nothing to investigate — which is the case for having built the ares host, made concrete.
39+
1440
- **`E3.06` now records the counts it compares, and that turned an ares disagreement into a
1541
measurement.** The row asserts timer 2 ticks ~8x timer 0 over one interval, with a band of `8..15`
1642
— and `TnOUT` is **four bits**, so the band ends one tick short of its own wrap. That tightness is

‎scripts/accuracysnes/ares_host/README.md‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ obvious from the tally and it changes what each row means:
3939
| `C7.05` | 1 | snes9x (code **2** — a different failure) | 2-vs-2, and the two dissenters disagree with each other |
4040
| `C7.10` | 1 | snes9x | **2-vs-2** — RustySNES + Mesen2 against snes9x + ares |
4141
| `F1.10` | 2 | snes9x | **2-vs-2** (but see the caveat below) |
42-
| `E8.02` | 3 | nobody | ares alone |
43-
| `E3.06` | 2 | nobody | ares alone — **but see below: probably a wrap, not a rate** |
42+
| `E8.02` | 3 | nobody | ares alone — **same cause as `E3.06`, see below** |
43+
| `E3.06` | 2 | nobody | ares alone — **an ares bug, found by this cart** |
4444

4545
So ares is *corroborating snes9x* on three rows rather than standing alone, and only `E8.02` and
4646
`E3.06` are genuinely ares-only. **Nothing here is adjudicated** — this is the shape of the
@@ -53,7 +53,37 @@ does not involve controller state at all. The claim came from the Mesen2 known-f
5353
`crossval.sh`, which attributes *its* `F1.10` failure to the port-2 limitation and is itself now
5454
flagged as doubtful there.
5555

56-
**`E3.06` is probably the row's fault, not ares'.** It compares timer 2's tick count against timer
56+
## The two ares-only rows share one cause, and it is an ares bug
57+
58+
`E3.06` and `E8.02` both read **0** from every timer-2 slot they record. One mechanism explains
59+
both, and it is visible in ares' source. `ares/sfc/smp/io.cpp`, the `$F1` (CONTROL) handler:
60+
61+
```cpp
62+
if(timer0.enable.raise(data.bit(0))) { timer0.stage2 = 0; timer0.stage3 = 0; }
63+
if(timer1.enable.raise(data.bit(1))) { timer1.stage2 = 0; timer1.stage3 = 0; }
64+
if(!timer2.enable.raise(data.bit(2))) { timer2.stage2 = 0; timer2.stage3 = 0; }
65+
// ^ negated, and only here
66+
```
67+
68+
`enable.raise(x)` is true on a 0→1 transition. Timers 0 and 1 reset their counters **on** a raise,
69+
which is the documented behaviour. Timer 2 resets on **anything except** a raise — so every later
70+
`$F1` write clears it, including the write that *stops* the timer.
71+
72+
Both rows do exactly that: enable timer 2 via `$F1`, run an interval, write `$F1` again to stop it,
73+
then read `$FF`. In ares the stopping write has already zeroed `T2OUT`.
74+
75+
**ares is internally inconsistent here**, which is the strongest evidence available that this is a
76+
stray `!` rather than intent: its own timers 0 and 1 do the un-negated version, and RustySNES,
77+
snes9x and Mesen2 all treat the three identically (`rustysnes-apu/src/lib.rs`, the `0x01` arm,
78+
resets `stage2`/`stage3` only when `raised`).
79+
80+
**This is the first bug in a reference emulator that AccuracySNES has found**, and it took the third
81+
opinion to see: with only snes9x and Mesen2, both rows passed everywhere and there was nothing to
82+
investigate.
83+
84+
## The row that is still weak regardless
85+
86+
**`E3.06` also has no headroom of its own**, and that is worth fixing independently of ares. It compares timer 2's tick count against timer
5787
0's over one interval, and `TnOUT` is four bits. The band is `8..15`, so it ends one tick short of
5888
its own wrap — structurally, because timer 0 must tick at least once and one timer-0 period is eight
5989
timer-2 periods. The row now records both counts: RustySNES reads timer 2 = **10**, ares reads

0 commit comments

Comments
 (0)