Repository navigation
v3.1.0 "Bellwether": AccuracyCoin 146/146 for real, the overclock and sprite-limit options, dual-cabinet rewind - #594
Conversation
…ound
T-ACCURACYCOIN-RESYNC-2610 (v3.1.0 item 1, D25). The battery moves from
upstream 46199ae4 to f5f41dc2: nine commits, two new tests (DMA Landing on
Write $0496, DMC Reload Timing $0497), and new row macros. The catalog grows
149 -> 151 rows / 144 -> 146 scored, and the battery reads 146 of 146.
THE NEW ROM FOUND TWO DEFECTS, FIXED RED FIRST
1. A DMC load DMA refused by a write took three cycles, not four
(DMA Landing on Write, error 9 = test 9). RDY cannot halt a write. When
a pending load reaches the get half it would enter on and that cycle is
a CPU write, it is refused and enters on the NEXT read whichever half
that is: four cycles ([Put (halt)] [Get] [Put] [Get]) after one refusing
write, three after two. Our "a load enters on its get half" deferral
applied a second time and pushed it to cycle 2, so the CPU ran the opcode
fetch the hardware spends halted and was one cycle ahead from then on.
Found by a black-box per-cycle comparison with the TriCNES harness's
output, aligned on the test's STA $5000 (rung 3; no TriCNES source was
read). Both sides show the load pending on the write cycle (a get half);
TriCNES halts at +1, we fetched the opcode there.
Fix: a one-shot latch, `dmc_load_write_delayed` (bus.rs), set in
Bus::write when a pending, serviceable load would have entered there,
consumed by the DMC entry in unified_dma_cycle_impl (which then skips the
get-half defer), cleared by the next CPU read. It outlives an instruction
(the refusing write is a store's last cycle), so it is in the BUS
save-state section: BUS_SECTION_VERSION 2 -> 3, older sections refused.
2. Misaligned sprite evaluation (Misaligned OAM behavior), three rules,
each red in turn (error 3, then 6, then 7):
- the FSM seeded (n, m) from OAMADDR at dot 0; nesdev: "the value of
OAMADDR at this tick [65] determines the starting address". The new
ROM's extra JSR/RTS (OAMDMAWithPage2) moved its $2003 write to dots
28-29 of scanline 0, after the capture. Re-seeded at dot 65.
- the fourth byte copied (X) is range-tested like Y: in range, OAMADDR
+= 1 and stays misaligned; out of range, += 1 then & $FC. The FSM
always realigned.
- a start at m = 3 copied ONE byte and stopped; it now copies four,
crossing into slot n + 1.
The X and m = 3 defects were always there: the OLD ROM recorded them as a
PASS. FAIL_MisalignedOAM_Behavior jumped out of a JSR'd routine without
popping the return address (upstream "stack fix" adacbc23), so a failure
in tests 2-7 returned into the test body and fell through to LDA #1.
Our 144/144 included that false pass. All three are written from the
ROM's comments and nesdev; the evaluation FSM is a Mesen2-derived region
(ppu.rs Provenance header), and these edits to it carry no new source.
EMULATION_EPOCH 2 -> 3 (ADR 0045): both fixes change bus cycles or frames.
TOOLING
- extract_catalog.py: upstream compressed the unofficial-opcode rows into
`tblf1`/`tblf2`, storing a one-byte token for "indirect", "zeropage",
"absolute", "immediate". The script returned 85 of 151 rows and exited 0
(it only checked total > 0). It now rebuilds names from the ROM's own
PrintTextSpecialStrings (so the eight Unofficial Immediates rows read
"immediate", as printed; addresses unchanged) and fails closed on any
row-shaped line it cannot read or any empty suite. 8 self-test cases; the
old asm still reproduces the old TSV byte for byte.
- derive_indices.py shares that grammar (it would have mis-indexed every
unofficial suite), and runs again: it read BUILD-PROVENANCE.tsv by column
position, stale since subtest_identify added result_addr, so every
validation compared a name with an address and it refused to run at all
(reproduced at HEAD against the old asm). Columns are now found by name,
and legacy-unrecorded rows (indices of an unknown older source) are not
used as validations. Old and new sources now derive 149 and 151 rows;
the diff is the 8 renames plus the 2 new tests.
- The mirror ROM is rebuilt from f5f41dc2 (unpatched rebuild reproduces
upstream's md5 a3635c87; budget header 1, call site 5, fill 29, elsewhere
0; mirror control 146 of 146).
- BUILD-PROVENANCE.tsv: sprite-eval-misaligned-oam settles on frame 211
(212), from the generator; verdict unchanged (Pass), the only field moved.
VERIFIED
- --features test-roms --release: 3,234 passed, 1 failed, 11 ignored; the
failure was that settle frame, regenerated here; the provenance test then
passes 4/4. AccuracyCoin 146/146 (KNOWN_FAILING empty).
- misaligned_oam_eval_starts_at_dot_65_copies_four_bytes_and_tests_x: each
of the three mutants (dot-0 seed, one-byte copy, unconditional realign)
fails at its own assertion.
- The old ROM still reads 144/144 on the fixed build.
- fmt; clippy (workspace, and the harness with test-roms); rustdoc -D
warnings; thumbv7em no_std build; markdownlint.
NOT DONE HERE
- The sibling (RustyNES_MiSTer): its RTL already seeds at the end of the
clear and copies four bytes, but never applies the X out-of-range & $FC;
the pin move will show it. Sibling sprint.
- The 31 legacy sub-test ROMs are not rebuilt.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
T-EPOCH-FINGERPRINT (v3.1.0 item 2, CI-02). ADR 0045's rule -- raise EMULATION_EPOCH in the same change as anything that alters a frame, a sample or a bus cycle against the last release -- was enforced by hand. Forgetting it ships movies that replay on the new timing and netplay sessions that desync, with nothing saying why. ADR 0045 asked for a check that ties the epoch to a fingerprint of a fixed panel; this is it. MECHANISM tests/epoch_fingerprint.rs runs seven committed test ROMs, one per subsystem (the full AccuracyCoin battery, ny2011, spritecans, dmc_tests latency, mmc3_test_2 4-scanline_timing, sprdma_and_dmc_dma, flowing_palette), and fingerprints each: every frame's framebuffer (FNV of per-frame FNVs, so a transient cannot hide behind a converged last frame), all audio samples, end RAM and the CPU cycle count. golden/epoch_fingerprint.tsv records the rows, the epoch they describe, and `last_release_epoch`, the epoch the last release shipped. - moved output while EMULATION_EPOCH == last_release_epoch: FAIL, and RUSTYNES_BLESS_EPOCH_FINGERPRINT=1 is REFUSED in that state. The refusal is the gate; a bless that accepted moved rows would make the rule optional again. - once the epoch is above last_release_epoch, a re-bless records new output. A second behaviour change in the same release needs a re-bless, not a second rise, because the rule is relative to the last release (a first version demanded a rise per change; corrected before commit). - the table must name the current epoch and have one row per panel ROM. Blessed at epoch 3 / last_release_epoch 2 (this branch already raised the epoch for the AccuracyCoin fixes). SHOWN TO WORK (last_release_epoch set to 3 to simulate the next cut; all four touched files restored byte for byte afterwards): - control: passes - mutant A, the DMC write-refusal latch never set: fails, names the epoch rule; its bless is refused and the table is unchanged - mutant B, the X range test removed: fails - B with EMULATION_EPOCH raised to 4, no bless: fails, asks for a re-bless - B, epoch 4, blessed: passes, and a clean rerun passes Both mutants are this release's own accuracy fixes reverted, so the panel is shown to see the class of change it exists for. Only the AccuracyCoin row moved for either. COVERAGE LIMIT, stated where it lives (module docs, ADR 0045 amendment): a change that moves nothing on these seven ROMs passes. The commercial snapshot suites stay the wider net. It runs in CI's test-roms job (the job runs the whole harness with the feature); about 15 s. RELEASE CEREMONY: the cut must set `# last_release_epoch` to the shipped epoch in the release commit, or the gate stops guarding. Recorded in docs/agents/ci-and-release.md beside the epoch rule. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Records carried from the v3.0.1 cycle into v3.1.0. - docs/performance.md: the palette-offset lead closes as measured. 8f449691 -> 33ea0572 alone, ab_check.sh, two independent runs on a quiet host (order-bias drift <= 0.95%): nestest +1.5/+1.8%, palette +0.4/+1.9%, nestest_fast +0.5/+1.1%, palette_fast +1.4/+0.5%. Slower on every workload in both runs, so 33ea0572 is confirmed as a contributor, but at 0.4-1.9%, not the 4-5% the bisection step showed, and the runs disagree on which workload pays most. The commit adds nothing to the frame loop; code layout or inlining is the untested working explanation. Nothing adopted. Logs are in the gitignored salvaged/perf-v3.0.1-palette-ab/. - docs/agents/tooling-traps.md: `until ! pgrep -f <pat>` never exits, because the loop shell's own command line contains the pattern. Three wait loops outlived their jobs this way in v3.0.1. Wait on a PID, a file the job writes last, or `pgrep -f '[p]attern'`. - docs/agents/review-bots.md: a CodeRabbit review slice is red in CI by construction when a feature-gated test depends on the other slice (#591: holy_mapperel.rs and the provenance audit). Say so on the slice PR and judge CI on the release PR only. No code changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
v3.1.0 items 3-5 (T-CPU-OVERCLOCK, T-SPRITE-LIMIT, D22): two options carried in HardwareOptions, so movies record them and netplay peers must match, with one movie-format and one protocol bump for both. Plus a defect the purity work exposed: the debugger's and HD-pack's CHR peek changed the game on five boards. CPU OVERCLOCK (Nes::set_cpu_overclock, 1..=4, default 1) The bus divides the region's master-clock CPU divider (NTSC 12 -> 6/4/3; PAL and Dendy round down, PAL x3 = 5 = x3.2). Everything that measures console time stays at the STOCK rate: the APU and DMC, the mappers' notify_cpu_cycle IRQ counters (VRC / FME-7 / N163 raster splits), the PPU's open-bus decay and post-reset timers. Mechanism: each CPU cycle adds the effective divider to `overclock_debt`; a "stock step" runs those devices once whenever the debt reaches the stock divider, and the APU is handed its own counter (`apu_cycle`) because it derives its put/get phase from the counter it gets. DMA follows that phase, so a DMA takes ~k times the CPU cycles and the same real time. run_frame's 150,000-cycle budget scales by k (x4 plus the 80-line scanline overclock needs ~155,000). The smallest divider, 3, keeps both cycle splits valid: read (0, 3), write (2, 1). At x1 the branch is never taken: every cycle is a stock step and the APU gets the CPU counter, byte for byte the old path; the epoch gate, whose seven ROMs all run at x1, passes unchanged. debt + apu_cycle are in BUS section 3 (unreleased; run-ahead restores mid-run). Desktop: Settings > Enhancements combo; unlike the extra-scanline overclock (held at stock while a movie records and under netplay since v2.9.7) it is recorded as set and replayed with it. NOT done: libretro and mobile options (neither exposes the scanline overclock either). Not hardware behaviour, and said so in the docs. SPRITE LIMIT (Nes::set_sprite_limit_disabled; the desktop checkbox existed since v1.x and was inert) Render-only. After the eighth REAL sprite fetch (slot 7, dot 316) of a visible line, `fetch_extra_sprites` walks OAM from entry 0, skips the first eight in range, and fetches up to 56 more (MAX_EXTRA_SPRITES) through ppu_read_sprite with NO observe_a12_addr; `emit_pixel` draws them only where none of the eight hardware sprites is opaque (higher OAM index = lower priority), never as sprite 0. Evaluation, secondary OAM, overflow, sprite-0 hit and the real fetches are untouched. The pending extras are PPU snapshot v13 (a snapshot can fall between the fetch and the line). The extra reads are made only where `Mapper::chr_reads_are_pure()` (new, default true). A scan of every ppu_read / ppu_read_sprite body for writes to `self` (brace-matched, helper calls followed) found five impure families: MMC2 (9) / MMC4 (10) CHR latches on tiles $FD/$FE, the J.Y. ASIC (35, 90, 209, 211) clocking its IRQ on PPU reads, Bandai 96's address-following inner bank, Nanjing 163's A13 latch. `every_board_that_claims_pure_chr_reads_has_them` reads all of CHR on every constructible id (iNES 0-255, NES 2.0 256-4095) that claims purity and requires save_state unchanged, and pins the impure set; MMC2 or the J.Y. ASIC claiming purity fails it (both shown). FOUND ON THE WAY (fixed): `SystemBus::debug_peek_ppu`, documented as side-effect free and used per frame by the HD-pack compositor (desktop, emu thread, mobile) and by the pattern viewer / hex viewer, read CHR through `mapper.ppu_read`. On MMC2 / MMC4 a full pattern-table read crosses tiles $FD/$FE and flipped the latch: opening the debugger on Punch-Out!! changed the game. It now brackets the read with the board's save_state / load_state on the five impure boards (free elsewhere). Red first: `debug_peek_ppu_changes_no_board` failed on mapper 9, passes now. The existing CHR-RAM sweep had documented the latch flip and worked around it. FORMATS (ADR 0044 amended) - .rnm format 6, minimum 6: the options record gains both fields; a format-5 record is shorter and would decode as garbage, so it is refused (every v5 movie is epoch 1 or 2, already refused). - netplay protocol 7, magic "RNE7", protocol 6's 72-byte Sync unchanged. The configuration hash's input changed, so a v3.0.x peer with identical settings would hash differently and be refused as "settings differ", the wrong reason. "RNE6" joins OLDER_SYNC_MAGICS and is refused as another emulator version, naming its epoch (protocol 6 carries it). TESTS (all mutation-checked; each mutant reverted) - cpu_overclock.rs 6/6. Mutants: every cycle a stock step (2 fail); the restored apu_cycle parity flipped and the debt dropped (snapshot test fails). That test was first BLIND: ny2011 is silent for its first 300 frames (measured peak 0.000), so the APU phase was unobservable; moved to apu_mixer/square.nes and it now asserts the window is audible. - sprite_limit.rs 5/5: with the option on, RAM, audio and CPU cycles match stock frame by frame; blargg sprite_overflow 1-5 pass; carriage; a mid-frame snapshot with 8 extras pending. The stimulus is a ROM BUILT IN THE TEST (16 opaque sprites on scanline 101): spritecans never exceeds 7 per line (measured over 2,400 frames) and NEStress's 62 are transparent (54 extras WERE fetched there, which separated "blind" from "broken"). Mutants: extras never drawn, extras dropped on restore -- both caught. - message.rs: a_protocol_6_peer_is_refused_as_another_version_naming_its_epoch; movie.rs: a_format_5_movie_is_refused_as_too_old. - an_unknown_expansion_device_tag_is_refused located its bytes by a fixed offset from the BUS section's end that went stale in 41367fd (the latch byte) and still passed by accident: the shifted window held [0, 0] and the byte it corrupted was refused for its own reason. Its window assertion caught it once 9 more bytes landed; the offset is now the real tail. - snapshot_schema_audit: cpu_overclock / cpu_div_effective (config, derived), stock_step (intra-cycle transient) and sprite_limit_disabled (config) classified with reasons; the state they drive is serialized. GATES: fmt; clippy workspace, harness+test-roms, frontend scripting / scripting,hd-pack / retroachievements / full, both wasm builds; rustdoc -D warnings; no_std; --features test-roms --release 3,251 passed / 0 failed / 11 ignored; default workspace 2,891 / 0 / 7; the epoch gate unchanged (x1 and option-off output identical). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
v3.1.0 Sprint 3, plan items 6, 7 and 8 (T-PAL-EMPHASIS, T-COMPOSITE-ARTIFACTS, T-MMC3-NEC-OVERRIDE). Item 6 -- the PAL / Dendy emphasis bits. On the 2C07 and the Dendy PPU, PPUMASK bits 5 and 6 swap meaning: bit 5 emphasises green and bit 6 red, the reverse of the NTSC 2C02 (NESdev "PPU registers", PPUMASK's bit table: "Emphasize red (green on PAL/Dendy)"). The core applied the NTSC order everywhere, so every PAL or Dendy game that set emphasis was tinted the wrong way. `emit_pixel` now exchanges the two bits when the region is not NTSC, before the emphasis index reaches the palette; NTSC is untouched. Pinned by `pal_and_dendy_swap_the_red_and_green_emphasis_bits`. The epoch was already raised this release (3), and the fingerprint panel is NTSC, so it does not move. Item 8 -- the MMC3 IRQ revision as a setting, and the defect it found. `Mapper::set_mmc3_revision_override(Option<Mmc3Revision>)` forces Sharp or the alternate (NEC / MMC3A) behaviour on mapper 4 whatever the header says; `None` returns to the header's. It rides in `HardwareOptions` (`mmc3_revision`: 0 none, 1 Sharp, 2 Nec, named "MMC3 revision" in a mismatch), so a movie or a netplay peer on another value is refused. The bus keeps the override in `mmc3_revision_override` and re-applies it after a power cycle, which rebuilds the mapper from the ROM and would otherwise drop it (`the_mmc3_override_survives_a_power_cycle_and_clears`). The desktop exposes it as `[emulation] mmc3_irq_revision` (Auto / Sharp / Alternate) in Settings > Emulation, EN and ES. Running blargg's `mmc3_test_2/6-MMC3_alt` under the override is what the plan's gate asked for, and it FAILED: "IRQ should be set when reloading due to clear". MMC3.md states the rule: the alternate revision "generates only a single IRQ when $C000 is $00 ... In addition, writing to $C001 with $C000 still at $00 will result in another single IRQ being generated." The mapper's reload path asserted on a $C001 reload to 0 only for Sharp. Path 1 of `clock_irq` (the explicit reload) now asserts for both revisions when the reloaded value is 0; path 2 (the natural reload of a counter already at 0) still asserts on Sharp only, which is the "single IRQ" half. With that, `6-MMC3_alt` passes under the override and `5-MMC3` then fails, proving the override acts; all 20 MMC3 tests pass at the default. Two older tests pinned the opposite of the documented rule and are corrected here, with the wiki's text in their docs rather than silently: `nec_does_not_assert_on_reload_to_zero` became `nec_asserts_once_on_a_c001_reload_to_zero_and_not_after` (one IRQ on the $C001 reload, none on the was-zero reload, one more on a second $C001 write), and `mmc3_submapper_4_is_nec_and_0_is_sharp` now expects exactly one IRQ from its arm sequence, not zero. The submapper table's "Loading the latch with 0 disables IRQ" is shorthand; MMC3.md is exact. The schema audit classifies `mmc3_revision_override` as host configuration: a restore loads into the live mapper, whose revision the override already set. Item 7 -- differential phase, display only. `signal_decode.wgsl` rotates the decoded I/Q by `-knobs.w * row`, where `knobs.w` is radians per palette luma row, which is how the PPU's brighter rows shift hue (about 2.5 degrees per row on a 2C02E, 5 on a 2C02G). A sixth pragma parameter `diff_phase` (default 0) feeds `u[15]`. At 0 the rotation is the identity, so the default picture is unchanged; `shader_pass` pins six parameters with `diff_phase` defaulting to 0. It changes no emulated output, so no epoch rise. The plan's "inter-pixel artifacts" half is the existing signal decode; only the differential phase was missing. Verification, on this tree plus the Sprint 4 work that follows it: cargo test --release --workspace --features test-roms: 3,262 passed, 0 failed, 11 ignored. fmt clean; clippy -D warnings for the workspace and for scripting, scripting+hd-pack, retroachievements and full; both wasm32 clippy builds; rustdoc -D warnings; the thumbv7em no_std build. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
v3.1.0 Sprint 4, plan item 9 (T-PS-dual-runahead, FE-09, decision D27), the work ADR 0032's 2026-10-07 amendment decided. Until now a two-console cabinet ignored the rewind key and the run-ahead setting: produce_dual_frame never looked at either. The unit of rewind and run-ahead is the CABINET, never one console. The two consoles share a 2 KiB WRAM and drive each other's /IRQ through the $4016 bit-1 latch, so stepping one back, or running one ahead, without the other produces a cabinet from two timelines. Everything is therefore built on the existing "RVSD" container, which snapshots both consoles and the latch and restores them all-or-nothing. Core (`VsDualSystem`): - The cabinet owns a `RewindRing` (`enable_rewind[_with]`, `disable_rewind`, `rewind_len`, `set_rewind_capture`, `rewind_capture`, `rewind_step_back`, `rewind_clear`). `run_frame` pushes a capture after every frame while capture is on; the consoles' own rings stay disabled. - Entries are whole RVSD containers with BOTH framebuffers. The single console's ring stores slim entries and re-renders the picture after a step back by running a frame and restoring again; for the cabinet that would run the five-cycle soft lockstep forward and back on every step. Keeping the framebuffers costs two 245,760-byte buffers per entry before the ring's XOR delta and LZ4, and makes a step back exact for both screens with one restore. - `restore_quiet` is the restore that keeps the ring: each console restored with `Nes::restore_quiet` (no timeline-generation bump, no rewind clear). Run-ahead's rollback and a rewind step use it. `restore` (a loaded state) and `power_cycle` now empty the ring, because both replace the timeline it describes, as `Nes::restore` treats its own ring. `restore` and `restore_quiet` share one `restore_inner`, so the v2.9.0 all-or-nothing rollback across both consoles applies to both. Frontend: - `RunAhead::run_cabinet_ahead` / `finish_cabinet`: the single-console cycle on the cabinet. The persistent frame (captured by the ring), a snapshot, n-1 hidden frames and the visible one with capture off, then a quiet rollback. Hidden frames drain and discard both consoles' audio. - `produce_dual_frame` takes the frame inputs. Rewind held: step back and present both restored screens, no audio pushed, coin latch untouched (as the single path). Run-ahead (native only, as the single path): present the visible frame's two screens and main audio, then roll back. The depth goes through `effective_run_ahead`, so the budget throttle applies to cabinets too. - `rewind_budget(&Config)` is the one definition of the ring's byte budget and keyframe period. It was written out at three sites and the cabinet made a fourth; the load paths, the settings toggle and `build_dual_cabinet` all call it, and the settings toggle now reaches a loaded cabinet as well as a console. The gate, both clauses of the amendment, each shown able to fail: - `vs_dualsystem_rewind.rs` (6 tests): every step back across ten frames lands on that frame's two framebuffers and whole cabinet byte for byte, across keyframes and deltas; play resumed after a rewind replays the same frames; capture-off frames stay out of the ring; a loud restore and a power cycle empty it and a quiet restore keeps it. Mutants: the sub console's block captured SLIM (state kept, picture dropped), CAUGHT by the both-framebuffers assertion; a loud restore in the step back, CAUGHT by two tests. - `cabinet_runahead_matches_a_plain_run_on_both_screens` at depths 1 and 2: after every cycle the persistent cabinet's snapshot equals a plain run's, both visible screens equal the plain run n frames on, and the ring holds only persistent frames. Mutants: no rollback, CAUGHT; capture left on across hidden frames, CAUGHT (80 entries against the plain run's). - `a_cabinet_rewinds_and_runs_ahead_through_the_produce_path`, through `EmuCore`'s real produce path. Mutants: rewind ignored, CAUGHT; run-ahead ignored, CAUGHT. The stimulus is a cart built for it, and that matters. The protocol cart in `vs_dualsystem_synth.rs` never turns rendering on, so both of its screens are one unchanging colour and a framebuffer comparison against it passes whatever a restore does to the pictures. The new cart enables background rendering over blank CHR-RAM and writes a per-frame counter into $3F00 in each console's NMI: eight colours, $10-$17 on the main and $20-$27 on the sub. The first version used a full 64-entry counter and the run-ahead test's own sanity check failed on it: the palette's blacks ($xD-$xF) come three in a row, so consecutive frames could be identical. Same cart in both test files (the frontend's copy is `runahead::tests::flashing_cabinet`). Out of scope, unchanged: netplay and TAS (`T-PS-dual-netplay`), the debugger and HD packs stay single-console; a cabinet keeps stock timing. ADR 0032's amendment gains an outcome note; docs/frontend.md, docs/compatibility.md and the accuracy ledger no longer list rewind and run-ahead as excluded. Verification: cargo test --release --workspace --features test-roms: 3,262 passed, 0 failed, 11 ignored. fmt; clippy -D warnings for the workspace, scripting, scripting+hd-pack, retroachievements and full; both wasm32 clippy builds; rustdoc -D warnings; the no_std build. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…ecklist
v3.1.0 Phase S, SUB-1 and SUB-2 (plan item 16), plus a stale RTL-5 box.
SUB-1. ref-docs/2026-10-07-mister-core-contribution-requirements-update.md
records the MiSTer-devel page "Contributing a Core to MiSTer FPGA" as it
stands, read from the wiki's own git repository (head 1227b217, 2026-10-06;
the page's last revision 317b50e, 2026-09-26), so the text and the date of
every edit are exact. The 2026-08-23 record (revision a6c9017) is immutable
and stays; this one supersedes the parts it names.
The page was rewritten, not edited. 6aaf88a (2026-09-19) removed the
ten-step page and 01c1da7 (2026-09-20) added the current one; 2026-09-26
only reworded the update_all FAQ answer. That corrects the plan, which said
the page "changed on 2026-09-26".
What the rewrite removed matters to the submission case. Gone: "preservation
value"; the sentence the case was built on ("Fully AI generated code should
meet a minimum reasonable bar for readability and include some evidence of
quality and accuracy testing"); the word "public"; the invitation, the
repository transfer and the Cores-list step; "reviewed within a few days".
Added: four reviewer criteria (the guidelines followed; does the developer
understand their code; will they maintain it; do they collaborate) and an
FAQ ("Is MiSTer-devel Anti-AI? Not at all ... as long as a developer
understands their code"), a review time of "upwards of a month", and other
routes into update_all, including a drop-in downloader database. The record
lists the consequences for the maintainer to weigh and decides none of
them: the testing evidence still answers "properly tested" but no longer a
named criterion; the central question is now asked of the maintainer, which
no test suite can answer; the duplicate-core risk is unstated rather than
gone; and the private sibling repository must be reachable by a reviewer.
SUB-2. to-dos/mister/contribution-checklist.md traces to both records and is
re-scoped in place, keeping each box's history: the preservation-value box
says the criterion is gone and why its evidence stays; the AI-code box
becomes the reviewer's criteria; the transfer box asks about whatever next
steps a review names; the Cores-list box is CONTINGENT on those steps. Two
boxes are new: the repository being reachable by the reviewer (BLOCKED on a
maintainer decision) and the drop-in database route (CONTINGENT).
contribution_checklist_audit.rs: 14 passed, including the three protected
boxes and the completion sentence; markdownlint clean.
RTL-5 (plan item 13) turned out to be closed already: sibling mkrom program
75, ppudecay075, has caught all three decay mutations since v2.9.5
(docs/rung3-ppu.md there). The TASKS.md box stayed open for four releases
after its work landed, which is how v3.1.0 came to plan it again; it is
ticked with the citation.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…ed 100% v3.1.0 plan item 10. Each stale statement from the 2026-10-06 backlog survey was re-checked against the code before it was changed, and the survey's DOC table, which existed only in a session transcript, is now in to-dos/plans/v3.1.0-plan.md with each item's outcome. What the checking changed about the survey's own claims: - DOC-02's "open questions (answered, FE-11)" was half right. The CRC32 question is half-answered: rustynes-gamedb, vendored from TetaNES, IS CRC32-keyed, while the Vs. database and save naming key on SHA-256. (A first draft of this change said "SHA-256 only"; reading the crate refuted it.) The region override is genuinely open: no frontend has one. - DOC-03 found an error the survey did not name: mappers.md called the Sharp default "MMC3A", which is the other revision. - DOC-04: `vrc24test` really is absent from the corpus; VRC2/4 coverage is m22's CHR walk plus local dumps. Kept, made precise. - DOC-05: CI-05 is obsolete (no x86_64-apple-darwin since v1.6.0, ADR 0009); the other two ROADMAP items were already done on 2026-10-07. - DOC-06: libretro-super#2131 merged 2026-10-06 (9c08e5e6f3, read with gh); docs#1215 is open. - DOC-07: already correct after the v3.0.1 cut; verified, not edited. - DOC-08: the wasm.rs comment's version claim for the winit build was not written, because `wasm-winit` predates v1.3.0 in the tree; the comment says what exists rather than when. Decision D25: to-dos/v1.8.x-on-device-verification.md is folded into docs/mobile-v2.9.3-run-sheet.md as rows G1-G17 and deleted. Only rows the run sheet did not already cover were kept (A1, A3-A7, A10 and A13 cover the rest). One row was dropped because it had become wrong: "the picker offers iNES / NES 2.0 only", when FDS and NSF shipped in v2.9.7 (T1-T7). Its three live links (STATUS, the v2.0.x mobile plan) now point at the run sheet; CHANGELOG-FULL and salvaged copies are history and keep theirs. The CHANGELOG now says plainly what the AccuracyCoin re-sync revealed: every release that reported 144/144 (or 141/141) failed parts of `Misaligned OAM behavior` as the fixed ROM scores it, because the old ROM's fail path returned into the test and recorded a pass. v3.1.0's 146/146 is the first without that masked failure. The S6 release-notes gate in the plan now requires the same statement in the notes. Verification: markdownlint on every touched file; the eleven release audits (contribution_checklist, cosim_manifest, f2_accuracy, feature_flag, libretro_info, libretro_makefile, mister_source_map, provenance_record, release_anchor, release_notes_render, release_state_prose): all pass. The code edits are doc comments only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Two sub-test ROMs built from upstream f5f41dc2 (the v3.1.0 re-sync), with build_sub_test_rom.py under upstream's own nesasm.exe through wine, rows measured by subtest_identify. - sprite-eval-misaligned-oam.nes is REBUILT (suite 18, test 6; the legacy build was 17/5). The legacy build carries the upstream bug fixed in adacbc23: FAIL_MisalignedOAM_Behavior jumped out of a JSR'd routine without popping the return address, so a failure in tests 2-7 fell through to a pass. Its `Pass` proved nothing, and it is the ROM the MiSTer sibling's ladder gated that entry by: the sibling's RTL failed test 5 (the X out-of-range mask) for every release before v3.1.0 while that gate read green. Settles at frame 175 (legacy: 211). - dmc-reload-timing.nes is NEW (suite 14, test 6, $0497), for the test upstream added. 15 frames to a verdict against the battery's 4500. It located the sibling's DMC reload defect to one bus cycle (423,976): a $4010 write on the timer's reload edge must set that reload's period. Both pass on this emulator; on the sibling they fail on the RTL without its v3.1.0 fixes (Fail(test 1), Fail(test 5)) and pass with them. Also to-dos/mister/TASKS.md: a board-session item for the $2006 copy delay. Since v3.1.0 the sibling follows the oracle's window-dependent delay (3 dots in a background-fetch window, the next dot elsewhere), which NESdev's constant "1 to 1.5 dots" does not support; it is recorded as provisional (sibling ledger 3.50), and only a board can settle it. Verification: accuracycoin_subtest_provenance 4/4 (it re-measures every row); fast_dotloop_diff 5/5. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
The cut, made with scripts/release-automation/bump_release.py from the CHANGELOG's new [3.1.0] section (codename chosen by the maintainer). The tool refused once, correctly: to-dos/ROADMAP.md's release chain ended "v3.0.1, the current release" and needed a written v3.1.0 clause rather than a token swap. That clause also corrects the chain's AccuracyCoin sentence, which stated 144/144 as clean. - Version 3.1.0 in Cargo.toml, both Cargo.lock files, the libretro .info, Android (versionCode 30100) and iOS; every "Current release" anchor. - VERSION-PLAN: the v3.1.0 "Bellwether" (current) row, and the planned row removed. - golden/epoch_fingerprint.tsv: last_release_epoch 2 -> 3. From here a moved fingerprint fails until the epoch rises again. - The masked AccuracyCoin score, said where it is claimed. Eleven documents stated 144/144 as the current score (README x4, OVERVIEW x2, SUPPORT, VERSION-PLAN's summary, STATUS, the accuracy ledger, TESTING, frontend.md, the user guide, the ROM licences, to-dos/README); each now reads 146/146 at f5f41dc2 and says the earlier 100% hid a failure. Historical records (release rows, dated measurements) keep their number. - CHANGELOG [3.1.0]: an introduction, the MiSTer core's v3.1.0 work, the records, and the verification below. - One commercial baseline re-blessed: external_coverage's Millionaire (Sachen, mapper 146), checkpoint f900 only (f1100, cycles and audio unchanged). Attributed by running that ROM alone on each v3.1.0 commit: unchanged through 687a5a8, moved at 5382218. The game database marks its CRC (F8C358D7) PAL, and 5382218 is the PAL emphasis swap. Verification on this tree: - cargo test --workspace --features test-roms --release: 3,262 passed, 0 failed, 11 ignored (epoch fingerprint passes at epoch 3). - external_real_games 60/0, external_extended 137/0, external_coverage 6/0 over 744 staged ROMs after the one attributed re-bless. - markdownlint; the release audits. Not yet: .github/release-notes/v3.1.0.md, which carries the MiSTer ladder and bitstream figures still being measured. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Docs7 for doublegate/rustynes
Commit |
|
@coderabbitai review |
|
Antigravity review (Gemini via Ultra)This PR implements CPU overclocking and PPU sprite limit toggles, refactors DualSystem state management to support rewinding and runahead, adjusts MMC3 and PAL emphasis emulation for test accuracy, and increments the netplay, save state, and movie format epochs. Blocking issues
Suggestions
Nitpicks
Automated first-pass review by Earlier review rounds (newest first)Round reviewed at 2026-10-08 11:56 UTCAntigravity review (Gemini via Ultra)This PR resyncs the AccuracyCoin test suite to achieve a true 146/146 score, implements CPU overclocking and a sprite-limit toggle that correctly sync with movies and netplay, fixes PAL/Dendy color emphasis, adds an alternate MMC3 IRQ revision option, and extends rewind/run-ahead support to the Vs. DualSystem cabinet. Blocking issues
Suggestions
Nitpicks
Automated first-pass review by Round reviewed at 2026-10-08 10:49 UTCAntigravity review (Gemini via Ultra)This PR updates the core to pass all 146 AccuracyCoin tests, introduces CPU overclocking and sprite-limit options for movies and netplay, fixes PAL emphasis and alternate MMC3 timing, enables Vs. DualSystem rewind/run-ahead, and synchronizes the MiSTer core and test suites. Blocking issuesNone found. SuggestionsNone. Nitpicks
Automated first-pass review by Round reviewed at 2026-10-08 10:32 UTCAntigravity review (Gemini via Ultra)Adds CPU overclocking, an option to disable the 8-sprite limit, fixes for MMC3 NEC variants to pass AccuracyCoin tests, and dual-cabinet rewind support. Blocking issuesNone found. Suggestions
Nitpicks
Automated first-pass review by Round reviewed at 2026-10-08 10:12 UTCAntigravity review (Gemini via Ultra)This PR updates the AccuracyCoin catalog to 146/146, adds configuration options to disable the sprite limit and overclock the CPU, and enables rewind for the Vs. DualSystem. Blocking issues
Suggestions
NitpicksNone found. Automated first-pass review by Round reviewed at 2026-10-08 09:02 UTCAntigravity review (Gemini via Ultra)This PR implements CPU overclocking, sprite limit toggles, and dual-cabinet rewind support, while bumping internal state, movie, and netplay protocol formats. Blocking issues
Suggestions
Nitpicks
Automated first-pass review by Round reviewed at 2026-10-08 08:53 UTCAntigravity review (Gemini via Ultra)This PR implements CPU-multiplier overclocking, a sprite-limit disable option, and dual-cabinet rewind, while achieving 146/146 on the AccuracyCoin test suite by fixing DMC load DMA timing and misaligned OAM evaluation, along with necessary version bumps for save-states, movies, and netplay formats. Blocking issuesNone found. Suggestions
Nitpicks
Automated first-pass review by |
There was a problem hiding this comment.
🟡 Changes recommended
Regional divider truncation violates the advertised CPU-overclock multipliers, and several updated records remain contradictory or incomplete.
5 open findings
Region-specific CPU multiplier uses incorrect integer division · New Replay/netplay note contradicts deterministic enhancement settings · New Platform status omits shipped wasm/mobile support · New Launch checklist omits Play-services flavor verification · New ROM provenance paragraph mismatches regenerated artifact · New
What changed in this PR
This release synchronizes AccuracyCoin, adds deterministic hardware options, expands DualSystem features, and updates v3.1.0 compatibility boundaries.
Changes:
- Raises AccuracyCoin to 146/146 and fixes DMC, sprite evaluation, PAL emphasis, and MMC3 behavior.
- Adds CPU overclocking, sprite-limit control, epoch fingerprinting, and DualSystem rewind/run-ahead.
- Bumps release metadata and updates tests, snapshots, plans, and documentation.
| File | Description |
|---|---|
AGENTS.md |
Updates release anchors. |
ARCHITECTURE.md |
Updates architecture release status. |
CHANGELOG.md |
Adds Bellwether release notes. |
Cargo.lock |
Updates workspace versions. |
Cargo.toml |
Bumps workspace to 3.1.0. |
OVERVIEW.md |
Updates release overview. |
README.md |
Updates release and accuracy claims. |
ROADMAP.md |
Advances release roadmap. |
SECURITY.md |
Updates supported release. |
SUPPORT.md |
Updates support metadata. |
VERSION-PLAN.md |
Records v3.1.0 release. |
android/app/build.gradle.kts |
Bumps Android version. |
crates/rustynes-android/src/gfx.rs |
Updates Android graphics behavior. |
crates/rustynes-core/src/bus.rs |
Implements overclock and DMA timing. |
crates/rustynes-core/src/bus_snapshot.rs |
Serializes new bus state. |
crates/rustynes-core/src/hardware_options.rs |
Adds deterministic hardware options. |
crates/rustynes-core/src/lib.rs |
Exports updated APIs and epoch. |
crates/rustynes-core/src/movie.rs |
Bumps movie format. |
crates/rustynes-core/src/nes.rs |
Applies new core options. |
crates/rustynes-core/src/vs_dualsystem.rs |
Extends cabinet state handling. |
crates/rustynes-cosim/Cargo.lock |
Synchronizes cosim version. |
crates/rustynes-cosim/Cargo.toml |
Bumps cosim crate version. |
crates/rustynes-frontend/src/app.rs |
Wires frontend options. |
crates/rustynes-frontend/src/config.rs |
Persists enhancement settings. |
crates/rustynes-frontend/src/crt.rs |
Updates display processing. |
crates/rustynes-frontend/src/debugger/settings_panel.rs |
Adds enhancement controls. |
crates/rustynes-frontend/src/emu.rs |
Integrates cabinet rewind/run-ahead. |
crates/rustynes-frontend/src/i18n.rs |
Adds option labels and help. |
crates/rustynes-frontend/src/netplay_ui.rs |
Updates netplay compatibility UI. |
crates/rustynes-frontend/src/ntsc.rs |
Adds differential phase handling. |
crates/rustynes-frontend/src/runahead.rs |
Supports cabinet run-ahead. |
crates/rustynes-frontend/src/shader_pass.rs |
Passes NTSC phase state. |
crates/rustynes-frontend/src/wasm.rs |
Updates browser option handling. |
crates/rustynes-gfx-shaders/src/signal_decode.wgsl |
Implements differential phase decoding. |
crates/rustynes-libretro/rustynes_libretro.info |
Bumps libretro display version. |
crates/rustynes-mappers/src/m004_mmc3.rs |
Adds alternate MMC3 behavior. |
crates/rustynes-mappers/src/m009_mmc2.rs |
Declares CHR-read behavior. |
crates/rustynes-mappers/src/m010_mmc4.rs |
Declares CHR-read behavior. |
crates/rustynes-mappers/src/m035_jy_asic.rs |
Declares CHR-read behavior. |
crates/rustynes-mappers/src/m096_bandai96.rs |
Declares CHR-read behavior. |
crates/rustynes-mappers/src/m163_nanjing.rs |
Declares CHR-read behavior. |
crates/rustynes-mappers/src/mapper.rs |
Adds mapper read-purity contract. |
crates/rustynes-netplay/src/message.rs |
Bumps netplay protocol. |
crates/rustynes-ppu/src/bus.rs |
Exposes safe extra-sprite reads. |
crates/rustynes-ppu/src/emphasis.rs |
Corrects PAL/Dendy emphasis. |
crates/rustynes-ppu/src/ppu.rs |
Fixes sprite evaluation and limits. |
crates/rustynes-ppu/src/snapshot.rs |
Bumps and extends PPU snapshots. |
crates/rustynes-test-harness/golden/epoch_fingerprint.tsv |
Updates release fingerprint. |
crates/rustynes-test-harness/src/accuracy_coin.rs |
Updates AccuracyCoin decoding. |
crates/rustynes-test-harness/src/accuracy_coin_catalog.rs |
Updates test catalog. |
crates/rustynes-test-harness/src/bin/accuracycoin_status.rs |
Reports updated battery results. |
crates/rustynes-test-harness/src/lib.rs |
Exposes new test utilities. |
crates/rustynes-test-harness/src/nes_runner.rs |
Supports updated test execution. |
crates/rustynes-test-harness/tests/accuracycoin.rs |
Gates 146 scored tests. |
crates/rustynes-test-harness/tests/accuracycoin_mirror.rs |
Updates mirror validation. |
crates/rustynes-test-harness/tests/cpu_overclock.rs |
Tests CPU overclocking. |
crates/rustynes-test-harness/tests/epoch_fingerprint.rs |
Adds epoch fingerprint gate. |
crates/rustynes-test-harness/tests/mmc3.rs |
Tests MMC3 revisions. |
crates/rustynes-test-harness/tests/roster_boards.rs |
Audits mapper purity declarations. |
crates/rustynes-test-harness/tests/snapshot_schema_audit.rs |
Updates snapshot schema checks. |
crates/rustynes-test-harness/tests/snapshots/external_coverage__mapper_146_Sachen_NINA_Millionaire_Sachen.snap |
Re-blesses PAL output. |
crates/rustynes-test-harness/tests/sprite_limit.rs |
Tests sprite-limit removal. |
crates/rustynes-test-harness/tests/vs_dualsystem_rewind.rs |
Tests cabinet rewind/run-ahead. |
docs/STATUS.md |
Updates authoritative release status. |
docs/accuracy-ledger.md |
Records accuracy work. |
docs/adr/0032-vs-dualsystem-desktop-presentation.md |
Amends DualSystem decisions. |
docs/adr/0044-movies-and-netplay-carry-the-emulation-options.md |
Documents added options. |
docs/adr/0045-a-core-timing-epoch-guards-movies-and-netplay.md |
Records epoch 3. |
docs/agents/ci-and-release.md |
Documents fingerprint release gate. |
docs/agents/review-bots.md |
Updates review guidance. |
docs/agents/tooling-traps.md |
Updates tooling notes. |
docs/apu-2a03.md |
Documents corrected DMA timing. |
docs/compatibility.md |
Updates platform compatibility. |
docs/dev/TESTING.md |
Updates verification guidance. |
docs/frontend.md |
Documents new frontend behavior. |
docs/ios-v1.9.9-readiness.md |
Updates iOS records. |
docs/libretro/UPSTREAM_SYNC.md |
Updates libretro synchronization status. |
docs/mappers.md |
Documents MMC3 revision behavior. |
docs/mobile-v2.9.3-run-sheet.md |
Folds in Android verification. |
docs/nesdev-hardware-emulation-checklist.md |
Updates hardware checklist. |
docs/netplay-webrtc.md |
Documents protocol 7. |
docs/performance.md |
Updates performance records. |
docs/ppu-2c02.md |
Documents PPU fixes and options. |
docs/scheduler.md |
Documents overclock scheduling. |
docs/user-guide/compatibility.md |
Updates user-facing compatibility. |
ios/project.yml |
Bumps iOS release version. |
ref-docs/2026-10-07-mister-core-contribution-requirements-update.md |
Records MiSTer requirements. |
scripts/accuracycoin-build/derive_indices.py |
Supports compressed source rows. |
scripts/accuracycoin-build/extract_catalog.py |
Extracts updated catalog format. |
tests/roms/AccuracyCoin/README.md |
Documents upstream resynchronization. |
tests/roms/AccuracyCoin/SOURCE_CATALOG.tsv |
Adds new AccuracyCoin tests. |
tests/roms/AccuracyCoin/mirror/AccuracyCoin-mirror.nes |
Updates mirrored test ROM. |
tests/roms/AccuracyCoin/mirror/README.md |
Records mirror provenance. |
tests/roms/AccuracyCoin/sub-tests/BUILD-PROVENANCE.tsv |
Records sub-test builds. |
tests/roms/AccuracyCoin/sub-tests/dmc-reload-timing.nes |
Adds DMC timing sub-test. |
tests/roms/AccuracyCoin/sub-tests/sprite-eval-misaligned-oam.nes |
Rebuilds sprite sub-test. |
tests/roms/LICENSES.md |
Updates AccuracyCoin licensing record. |
tests/roms/accuracycoin/AccuracyCoin.nes |
Updates runtime test ROM. |
tests/roms/accuracycoin/RUNTIME.md |
Records runtime provenance and results. |
to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md |
Updates completed carryovers. |
to-dos/README.md |
Updates delivered release summary. |
to-dos/ROADMAP.md |
Advances planning state. |
to-dos/mister/IMPLEMENTATION_PLAN.md |
Records submission preparation. |
to-dos/mister/TASKS.md |
Updates MiSTer task status. |
to-dos/mister/contribution-checklist.md |
Revises contribution checklist. |
to-dos/plans/v2.0.x-mobile-finalization-plan.md |
Redirects verification checklist. |
to-dos/plans/v3.1.0-plan.md |
Records completed release plan. |
to-dos/v1.8.x-on-device-verification.md |
Removes superseded checklist. |
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
CI's test-roms job on the release PR (#594, run 37747083839) failed all five cpu_overclock tests: "read .../tests/roms/nes-test-roms/apu_mixer/ square.nes: No such file or directory". `tests/roms/nes-test-roms/` is GITIGNORED (.gitignore:160), a local aggregate; only a few of its files are force-tracked. Both tests passed locally for exactly that reason, and cargo stopped at the first failing binary, so epoch_fingerprint (which reads four ROMs from the same place) never ran in CI at all. - cpu_overclock: ROM -> blargg/apu_mixer/triangle.nes, AUDIBLE_ROM -> blargg/apu_mixer/square.nes (cmp: byte-identical to the aggregate copy). - epoch_fingerprint's panel: the aggregate's 4-scanline_timing and flowing_palette -> the tracked blargg/mmc3_test_2/4-scanline_timing.nes and assorted/flowing_palette.nes (both cmp-identical; their rows keep the same four hashes, only the path column moves, which is the check that the swap changed nothing); ny2011 and spritecans have no tracked copy and become blargg/apu_mixer/triangle.nes (a continuous tone through the mixer) and blargg/sprite_overflow_tests/3.Timing.nes (evaluation and overflow timing). The table was re-blessed with last_release_epoch set to 2 for the bless only, then restored to 3: a panel change is not a behaviour change, and the gate cannot tell the two apart, so its refusal to bless at the released epoch had to be stepped around deliberately. The plain run at 3 passes; cpu_overclock 6/6. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
The v3.1.0 CPU overclock divided the region's CPU cycle length by the multiplier with integer division: `cpu_div_effective = cpu_div_cached / k`. That is exact only when the divider is a multiple of k. NTSC's 12 master clocks divide by 2, 3 and 4; PAL's 16 does not divide by 3 and Dendy's 15 does not divide by 2 or 4. So PAL x3 ran 16/3 = 5-clock cycles, x3.2, and Dendy x4 ran 15/4 = 3-clock cycles, x5. The stock-step bookkeeping (which CPU cycles also clock the APU, the DMC and the mapper hooks) was a separate master-clock debt, so sound and timers stayed right while the CPU ran too fast: the option said x3 and delivered x3.2. Reported by Copilot on the release PR (#594, `bus.rs:1424`); the report was correct. The fix replaces the debt with an exact schedule. The k overclocked cycles that make up one stock cycle have lengths overclock_cycle_len(div, k, phase) = ((phase+1)*div)/k - (phase*div)/k which sum to exactly `div` over phase 0..k-1 (a telescoping sum), and differ by at most one master clock: PAL x3 is 5, 5, 6; Dendy x4 is 3, 4, 4, 4. The last cycle of each group, `phase + 1 == k`, is the stock step, so the stock step lands exactly on the stock cycle boundary rather than drifting against it. `overclock_debt` becomes `overclock_phase` (0..k-1) in the BUS section 3 snapshot; restore clamps an out-of-range phase to k-1 and recomputes the cycle length from it, so a crafted state cannot stall the stock step. x1 is untouched: phase stays 0 and the length is `div`. Two details worth keeping: - The phase advances at the END of a cycle, in `cpu_clock_apu_dmc`, so the length a cycle runs with is chosen before its first half and the DMC end-of-cycle half still belongs to the stock step its start half ran in. - `overclock_cycle_len` is a const fn, so `.min()` is unavailable in the restore clamp; it is an if-expression for that reason. Verified: - New test `the_multiplier_is_exact_on_pal_and_dendy_too` counts CPU cycles over 60 frames at x1 and at x2, x3 and x4 on NTSC, PAL and Dendy, and requires each ratio within 0.1% of k; the defect it pins was 6.7% (PAL x3) and 25% (Dendy x4). Mutant (revert to `div / k`): fails with "PAL x3: 6383520 CPU cycles against 1994850 stock is x3.2000, not x3". - cpu_overclock 7/7, snapshot_schema_audit 9/9, epoch_fingerprint 1/1 (x1 is byte-identical; the panel runs at x1), rustynes-core lib 253/253. - fmt; clippy -D warnings for the workspace and the scripting, hd-pack, retroachievements and full feature sets. The overclock is new in this unreleased version, so the snapshot field's meaning changes without a further section bump: no released state carries it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Copilot's review of #594 found four statements that this release made false or that its own fold dropped. Each was checked against the tree before it was changed. - Settings > Enhancements note (`i18n.rs`, EN and ES): it said enhancement modes are NEVER applied during TAS replay or netplay. v3.1.0 made two of them (the CPU overclock and the sprite-limit switch) part of HardwareOptions, so movies record them and netplay peers must match, while the scanline overclock is still ignored there. The note now says accuracy tests never use them and points at each option's own line, which already states its movie and netplay behaviour. - accuracy-ledger, Vs. DualSystem row: "wasm/mobile deferred". The mobile bridge has carried the cabinet since v2.9.7 (a screen switch, not two screens). The row now says so, and that its on-device rows (run sheet T8-T10) are NOT RUN with the Swift half uncompiled. A first draft of this edit called Android "verified on the emulator" and the web build "one screen"; T8 is NOT RUN and nothing was measured about the web build, so both claims were dropped before commit. - mobile run sheet: folding the v1.8.x checklist (638d5d3) kept the `foss` flavor check (G17) and lost the requirement that the `play` flavor be verified too. G18 restores it: `installPlayDebug`, boot a ROM, open Settings; the Play-Services surface (`android/app/src/play/`: Play Games, cloud save, Cast, in-app updates) appears, and a device without Play Services still boots and plays. - AccuracyCoin BUILD-PROVENANCE: the v3.1.0 paragraph described the frame-211 settle of the LEGACY `sprite-eval-misaligned-oam` build, which 3df59e8 replaced with a build from upstream f5f41dc2 (frame 175, 18/6). The paragraph now says the ROM was rebuilt and labels 211 as the replaced build's figure. The data rows are unchanged; accuracycoin_subtest_provenance passes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
… list Antigravity's review of #594 pointed at `magic == OLDER_SYNC_MAGICS[2]`: the index means "protocol 6", and that meaning lives only in the order of the array. Four places relied on the same convention ([0] = protocol 4, [1] = protocol 5, [2] = protocol 6): the epoch-bearing refusal in `check_sync`, both legacy-length decode arms in `from_bytes`, and two tests. Adding a protocol at the front of the list, or sorting it, would have re-pointed every one of them with no compile error, and the decode arms would then have accepted a 68-byte Sync under protocol 4's magic. Each magic is now an associated constant (`PROTOCOL_4_SYNC_MAGIC`, `PROTOCOL_5_SYNC_MAGIC`, `PROTOCOL_6_SYNC_MAGIC`, each documented with its version range and payload shape), `OLDER_SYNC_MAGICS` is built from them and kept for the membership test, and no indexed use remains (`rg 'OLDER_SYNC_MAGICS\['` finds nothing). The wire values are unchanged, so this is not a protocol change. Also from the same review: the Settings combo for the CPU overclock showed `config.enhancements.cpu_overclock.max(1)`, so a hand-edited config of 9 displayed "x9" while the core, which clamps in `Nes::set_cpu_overclock`, ran x4. The combo now clamps to 1..=MAX_CPU_OVERCLOCK the way the core does. The review's broader suggestion, validating the field at deserialization, was not taken: the frontend clamps at the use site (as `Overscan::clamped` does), and nothing unclamped reaches a record. HardwareOptions captures `nes.cpu_overclock()`, already clamped, and the `.rnm` reader refuses 0 or anything above 4. The accuracy-ledger Vs. DualSystem row also gains the browser half of Copilot's finding: since v2.9.7 the wasm-winit build runs the cabinet and presents both screens, while the wasm-canvas embed runs the main console only (CHANGELOG [2.9.7]). Verified: rustynes-netplay lib 102/102 and its integration tests; fmt; clippy -D warnings on rustynes-netplay and rustynes-frontend. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
|
Antigravity review, answered:
CodeRabbit skipped this PR (101 files against a limit of 100), so it is reviewing review-only slices #595 (code) and #596 (docs and data), which will be closed unmerged. |
…on restore Two defects CodeRabbit found in the release's code (review slice #595 of #594). Both reproduced red before the fix. 1. The CPU overclock and the sprite-limit option did nothing on a Vs. DualSystem cabinet. `produce_frame` pushes both into the console each frame; `produce_dual_frame` pushed neither, and `configure_console` deliberately leaves them out, so with a cabinet loaded both settings were shown, saved and inert. `produce_dual_frame` now applies them to the main AND the sub console. That is coherent for the overclock because the cabinet locksteps its consoles by CPU cycle count (`VsDualSystem::run_frame` keeps `main.cycle()` within 5 of `sub.cycle()`), so the same multiplier on both keeps the gap meaningful; its 150,000-cycle frame guard still covers x4 (PAL x4 is about 133,000). The single path's movie-idle condition has no counterpart: no movie or netplay session runs on a cabinet (ADR 0032). The extra-scanline overclock is still not applied on a cabinet; that predates v3.1.0 and is left as it was. Test `a_cabinet_runs_both_consoles_with_the_enhancement_options` (emu.rs): failed before the fix; mutant dropping the sub console's call fails with "sub console overclock". 2. Restoring a state could change the running MMC3 IRQ revision behind the setting's back. The MMC3's MAP section carries its live `revision`, and `SystemBus::restore` installed it without re-applying the configured override, so a state saved under the alternate override and loaded with none kept running the alternate chip while `mmc3_revision_override()` reported `None`, and the reverse. Movies, rewind, run-ahead and rollback all restore through this path. Restore now re-applies the configured override after MAP (`None` returns the board to its header's revision; a no-op on every other board): the revision is configuration, as the override's own docs already said. Test `a_restore_keeps_the_configured_mmc3_revision` (mmc3.rs) checks both directions; it failed before the fix with "a state saved under the override must not bring it back: MMC3 (Nec)". MMC3 suite 21/21. Verified: rustynes-core lib 253/253, rustynes-frontend lib 684/684, mmc3 21/21; fmt; clippy -D warnings for the workspace, the full, retroachievements and scripting+hd-pack feature sets and both wasm32 configurations; rustdoc -D warnings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…blind spot From CodeRabbit's review of slice #595 (of #594): two gate defects and four stale docs. Epoch fingerprint gate. A probe added to the panel after a release has no row in the table, and the gate counted every row it could not find as moved output, which may only happen under a raised EMULATION_EPOCH. So adding a probe at the released epoch was refused even though the core had not changed. This is not hypothetical: 5e99b07, which repointed two probes at tracked ROMs, had to lower `last_release_epoch` by hand to 2 to bless the table and then restore it. `classify` now separates the three cases by (rom, frames): an exact row is Unchanged; the same probe with different output is Changed (the epoch rule applies); no row for the probe is New (blessable at any epoch, and an unblessed new probe still fails with its own message). Unit test `a_new_probe_is_not_a_moved_output`; mutant forcing every unmatched row to Changed fails it with `left: Changed, right: New`. AccuracyCoin catalog extractor. Its completeness check only recognised a row macro whose first argument is a quoted string, so a future token-first row (`tblf3 str_Name, $FF, result_X, ...`) would have been skipped by both the parser and the check, and every later index in its suite would shift, which builds sub-test ROMs that run the wrong test. RE_ANY_ROW now keys on the macro family (`table`, `tbl*`), not on the argument; the family is named because 6502 mnemonics starting with `t` (`tax`, `tay`, `tsx`, `txa`, `txs`, `tya`) must not match. New self-test case (9 cases). On the pinned upstream source (f5f41dc2) the catalog is byte-identical to the committed SOURCE_CATALOG.tsv and derive_indices validates every recorded mapping, with output identical to HEAD's script. (The ~/.cache source checkout is the older 46199ae4, which lacks suite 14 test 6 and fails validation under both versions; it is not what the catalog is built from.) Docs corrected against the code: - `Nes::set_cpu_overclock` still described the integer division 11c96b9 removed ("x3 on PAL is x3.2"); it now gives the exact split. - `Nes::set_mmc3_revision_override` said the alternate revision asserts only on a 1 -> 0 decrement; since v3.1.0 it also asserts on a `$C001` reload to 0, even with the counter already 0 (`clock_irq` path 1). - snapshot_schema_audit described `cpu_div_effective` as `cpu_div_cached / cpu_overclock`. - `RamResultSummary.total` said 151; it is `scored_len()`, 146. - `fetch_extra_sprites` now says its raw `oam` walk bypasses the OAM-decay read hook on purpose, so the display option cannot refresh a DRAM row. Verified: epoch_fingerprint 2/2, snapshot_schema_audit 9/9, extract_catalog self-test 9/9; fmt; clippy -D warnings (workspace and feature sets); rustdoc -D warnings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Brings this review-only slice up to #594 at 8af50dc; every path in the slice equals the release head (git diff --quiet checked). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Brings this review-only slice up to #594 at 8af50dc; every path in the slice equals the release head (git diff --quiet checked). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Antigravity's review of #594 (an archived round) reported as BLOCKING that returning the CPU overclock to x1 hangs the emulator: under the overclock the APU runs on its own stock-rate counter (`apu_cycle`) while the CPU's `cycle` runs k times faster, so after a long session they are tens of millions of cycles apart, and x1 hands the APU `cycle` again. The review read that as a catch-up loop that stalls the thread and floods the audio buffer. It is not one, and this commit shows it rather than arguing it. `Apu::set_canonical_cycle` is a plain assignment; the APU uses the counter only for its put/get parity and a pending IRQ-flag-clear deadline, never as a distance to cover. New test `switching_back_to_stock_after_a_long_overclock_is_an_ordinary_frame` runs 600 frames at x4 (about 54 million cycles of gap), switches to x1, and requires each of the next ten frames to produce the stock CPU-cycle and audio-sample counts (within 2). It passes. The mutant is the defect the review described: a loop in `cpu_clock` that walks the APU through the whole gap at x1. It fails the test with "frame 0 after switching back: 1319359 audio samples, stock is 733". What the switch does cost, recorded rather than fixed: the counter's jump can flip the APU's put/get parity once at the switch (switching ON keeps it, by re-basing `apu_cycle` on `cycle`). That is a one-cycle phase change at a moment no real console has, deterministic, and of the same kind as changing the option at all. Also from the same review: the `HardwareOptions::cpu_overclock` field doc now says what happens outside 1..=MAX_CPU_OVERCLOCK (decoding refuses, applying clamps). Not taken: making the field private (the struct is `#[non_exhaustive]` with public fields by design since v3.0.0), and the nitpick that the snapshot's phase error omits the value (it already reads "CPU-overclock phase {overclock_phase} out of range"). Verified: cpu_overclock 8/8; fmt; clippy -D warnings on rustynes-core and the harness with test-roms; rustdoc -D warnings on rustynes-core. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Brings this review-only slice up to #594 at c749c43; every path in the slice equals the release head (git diff --quiet checked). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
|
Antigravity, later rounds, answered:
|
CodeRabbit's review of slice #596 (the docs half of #594) found seventeen statements the release left stale. Each was checked against the tree; sixteen were real and are fixed here, one was a cross-slice misread (answered on the PR). The most serious was not prose at all: - **CHANGELOG [3.1.0] shipped `- The MiSTer core: LADDER-FILL.`**, a drafting placeholder, in the release PR. It now gives the ladder result (on-die 212 / 0 / 1, off-die 213 / 0 / 1, one frozen-tree run each; the core matches all 146 AccuracyCoin entries). Nothing caught it but a reviewer, so `release_notes_render_audit.rs` gains `no_fill_placeholder_reaches_a_release_body`: every `.github/release-notes/*.md` and the CHANGELOG must contain no upper-case `WORD-FILL` token. Its first run caught the one other placeholder in the tree, `BITSTREAM-FILL` in the still-uncommitted v3.1.0 notes, which stays until the bitstreams are measured. A scanner unit test pins what counts (`LADDER-FILL` yes; `pre-fill`, `Fill-in`, `X-FILLER`, a bare `-FILL` no). The rest, by file: - README: the AccuracyCoin badge read 144/144 under the 146/146 headline. - STATUS, compatibility.md: 144/144 (and 141/141) stated without saying they included the masked `Misaligned OAM behavior` failure; now bounded to their releases and marked overstated, with 146/146 since v3.1.0. - ROADMAP: v3.0.1 still "in progress" under a v3.1.0 current-release anchor. - SECURITY: 3.0.x was "the current line"; 3.1.x is, and 3.0.x is Partial. - to-dos/README: the release line ended at v3.0.0 as "the current release". - v3.1.0 plan: row 2 gave `last_release_epoch` 2 without saying the cut set it to 3; row 10 said G1-G17 after the review added G18; DOC-06 said libretro/docs#1215 open. - libretro UPSTREAM_SYNC: docs#1215 MERGED 2026-10-08 08:55 UTC (`4a0c09f236`, checked with `gh pr view`), not open. - docs/agents/ci-and-release: named `.rnm` format 5 / protocol 6 as current; format 6 / protocol 7 (`"RNE7"`) since v3.1.0, and a probe newly added to the epoch panel now blesses at any epoch (8af50dc). - mappers.md gotcha 2 called the Sharp default "MMC3A" and put MMC3B on submapper 1 (that is the MMC6; the alternate revision is submapper 4). It now states the rule `clock_irq` implements. The open-questions answer names the public owner, `Nes::set_mmc3_revision_override`. - ppu-2c02.md: "the fourth byte copied is the X position" is false for a walk starting at m = 3, where the fourth byte is slot n+1's attribute byte; it is evaluated AS X, which is what the rule needs to say. - mobile run sheet: a device-vs-host frame mismatch is attributed to the mobile integration only given the same ROM, initial state and inputs. - DEFERRED: the CPU-multiplier overclock item still `[ ]` and scheduled for v3.1.0; now `[x]` with what shipped. The old "141/141 on the shipping core" sentence is marked historical. Not changed: tests/roms/AccuracyCoin/README.md's claim that the extractor aborts on an unreadable row-shaped line and on an empty suite is accurate. The review read a version of `parse()` that slice did not carry; `extract_catalog.py` compares every `RE_ANY_ROW` candidate against the rows parsed and rejects a suite that yields none. Verified: release_notes_render_audit 3/4 here, the fourth failing only on the uncommitted notes file as intended; markdownlint via pre-commit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Brings this review-only slice up to #594 at c95a8d1; every path in the slice equals the release head (git diff --quiet checked). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Brings this review-only slice up to #594 at c95a8d1; every path in the slice equals the release head (git diff --quiet checked). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
The overclock bookkeeping became a phase in 11c96b9, but three comments still described a master-clock debt: the BUS decoder's range check ("A debt is below one stock CPU cycle (16 master clocks on PAL ...)"), the snapshot-tail byte count in a bus test, and docs/scheduler.md. The check itself was already right (`overclock_phase >= MAX_CPU_OVERCLOCK`); only the words described the removed model. Found by Antigravity's review of #594. Comment and doc text only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
|
Antigravity, round at 10:49 UTC:
|
The release body, written from the branch log of both repositories. It states the breaking changes (EMULATION_EPOCH 3, BUS section 3, PPU_SNAPSHOT_VERSION 13, .rnm format 6, netplay protocol 7), that every AccuracyCoin 100% before v3.1.0 included the masked Misaligned OAM behavior failure, and the release-candidate bitstream pair: seed 1 from eight at 261008, on-die 4afffd23 (+0.255 / +0.100 ns) and off-die 7b48198c (+0.188 / +0.001 ns; SDRAM read +0.449 / +1.184 ns), each byte-identical across two clean compiles, neither run on hardware. The test count is set after the final run at the release head. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
`cargo test --workspace --release --no-fail-fast --features test-roms` at 17823b6: 3,269 passed, 0 failed, 11 ignored. The 3,263 these files gave was measured at 5f7eac6. Since then: four tests from the review rounds (the cabinet options, the MMC3 revision across a restore, the epoch classifier, switching the overclock back to x1) made 3,267 at c749c43, and the two placeholder-gate tests make 3,269. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Brings this review-only slice up to #594 at 7274754; every path in the slice equals the release head (git diff --quiet checked). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Brings this review-only slice up to #594 at 7274754; every path in the slice equals the release head (git diff --quiet checked). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
|
Antigravity, round at 11:56 UTC:
|


v3.1.0 "Bellwether"
The first release of the v3.1 to v4.0 line (plan). Ten commits.
What it does
f5f41dc2: 146/146. The new ROM found two defects, both fixed red first: a DMC load DMA a write refuses took 3 cycles instead of 4, and misaligned sprite evaluation had three wrong rules. It also showed that every earlier 144/144 was overstated: the old ROM'sMisaligned OAM behaviorfail path fell through to a pass. That is stated in the CHANGELOG, README, STATUS and the docs that claimed the score.T-EPOCH-FINGERPRINT): output that moves without an epoch rise fails.HardwareOptions, so movies and netplay carry them.$C001reload to 0; differential phase in the NTSC decode.Breaking
EMULATION_EPOCH3, BUS section 3,PPU_SNAPSHOT_VERSION13,.rnmformat 6, netplay protocol 7: v3.0.1 save states, movies and netplay peers are refused, each with a reason.Verification
cargo test --workspace --features test-roms --release: 3,269 passed, 0 failed, 11 ignored.external_real_games60/0,external_extended137/0,external_coverage6/0 over 744 staged ROMs. One baseline moved (Millionaire, a PAL game; the PAL emphasis fix), attributed by running it alone on each v3.1.0 commit, and re-blessed.-D warningsfor the workspace and the scripting / hd-pack / retroachievements / full feature sets; both wasm32 builds; rustdoc-D warnings; theno_stdbuild; markdownlint; the eleven release audits.The MiSTer sibling
Its
release/v3.1.0PR follows: the core matches the re-synced battery on all 146 entries, RTL-1 (a$2006copy on every dot) matches the emulator, and both ladders are green at a pin on this branch (on-die 212 / 0 / 1, off-die 213 / 0 / 1). Its bitstream pair is built: seed 1 of eight at 261008, on-die4afffd23(+0.255 / +0.100 ns) and off-die7b48198c(+0.188 / +0.001 ns), each byte-identical across two clean compiles (doublegate/RustyNES_MiSTer#57). The release notes are.github/release-notes/v3.1.0.md.CodeRabbit skips a PR over 100 files, so it reviewed this one as two review-only slices, #595 (code) and #596 (docs and data), both kept equal to this head and closed unmerged. All 27 of its findings and every Copilot and Antigravity finding are answered.
No hardware has run any bitstream.
🤖 Generated with Claude Code
https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj