From 99ca8aa5d03f4c20581e6a8f174a2d416ce7ce4f Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Thu, 8 Oct 2026 04:58:10 -0400 Subject: [PATCH 1/4] review(v3.1.0): slice B, docs and test data (do not merge) Review-only slice of #594 for CodeRabbit, which skips PRs over 100 files. Every doc and test-data path the release changes (54), each equal to the release head 5f7eac66. Closed unmerged. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- AGENTS.md | 4 +- ARCHITECTURE.md | 2 +- CHANGELOG.md | 151 ++++++++++++++++++ OVERVIEW.md | 10 +- README.md | 12 +- ROADMAP.md | 4 +- SECURITY.md | 2 +- SUPPORT.md | 4 +- VERSION-PLAN.md | 8 +- .../rustynes-libretro/rustynes_libretro.info | 2 +- .../golden/epoch_fingerprint.tsv | 15 ++ ...er_146_Sachen_NINA_Millionaire_Sachen.snap | 2 +- docs/STATUS.md | 12 +- docs/accuracy-ledger.md | 4 +- ...0032-vs-dualsystem-desktop-presentation.md | 13 ++ ...and-netplay-carry-the-emulation-options.md | 18 +++ ...-timing-epoch-guards-movies-and-netplay.md | 16 ++ docs/agents/ci-and-release.md | 2 +- docs/agents/review-bots.md | 2 +- docs/agents/tooling-traps.md | 1 + docs/apu-2a03.md | 19 +++ docs/compatibility.md | 41 +++-- docs/dev/TESTING.md | 2 +- docs/frontend.md | 58 +++++-- docs/ios-v1.9.9-readiness.md | 6 + docs/libretro/UPSTREAM_SYNC.md | 13 +- docs/mappers.md | 38 ++++- docs/mobile-v2.9.3-run-sheet.md | 36 +++++ docs/nesdev-hardware-emulation-checklist.md | 20 +-- docs/netplay-webrtc.md | 3 +- docs/performance.md | 19 +++ docs/ppu-2c02.md | 70 +++++++- docs/scheduler.md | 37 ++++- docs/user-guide/compatibility.md | 5 +- ...r-core-contribution-requirements-update.md | 125 +++++++++++++++ tests/roms/AccuracyCoin/README.md | 21 ++- tests/roms/AccuracyCoin/SOURCE_CATALOG.tsv | 18 ++- .../mirror/AccuracyCoin-mirror.nes | Bin 40976 -> 40976 bytes tests/roms/AccuracyCoin/mirror/README.md | 10 +- .../sub-tests/BUILD-PROVENANCE.tsv | 27 +++- .../sub-tests/dmc-reload-timing.nes | Bin 0 -> 40976 bytes .../sub-tests/sprite-eval-misaligned-oam.nes | Bin 40976 -> 40976 bytes tests/roms/LICENSES.md | 2 +- tests/roms/accuracycoin/AccuracyCoin.nes | Bin 40976 -> 40976 bytes tests/roms/accuracycoin/RUNTIME.md | 24 ++- to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md | 15 ++ to-dos/README.md | 4 +- to-dos/ROADMAP.md | 28 ++-- to-dos/mister/IMPLEMENTATION_PLAN.md | 2 +- to-dos/mister/TASKS.md | 18 ++- to-dos/mister/contribution-checklist.md | 62 +++++-- .../plans/v2.0.x-mobile-finalization-plan.md | 6 +- to-dos/plans/v3.1.0-plan.md | 30 +++- to-dos/v1.8.x-on-device-verification.md | 101 ------------ 54 files changed, 899 insertions(+), 245 deletions(-) create mode 100644 crates/rustynes-test-harness/golden/epoch_fingerprint.tsv create mode 100644 ref-docs/2026-10-07-mister-core-contribution-requirements-update.md create mode 100644 tests/roms/AccuracyCoin/sub-tests/dmc-reload-timing.nes delete mode 100644 to-dos/v1.8.x-on-device-verification.md diff --git a/AGENTS.md b/AGENTS.md index e6fd5ca59..e4d74e11a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,7 +44,7 @@ Enforcement lives alongside the prose: `/ref-proj/` is gitignored/`.dockerignore RustyNES is a cycle-accurate Nintendo Entertainment System emulator written in pure Rust. The accuracy bar is Mesen2 / higan / ares: tight lockstep scheduling at PPU-dot resolution on a master-clock-precise timebase, sub-instruction PPU events visible to subsequent CPU code, and a lookup-table non-linear audio mixer with band-limited synthesis. The frontend is pure Rust (`winit` + `wgpu` + `cpal` + `egui`). -**Current release: v3.0.1 "Mortar"** (2026-10-07) — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** (2026-10-06) — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core ([ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md)). **No hardware has run any MiSTer bitstream**: hardware verification moves to a later v3.x release (ADR 0043 supersedes [ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)'s hardware-verified v3.0.0), and the SDRAM pin constraints stay provisional until the SuperStation One's memory is read. Suite counts, mapper matrix and per-release detail live in `docs/STATUS.md`, `CHANGELOG.md` and the GitHub releases; they are deliberately not duplicated here. The earlier release-by-release narrative and other condensed paragraphs are archived verbatim in `docs/history/AGENTS-archive.md`. +**Current release: v3.1.0 "Bellwether"** (2026-10-08) — the AccuracyCoin re-sync and an honest 146/146, the CPU overclock and the sprite-limit option in movies and netplay, PAL emphasis, the alternate MMC3, rewind and run-ahead on the Vs. cabinet, and the MiSTer core matched to all of it. Built on **v3.0.1 "Mortar"** (2026-10-07) — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** (2026-10-06) — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core ([ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md)). **No hardware has run any MiSTer bitstream**: hardware verification moves to a later v3.x release (ADR 0043 supersedes [ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)'s hardware-verified v3.0.0), and the SDRAM pin constraints stay provisional until the SuperStation One's memory is read. Suite counts, mapper matrix and per-release detail live in `docs/STATUS.md`, `CHANGELOG.md` and the GitHub releases; they are deliberately not duplicated here. The earlier release-by-release narrative and other condensed paragraphs are archived verbatim in `docs/history/AGENTS-archive.md`. - **Native Android app** — the **v1.8.0 → v1.8.9 "Android"** train (`crates/rustynes-mobile` UniFFI bridge + `crates/rustynes-android` JNI/NDK host + a Jetpack Compose app, ADR 0024): full on-device emulation, multi-touch + P1–P4 hardware controllers, wgpu `SurfaceView` rendering + the shared WGSL shader stack, save-states / battery SRAM, Lua, RetroAchievements, direct-IP + CGNAT/TURN room-code netplay, a box-art ROM library, and platform polish (adaptive / foldable / TV, Material You, capture / PiP / home-screen widget). Distributed as **GitHub-Releases sideload**; a free store listing is an unversioned later step with no monetization (ADR 0035, which superseded ADR 0025's v2.3.0 date; the `foss`/`play` flavour split remains). - **Native iOS / iPadOS app** — the **v1.9.0 → v1.9.9 "iOS" TestFlight train** (`crates/rustynes-ios` Metal + CoreAudio shim reusing `rustynes-mobile` verbatim → UniFFI-generated Swift, ADR 0026): a native SwiftUI shell over wgpu→Metal, multi-touch + GameController, the shader stack, TAS / HD-pack / palettes / per-game DB, Lua + RetroAchievements, LAN + room-code netplay, CloudKit save-state sync, accessibility + EN/ES i18n + ReplayKit + Game Center, and the v1.9.9 creator tools (Cheats, a FOSS-gated read-only debugger, a touch TAStudio piano-roll, foreign movie import, a host audio-depth DSP). Ships to **TestFlight**; a free App Store listing is an unversioned later step with no monetization (ADR 0035, which superseded ADR 0027's v2.3.0 date). Mobile ROM loading was iNES / NES 2.0 only until v2.9.7, which adds FDS (host-supplied BIOS), NSF and the Vs. DualSystem cabinet to the bridge (the Swift half uncompiled; run-sheet rows T1-T12). Readiness record: `docs/ios-v1.9.9-readiness.md`. @@ -236,7 +236,7 @@ that is a reason to add a tenth — not a reason to grow this section back. - `ref-docs/` is immutable. Research updates go in dated supplemental files. - ADRs go in `docs/adr/` (Michael Nygard format). - `rustynes-core` re-exports the public types from the chip crates; downstream consumers (`rustynes-frontend`, `rustynes-test-harness`) should depend on `rustynes-core` rather than the chip crates directly. -- When relabeling old engine "v2.x" narrative for users, present it as upstream lineage/history — **never as a current RustyNES release version.** The current release is **v3.0.1 "Mortar"** (2026-10-07). **Never claim any version *later* than v3.0.1 is released.** Two distinct "v2.0"s exist and must not be conflated: the **engine-lineage v2.0** master-clock work shipped as the **v1.0.0** production core (2026-06-13) and was the only scheduler through v1.10.0; RustyNES's own **v2.0.0 "Timebase"** (2026-07-03) is a different milestone that REPLACES that dot-lockstep scheduler with the one-clock, every-cycle-bus-access model (ADR 0002 / 0028 / 0029) and broke byte-identity and save-state compatibility, by design (v2.9.8 later broke save, movie and API compatibility again, ahead of v3.0.0). The per-release narrative that used to be inlined here is in `CHANGELOG.md`, the per-release notes under `.github/release-notes/`, and the published GitHub releases — three places that are maintained, against one copy here that was not. +- When relabeling old engine "v2.x" narrative for users, present it as upstream lineage/history — **never as a current RustyNES release version.** The current release is **v3.1.0 "Bellwether"** (2026-10-08). **Never claim any version *later* than v3.1.0 is released.** Two distinct "v2.0"s exist and must not be conflated: the **engine-lineage v2.0** master-clock work shipped as the **v1.0.0** production core (2026-06-13) and was the only scheduler through v1.10.0; RustyNES's own **v2.0.0 "Timebase"** (2026-07-03) is a different milestone that REPLACES that dot-lockstep scheduler with the one-clock, every-cycle-bus-access model (ADR 0002 / 0028 / 0029) and broke byte-identity and save-state compatibility, by design (v2.9.8 later broke save, movie and API compatibility again, ahead of v3.0.0). The per-release narrative that used to be inlined here is in `CHANGELOG.md`, the per-release notes under `.github/release-notes/`, and the published GitHub releases — three places that are maintained, against one copy here that was not. - **Forward plans + roadmap live in `to-dos/`.** `to-dos/ROADMAP.md` (updated in #129) is the planning entry point and frames the release line + "the path to v2.0.0 and beyond"; `to-dos/plans/` holds the per-release plan docs (through `v1.7.0-forge-plan.md` on `main`, plus the staged-forward `v1.8.0-android-plan.md` / `v1.9.0-ios-plan.md` / `v2.0.0-master-clock-plan.md`) + the `to-dos/plans/engine-lineage/` history archive + a `to-dos/plans/research/` reference-mining archive. - The v1.0.0 release + GitHub Pages/CI + post-release record is in `docs/v1.0.0-synthesis-handoff-2026-06-13.md` — read it before touching CI, Pages, or release tooling. Full per-release history is in `CHANGELOG.md`. - **Markdownlint is a CI gate** (pre-commit, pinned `markdownlint-cli v0.49.1`). The pin was v0.39.0 until the v2.6.3 dependency refresh, held because the newer local binary reported rules the pin lacked — chiefly **MD060** (`table-column-style`), which was therefore NOT gated. That is now measured and resolved: MD060's inferred default reads this corpus as style `compact` and reports **1,936 findings across 122 files** and nothing else, so `.markdownlint.json` pins `MD060` to the style actually in use (`leading_and_trailing`), which measures **zero** and rewrites no document. It IS a gate now. Still verify with `pre-commit run markdownlint --all-files` rather than the bare binary — the pin and the local build can drift apart again. `.markdownlint.json` also keeps `MD013`/`MD033`/`MD041` disabled by design (long technical tables, the README HTML banner/``, the HTML-led README). `.markdownlintignore` exempts `ref-docs/`, `ref-proj/` (the reference-emulator clone, now removed from disk but kept in the ignore lists as a firewall guard so it can never re-enter the tree — see the MOST IMPORTANT RULE section above), the vendored `tricnes/` + upstream READMEs, and the frozen `docs/archive/` + `to-dos/archive/` trees — don't lint or reformat those. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c23c54915..7a9984cee 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,7 +2,7 @@ **Document Version:** 2.1.0 **Last Updated:** 2026-10-07 -**Applies to:** RustyNES v3.0.1 (the scheduling model is v2.0.0 "Timebase" onward) +**Applies to:** RustyNES v3.1.0 (the scheduling model is v2.0.0 "Timebase" onward) This document fixes the high-level architecture of RustyNES. The per-subsystem specs under `docs/` (`cpu-6502.md`, `ppu-2c02.md`, `apu-2a03.md`, `mappers.md`, `scheduler.md`) take these decisions as given and elaborate one chip each. After reading this you should know the workspace shape, the scheduling model, the public boundary, and the load-bearing invariants. The canonical, always-current architecture spec is [`docs/architecture.md`](docs/architecture.md); this file is the top-level companion. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c0046cd6..b6d68f4d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,157 @@ cycle-accurate core later replaced. ## [Unreleased] +## [3.1.0] - 2026-10-08 - "Bellwether" (the AccuracyCoin re-sync and an honest 146/146, the CPU overclock and the sprite-limit option in movies and netplay, PAL emphasis, the alternate MMC3, rewind and run-ahead on the Vs. cabinet, and the MiSTer core matched to all of it) + +The first release of the v3.1 to v4.0 line. AccuracyCoin is re-synced to its +newest upstream, which showed that every earlier 100% score included a failure +the old ROM hid; the emulator now passes all 146 for real, and so does the +MiSTer core. Two long-shown options work and travel with movies and netplay +(a CPU overclock, and the sprite-limit switch), PAL emphasis is right, the +alternate MMC3 is selectable and correct, and the Vs. cabinet gets rewind and +run-ahead. **Save states, movies and netplay from v3.0.1 are refused.** + +### Breaking + +- **Movies and netplay from v3.0.1 are refused, and so are its save states.** + `EMULATION_EPOCH` rises from 2 to 3 (the two accuracy fixes below change bus + cycles and sprite evaluation). Save states: BUS section version 3 (the DMC + latch and the overclock's position) and `PPU_SNAPSHOT_VERSION` 13 (the + sprite-limit option's pending sprites). Movies: `.rnm` format 6 (the options + record gains the two options below). Netplay: protocol 7, magic `"RNE7"`; a + v3.0.x peer is refused as another emulator version, naming its epoch. + +### Fixed + +- **AccuracyCoin re-synced to upstream `f5f41dc2`: 146 of 146.** The catalog + grows to 151 rows / 146 scored with `DMA Landing on Write` and `DMC Reload + Timing`. The new ROM found two defects: + - **A DMC load DMA refused by a write took three cycles, not four.** It now + enters on the next read whichever half that is (`DMA Landing on Write` + test 9), found by a per-cycle comparison with TriCNES's output. + - **Misaligned sprite evaluation.** Evaluation now takes its start from + OAMADDR at dot 65 (it used dot 0), copies four bytes from a start at the + last byte of a slot (it copied one), and stays misaligned after an + in-range X (it always realigned). The old ROM recorded these failures as + a pass: its fail path returned into the test without popping the return + address, fixed upstream in `adacbc23`. + - **So the earlier 100% scores were overstated.** Every release that + reported AccuracyCoin 144/144 (and 141/141 before it) failed parts of + `Misaligned OAM behavior` as the fixed ROM scores it; the old ROM's bug + recorded those failures as a pass. v3.1.0 is the first release whose 100% does not + include that masked failure. +- **PAL and Dendy games that use colour emphasis show the right tint.** On the + PAL 2C07 and the Dendy, PPUMASK bits 5 and 6 swap meaning (green and red); + every PAL or Dendy game that set emphasis was tinted the wrong way. +- **The alternate MMC3 IRQ revision fires on a `$C001` reload to 0**, as the + MMC3A and non-Sharp MMC3B do (NES 2.0 submapper 4, and mapper 12). Sharp, + the default, is unchanged. +- **The AccuracyCoin tooling reads upstream's new row macros.** + `extract_catalog.py` returned 85 of 151 rows and exited 0 when upstream + compressed the unofficial-opcode rows into `tblf1` / `tblf2`; it now + rebuilds their names from the ROM's own string table and aborts on any row + it cannot read. `derive_indices.py` shares that grammar, and no longer + refuses to run: it read the provenance TSV's columns by position, which went + stale when an address column was added. The eight `Unofficial Immediates` + rows are now spelled `immediate`, as the ROM prints them. +- **The AccuracyCoin mirror ROM is rebuilt from `f5f41dc2`.** +- **Opening the pattern viewer, or using an HD pack, could change the game on + MMC2 / MMC4 boards** (Punch-Out!!, Fire Emblem). Their "side-effect-free" CHR + read went through the cartridge's normal read, which flips a CHR latch on + tiles `$FD`/`$FE`; the same held on three other boards. Those reads now + leave the cartridge exactly as it was. + +### Added + +- **CPU overclock** (`T-CPU-OVERCLOCK`, Settings > Enhancements): the CPU runs + 2 to 4 times faster against the same picture and sound, removing slowdown. + The APU, mapper IRQ counters and PPU timers stay at the stock rate, so pitch, + tempo and raster effects are unchanged. Unlike the extra-scanline overclock, + movies record it and replay with it, and netplay players must match. The + multiplier is exact on every region: a CPU cycle is 12 master clocks on NTSC + but 16 on PAL and 15 on Dendy, which do not divide by 3 or 4, so the + overclocked cycle lengths alternate to keep the average exact (a review of + the release PR found PAL x3 running at x3.2 and Dendy x4 at x5). +- **"Disable 8-sprite-per-scanline limit" now works** (`T-SPRITE-LIMIT`; it was + shown and saved but inert). It draws the dropped sprites behind the eight the + console shows, and changes nothing the game can see: the overflow flag, the + sprite fetches and every CPU cycle stay exact. It is skipped on the five + boards whose pattern reads change the cartridge (MMC2, MMC4, the J.Y. ASIC, + Bandai 96, Nanjing 163). +- **Differential phase distortion in the raw NTSC signal decode** + (`T-COMPOSITE-ARTIFACTS`): a new "Differential phase" slider rotates brighter + colours' hue as the NES PPU does (about 2.5° per palette row on a 2C02E, 5° on + a 2C02G). Off by default; display only. +- **MMC3 IRQ revision setting** (`T-MMC3-NEC-OVERRIDE`, Settings > Emulation): + run any mapper-4 game on the Sharp or the alternate (MMC3A / NEC) chip, for + dumps whose header cannot say which. blargg's `mmc3_test_2/6-MMC3_alt` passes + under the alternate setting. Carried in movies and netplay. +- **Rewind and run-ahead on the Vs. DualSystem cabinet** (`T-PS-dual-runahead`, + ADR 0032 amended): both work in two-screen mode, on the whole cabinet, so + the two consoles never fall out of step. A step back restores both screens + exactly, and run-ahead shows the same frames a run without it would, a + frame or more sooner. Netplay, movies, the debugger and HD packs stay + single-console. +- **A test now enforces the emulation-epoch rule** (`T-EPOCH-FINGERPRINT`). + It fingerprints seven test ROMs (frames, audio, RAM, CPU cycles) and fails + when that output moves while `EMULATION_EPOCH` still equals the last + release's, refusing a re-bless in that state. Until now the rule was kept + by hand. + +### The MiSTer core (`RustyNES_MiSTer`) + +- **It matches the re-synced AccuracyCoin, all 146 entries.** The new ROM found + the core's version of both emulator defects. A misaligned sprite evaluation + now masks the address after an out-of-range X byte, as the ROM's comments + state; and a `$4010` write on the DMC timer's reload edge now sets that + reload's period. Before them the core differed on two entries; after them the + status vector is identical. +- **A `$2006` write that meets the PPU's address pipeline matches the + emulator** (RTL-1). A new generated ROM lands the copy on every dot of a + rendering line; the core diverged on 28,129 of 331,838 background fetches, + for three reasons measured from the emulator's per-dot trace, and now matches + on all of them. One of the three, a copy delay that depends on where the + write lands, is the emulator's rule and not a documented one (NESdev says a + constant "1 to 1.5 dots"). It is recorded as provisional until a board can + settle it. +- **Ten more AccuracyCoin sub-test ROMs gate the core** (RTL-10), plus the new + `dmc-reload-timing`: 30 of 34 now, the other four out for stated reasons. The + `sprite-eval-misaligned-oam` sub-test ROM is rebuilt from the new source: the + old build's fail path fell through to a pass, so that gate read green every + release while the core failed it. +- **The oracle pin moves to v3.1.0**; every golden that changed is attributed + (AccuracyCoin, the rebuilt sub-test, two new stems) and the other 646 + artifacts are byte-identical. +- **A self-hosted ladder workflow** (`ladder.yml`, manual dispatch only) runs + the whole ladder on the maintainer's runner. CI's Verilator is recorded + (5.032). +- **No hardware has run any bitstream.** + +### Records + +- **The MiSTer contribution page was rewritten in September**, dropping the + "evidence of quality and accuracy testing" sentence the submission case + answered and adding reviewer questions about the developer. The new page and + its consequences are recorded in `ref-docs/`, the checklist is re-scoped, and + `submission-case.md` is refreshed. Two decisions are the maintainer's: how to + show reviewers the code is understood and maintained, and whether the + sibling repository becomes public. +- **Nine stale documents corrected against the code** (DOC-01..09), and the + v1.8.x Android checklist folded into the mobile run sheet. + +### Verification + +- `cargo test --workspace --features test-roms --release`: **3,262 passed, 0 + failed, 11 ignored**. The epoch fingerprint gate passes at epoch 3, now + recorded as the released epoch. +- AccuracyCoin **146/146** at upstream `f5f41dc2`; `nestest` 0-diff. +- The local commercial suites: `external_real_games` 60/0, `external_extended` + 137/0, `external_coverage` 6/0 over 744 staged ROMs. One baseline moved: + *Millionaire* (Sachen, mapper 146), which the game database marks PAL, at one + checkpoint, from the PAL emphasis fix. It was attributed by running that ROM + alone on each v3.1.0 commit, and re-blessed. +- The MiSTer core: LADDER-FILL. + ## [3.0.1] - 2026-10-07 - "Mortar" (the open items closed, one game's graphics fixed, the last MMC3 rule exception tested in the MiSTer core, Rust 1.99 everywhere, every unanswered bot review answered, and a roadmap to v4.0.0) A maintenance release on v3.0.0. It closes the items v3.0.0 left open: diff --git a/OVERVIEW.md b/OVERVIEW.md index 3bc8b90c0..e9a3cddfa 100644 --- a/OVERVIEW.md +++ b/OVERVIEW.md @@ -2,7 +2,7 @@ **Document Version:** 2.1.0 **Last Updated:** 2026-10-06 -**Applies to:** RustyNES v3.0.1 +**Applies to:** RustyNES v3.1.0 --- @@ -22,9 +22,9 @@ RustyNES is the **definitive NES emulator for the modern era** — combining cycle-perfect accuracy with a complete contemporary feature set and the safety guarantees of Rust. It is more than an emulator: it is a platform for NES preservation, competitive online play, tool-assisted speedrunning, and homebrew development. -As of **v1.0.0**, that vision was realized: RustyNES clears the Mesen2 / higan / ares accuracy bar, ships a polished desktop application and a browser build, and supports the full platform surface — netplay, achievements, TAS movies, a debugger, FDS, and arcade (Vs. / PlayChoice-10) hardware. Since then the additive v1.x line added three more platforms (native Android, iOS / iPadOS, and a Libretro / RetroArch core), **v2.0.0 "Timebase"** replaced the scheduler substrate with the one-clock / every-cycle-bus-access model (ADR 0029 — the first designated breaking release), and the v2.1.x → v2.3.x lines deepened accuracy, presentation, and analysis tooling. The current release is **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. Built on **v2.8.1 "Gasket"** — the libretro core fits the frontends around it: four-player games work through a Four Score option, a controller works again after its port leaves the Zapper, RetroArch no longer reads past the core's input-descriptor list, expansion audio no longer clips, the core declares UNIF images, and the Makefile honours PREFIX, platform=win, DEBUG and CARGO_TARGET_DIR. Built on **v2.8.0 "Bulkhead"** — the libretro core stops a fault at its own boundary: an internal error no longer closes RetroArch, save states survive plugging in a Zapper, closing a game withdraws its memory maps, the core loads from any libretro frontend, and the save state now carries the 2A03 internal data bus. Built on **v2.7.6 "Recount"** — the v2.7.5 deletions measured one at a time: the six performance proposals v2.7.5 bounded only together were each measured alone, where the benchmarks reach them: five are zero, and the sixth, the pulse sweep-mute check, bounds at about 0.2%, and the cheap byte-identical way to take it measured slower; the fast render path now asserts a rendering-history invariant it used to re-write; and the libretro buildbot builds macOS again and gains 32-bit Windows, 32-bit Linux and webOS targets. Built on **v2.7.5 "Tally"** — every audit claim closed with a measurement or a reason: the core audit's twelve performance proposals were closed, eleven of them by measurement, and one adopted (the audio buffer keeps its capacity between frames); 18 dead bus methods and the unused ApuBus trait are deprecated; and the core and frontend ledgers have no open row. Built on **v2.7.4 "Pocket"** — the mobile apps survive what a phone does to them: an internal error no longer closes the Android or iOS app, battery saves persist on both, the apps pause and give up audio when they should, and saves are written so a dying phone keeps the last good one. Built on **v2.7.3 "Hearth"** — the desktop and web frontends keep what they are given: battery saves persist on the desktop, a Lua script can no longer hang or exhaust the emulator, script HTTP cannot reach local services by default, and audio survives a device change. Built on **v2.7.2 "Bankroll"** — cartridge memory, every bank a cartridge has and nothing it has not: MMC1 reaches SUROM / SXROM, MMC5 banks its PRG-RAM, Namco 163 selects nametables, and `$6000-$7FFF` reads open bus where a board has nothing there. Built on **v2.7.1 "Keepsake"** — six cartridge boards now hand RetroArch their battery save instead of an empty one, every user file the frontend writes is written atomically, and three mapper files are now recorded as derived from Mesen2 and puNES. Built on **v2.7.0 "Palisade"** — a corrupt or hand-edited save state now fails at restore with a typed error instead of crashing the emulator one tick later, pulse 1 no longer mutes on the `$4001 = $08` sweep idiom, and the save-state fuzz target can finally reach what it exists to find. Built on **v2.6.23 "Pulse"** — the access does not increment, it pulses the load already there: the CHR-during-rendering gate closes at 61,440 of 61,440 pixels, and **no hardware has run any bitstream** — built on **v2.6.22 "Rigging"**, the instruments for the board, built before the board: AccuracyCoin reads back from hardware as bytes rather than as a photograph, the catalog turns out to carry 149 rows and 144 results so the headline no longer depends on when the run was sampled (at the same 144/144), and a `rom_sha256` recorded in every golden manifest and compared to nothing is now rung 0 — built on **v2.6.21 "Steward"**, which brought battery saves, the CHR-during-rendering gate RED at 32,861 of 61,440 pixels, and the deploy loop the runbook prescribed and **v2.6.20 "Telltale"**, itself on **v2.6.19 "Accession"** and **v2.6.18 "Errata"**, built on **v2.6.17 "Terminus"**. The never-tagged v2.4.0 "Concordance" shipped inside **v2.4.1 "Fabric"** — this sentence had attached that fact to whichever release was current, carried forward by three mechanical version bumps, and said it of v2.4.2, v2.4.3 and v2.4.4 in turn. +As of **v1.0.0**, that vision was realized: RustyNES clears the Mesen2 / higan / ares accuracy bar, ships a polished desktop application and a browser build, and supports the full platform surface — netplay, achievements, TAS movies, a debugger, FDS, and arcade (Vs. / PlayChoice-10) hardware. Since then the additive v1.x line added three more platforms (native Android, iOS / iPadOS, and a Libretro / RetroArch core), **v2.0.0 "Timebase"** replaced the scheduler substrate with the one-clock / every-cycle-bus-access model (ADR 0029 — the first designated breaking release), and the v2.1.x → v2.3.x lines deepened accuracy, presentation, and analysis tooling. The current release is **v3.1.0 "Bellwether"** — the AccuracyCoin re-sync and an honest 146/146, the CPU overclock and the sprite-limit option in movies and netplay, PAL emphasis, the alternate MMC3, rewind and run-ahead on the Vs. cabinet, and the MiSTer core matched to all of it. Built on **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. Built on **v2.8.1 "Gasket"** — the libretro core fits the frontends around it: four-player games work through a Four Score option, a controller works again after its port leaves the Zapper, RetroArch no longer reads past the core's input-descriptor list, expansion audio no longer clips, the core declares UNIF images, and the Makefile honours PREFIX, platform=win, DEBUG and CARGO_TARGET_DIR. Built on **v2.8.0 "Bulkhead"** — the libretro core stops a fault at its own boundary: an internal error no longer closes RetroArch, save states survive plugging in a Zapper, closing a game withdraws its memory maps, the core loads from any libretro frontend, and the save state now carries the 2A03 internal data bus. Built on **v2.7.6 "Recount"** — the v2.7.5 deletions measured one at a time: the six performance proposals v2.7.5 bounded only together were each measured alone, where the benchmarks reach them: five are zero, and the sixth, the pulse sweep-mute check, bounds at about 0.2%, and the cheap byte-identical way to take it measured slower; the fast render path now asserts a rendering-history invariant it used to re-write; and the libretro buildbot builds macOS again and gains 32-bit Windows, 32-bit Linux and webOS targets. Built on **v2.7.5 "Tally"** — every audit claim closed with a measurement or a reason: the core audit's twelve performance proposals were closed, eleven of them by measurement, and one adopted (the audio buffer keeps its capacity between frames); 18 dead bus methods and the unused ApuBus trait are deprecated; and the core and frontend ledgers have no open row. Built on **v2.7.4 "Pocket"** — the mobile apps survive what a phone does to them: an internal error no longer closes the Android or iOS app, battery saves persist on both, the apps pause and give up audio when they should, and saves are written so a dying phone keeps the last good one. Built on **v2.7.3 "Hearth"** — the desktop and web frontends keep what they are given: battery saves persist on the desktop, a Lua script can no longer hang or exhaust the emulator, script HTTP cannot reach local services by default, and audio survives a device change. Built on **v2.7.2 "Bankroll"** — cartridge memory, every bank a cartridge has and nothing it has not: MMC1 reaches SUROM / SXROM, MMC5 banks its PRG-RAM, Namco 163 selects nametables, and `$6000-$7FFF` reads open bus where a board has nothing there. Built on **v2.7.1 "Keepsake"** — six cartridge boards now hand RetroArch their battery save instead of an empty one, every user file the frontend writes is written atomically, and three mapper files are now recorded as derived from Mesen2 and puNES. Built on **v2.7.0 "Palisade"** — a corrupt or hand-edited save state now fails at restore with a typed error instead of crashing the emulator one tick later, pulse 1 no longer mutes on the `$4001 = $08` sweep idiom, and the save-state fuzz target can finally reach what it exists to find. Built on **v2.6.23 "Pulse"** — the access does not increment, it pulses the load already there: the CHR-during-rendering gate closes at 61,440 of 61,440 pixels, and **no hardware has run any bitstream** — built on **v2.6.22 "Rigging"**, the instruments for the board, built before the board: AccuracyCoin reads back from hardware as bytes rather than as a photograph, the catalog turns out to carry 149 rows and 144 results so the headline no longer depends on when the run was sampled (at the same 144/144), and a `rom_sha256` recorded in every golden manifest and compared to nothing is now rung 0 — built on **v2.6.21 "Steward"**, which brought battery saves, the CHR-during-rendering gate RED at 32,861 of 61,440 pixels, and the deploy loop the runbook prescribed and **v2.6.20 "Telltale"**, itself on **v2.6.19 "Accession"** and **v2.6.18 "Errata"**, built on **v2.6.17 "Terminus"**. The never-tagged v2.4.0 "Concordance" shipped inside **v2.4.1 "Fabric"** — this sentence had attached that fact to whichever release was current, carried forward by three mechanical version bumps, and said it of v2.4.2, v2.4.3 and v2.4.4 in turn. -> RustyNES's emulation core descends from an extensively-documented accuracy program. Where this and related docs reference deep "v1.x"/"v2.x" engine narrative, read it as upstream engine lineage (engineering history), not as RustyNES release versions. Two distinct "v2.0"s exist and must not be conflated: the engine-lineage v2.0 master-clock work shipped as RustyNES **v1.0.0**, while RustyNES's own **v2.0.0 "Timebase"** (2026-07-03) is the later release that *replaced* that same scheduler. The current release is **v3.0.1**. +> RustyNES's emulation core descends from an extensively-documented accuracy program. Where this and related docs reference deep "v1.x"/"v2.x" engine narrative, read it as upstream engine lineage (engineering history), not as RustyNES release versions. Two distinct "v2.0"s exist and must not be conflated: the engine-lineage v2.0 master-clock work shipped as RustyNES **v1.0.0**, while RustyNES's own **v2.0.0 "Timebase"** (2026-07-03) is the later release that *replaced* that same scheduler. The current release is **v3.1.0**. --- @@ -56,7 +56,7 @@ A one-directional crate graph keeps each chip (`rustynes-cpu`, `rustynes-ppu`, ` | Test | Result | |------|--------| -| **AccuracyCoin** | **100.00% (144/144)** (RAM-direct decoder) — the 2026-09 upstream re-sync grew the battery to 144 assigned tests and v2.6.18 closed the last one (`Advanced Sprite Evaluation :: Frozen OAM2 Increment`), so `KNOWN_FAILING` is empty; the two newest upstream PPU tests ("ALE + Read", "Hybrid Addresses") were closed by the v2.0.3 2-cycle-ALE promotion | +| **AccuracyCoin** | **100.00% (146/146)** (RAM-direct decoder) at upstream `f5f41dc2` since v3.1.0, whose re-sync showed that the 144/144 reported from v2.6.18 to v3.0.1 was overstated, because the older ROM's `Misaligned OAM behavior` fail path fell through to a pass and hid a real failure. Before it: the 2026-09 upstream re-sync grew the battery to 144 assigned tests and v2.6.18 closed the last one (`Advanced Sprite Evaluation :: Frozen OAM2 Increment`), so `KNOWN_FAILING` is empty; the two newest upstream PPU tests ("ALE + Read", "Hybrid Addresses") were closed by the v2.0.3 2-cycle-ALE promotion | | **`nestest`** | **0-diff** against the Nintendulator golden log | | **blargg / kevtris / `mmc3_test_2`** | Green | | **Commercial-ROM oracle** | 60-ROM byte-identical regression gate + extended visual survey | @@ -88,7 +88,7 @@ RustyNES uses **cycle-accurate** emulation rather than scanline-based shortcuts. | Area | What ships today | |------|----------------------| -| **Accuracy** | One-clock scheduler (v2.0.0 "Timebase"), master-clock timebase, AccuracyCoin **144/144 (100.00%)** on a battery that grew to 144 assigned tests at the 2026-09 re-sync, `nestest` 0-diff | +| **Accuracy** | One-clock scheduler (v2.0.0 "Timebase"), master-clock timebase, AccuracyCoin **146/146 (100.00%)** on the battery as re-synced to upstream `f5f41dc2` in v3.1.0 (the 144/144 before it hid a masked failure), `nestest` 0-diff | | **Cartridges** | **191** mapper families incl. expansion audio (VRC6/VRC7-OPLL/Sunsoft 5B/N163/MMC5) | | **Platforms** | iNES / NES 2.0, Famicom Disk System (real-BIOS boot, read/write, multi-side), Vs. System / PlayChoice-10 RGB | | **Online** | Rollback netplay, UDP (native) + WebRTC (browser), 2–4 players | diff --git a/README.md b/README.md index d09cd2c62..97a755a11 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

- Build Status License: GPL-3.0-or-later Version Rust: 1.99
+ Build Status License: GPL-3.0-or-later Version Rust: 1.99
AccuracyCoin nestest Mapper families Try in browser
Platform

@@ -18,7 +18,7 @@ pure Rust.** It aims at the Mesen2 / higan / ares accuracy bar: one master clock, every CPU cycle a real bus access, and the PPU caught up to each half of it, so a sprite-zero hit, a mid-scanline scroll write or an MMC3 IRQ lands on the exact dot -without a per-game patch. It passes **AccuracyCoin 144/144** and matches the +without a per-game patch. It passes **AccuracyCoin 146/146** and matches the Nintendulator `nestest` log with **zero diff**. Around that core sits a complete, modern platform: 191 mapper families, the @@ -72,7 +72,7 @@ audience: players, RetroArch, mobile and the Rust crates. | | | | --- | --- | -| **Cycle-accurate** | CPU, PPU and APU on one master clock: AccuracyCoin 144/144, `nestest` 0-diff, blargg's CPU, APU and PAL suites | +| **Cycle-accurate** | CPU, PPU and APU on one master clock: AccuracyCoin 146/146, `nestest` 0-diff, blargg's CPU, APU and PAL suites | | **191 mapper families** | NROM through MMC5, the whole VRC line, Sunsoft FME-7, Namco 163, Taito, J.Y. Company, the MMC3 and MMC1 multicarts, Waixing and Nanjing boards, homebrew flash boards with working saves, and a UNIF (`.unf`) loader. Each is classified Core, Curated or BestEffort by the evidence behind it | | **Famicom Disk System** | Real-BIOS boot, writable disks, side swapping, a timed disk-head model and 2C33 wavetable audio | | **Vs. / PlayChoice-10** | Arcade boards in true 2C03 / 2C04 / 2C05 RGB, per-game DIP presets, and Vs. DualSystem two-screen cabinets | @@ -261,7 +261,7 @@ to player 1 automatically. | Suite | Result | | --- | --- | -| **AccuracyCoin** | **144/144 (100.00%)**, read from RAM rather than the screen | +| **AccuracyCoin** | **146/146 (100.00%)** at upstream `f5f41dc2`, read from RAM rather than the screen. The 144/144 reported before v3.1.0 was overstated: the older ROM's `Misaligned OAM behavior` fail path fell through to a pass, hiding a real failure the re-sync found and fixed | | `nestest` | 0-diff against the Nintendulator log | | blargg `cpu_interrupts_v2` | 5/5, and the unstable-store tests 6/6 | | blargg APU (NTSC and PAL) | 11/11 and 10/10 | @@ -357,7 +357,7 @@ detailed in [`docs/architecture.md`](docs/architecture.md) and ## Current release -RustyNES's current release is **v3.0.1 "Mortar"** (2026-10-07) — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** (2026-10-06) — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** (2026-10-04) — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** (2026-10-02) — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** (2026-09-30) — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** (2026-09-30) — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. +RustyNES's current release is **v3.1.0 "Bellwether"** (2026-10-08) — the AccuracyCoin re-sync and an honest 146/146, the CPU overclock and the sprite-limit option in movies and netplay, PAL emphasis, the alternate MMC3, rewind and run-ahead on the Vs. cabinet, and the MiSTer core matched to all of it. Built on **v3.0.1 "Mortar"** (2026-10-07) — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** (2026-10-06) — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** (2026-10-04) — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** (2026-10-02) — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** (2026-09-30) — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** (2026-09-30) — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. **v3.0.0 is the API major** ([ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md)). It gathers every breaking change since v2.x, most of them made early in v2.9.8 and @@ -489,7 +489,7 @@ Full attribution is in [`NOTICE`](NOTICE). title = {RustyNES: A Cycle-Accurate NES Emulator in Rust}, year = {2026}, url = {https://github.com/doublegate/RustyNES}, - note = {Cycle-accurate NES emulator; AccuracyCoin 144/144, nestest 0-diff} + note = {Cycle-accurate NES emulator; AccuracyCoin 146/146, nestest 0-diff} } ``` diff --git a/ROADMAP.md b/ROADMAP.md index 4df8a813b..781b361d2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,13 +2,13 @@ **Document Version:** 2.0.4 **Last Updated:** 2026-10-06 -**Project Status:** v3.0.1 "Mortar" released — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** (the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core) and **v2.9.9 "Ballast"** (the release candidate for v3.0.0: all four audit scopes re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it with the release-candidate bitstream pair; the tenth release of the v2.9.x line and the sixth of the line to v3.0.0 (ADR 0043, amended)) and **v2.9.8 "Vanguard"** (v3.0.0's breaking changes landed early) and **v2.9.7 "Tandem"** (the desktop's features on the web and on phones). **No hardware has run any bitstream**; the mobile device runs and the SuperStation One board session move after v3.0.0 (maintainer, 2026-09-29). +**Project Status:** v3.1.0 "Bellwether" released — the AccuracyCoin re-sync and an honest 146/146, the CPU overclock and the sprite-limit option in movies and netplay, PAL emphasis, the alternate MMC3, rewind and run-ahead on the Vs. cabinet, and the MiSTer core matched to all of it. Built on **v3.0.1 "Mortar"** (a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0) and **v3.0.0 "Cornerstone"** (the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core) and **v2.9.9 "Ballast"** (the release candidate for v3.0.0: all four audit scopes re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it with the release-candidate bitstream pair; the tenth release of the v2.9.x line and the sixth of the line to v3.0.0 (ADR 0043, amended)) and **v2.9.8 "Vanguard"** (v3.0.0's breaking changes landed early) and **v2.9.7 "Tandem"** (the desktop's features on the web and on phones). **No hardware has run any bitstream**; the mobile device runs and the SuperStation One board session move after v3.0.0 (maintainer, 2026-09-29). --- ## Where we are -RustyNES is well past v1.0.0. The current release is **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). **No hardware has run any bitstream.** +RustyNES is well past v1.0.0. The current release is **v3.1.0 "Bellwether"** — the AccuracyCoin re-sync and an honest 146/146, the CPU overclock and the sprite-limit option in movies and netplay, PAL emphasis, the alternate MMC3, rewind and run-ahead on the Vs. cabinet, and the MiSTer core matched to all of it. Built on **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). **No hardware has run any bitstream.** **This root ROADMAP is a historical snapshot of the v1.0.0 cut.** For the authoritative, current forward roadmap see **[`to-dos/ROADMAP.md`](to-dos/ROADMAP.md)**; for the authoritative current-state pass counts and platform matrix see **[`docs/STATUS.md`](docs/STATUS.md)**; for the full per-release history see **[`CHANGELOG.md`](CHANGELOG.md)**. Many of the "post-1.0 directions" listed further down (mobile, Lua scripting, TAS editor, Vs. DualSystem, HD packs, hosted netplay) have since shipped — the tables below record what was **done at v1.0.0**, not the current feature set. diff --git a/SECURITY.md b/SECURITY.md index 09903d496..970347976 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,7 @@ ## Supported Versions -The current release is **v3.0.1 "Mortar"**. Built on **v3.0.0 "Cornerstone"** and **v2.9.9 "Ballast"** and **v2.9.8 "Vanguard"** and **v2.9.7 "Tandem"** and **v2.9.6 "Roster"** and **v2.9.5 "Caliper"** and **v2.9.4 "Plumb"** and **v2.9.3 "Handset"** and **v2.9.2 "Candidate"** and **v2.9.1 "Hone"** and **v2.9.0 "Survey"** and **v2.8.4 "Tether"** and **v2.8.3 "Rivet"** and **v2.8.2 "Solder"** and **v2.8.1 "Gasket"** and **v2.8.0 "Bulkhead"** and **v2.7.6 "Recount"** and **v2.7.5 "Tally"** and **v2.7.4 "Pocket"** and **v2.7.3 "Hearth"** and **v2.7.2 "Bankroll"** and **v2.7.1 "Keepsake"** and **v2.7.0 "Palisade"** and **v2.6.23 "Pulse"** and **v2.6.22 "Rigging"** and **v2.6.21 "Steward"** and **v2.6.20 "Telltale"** and **v2.6.19 "Accession"** and **v2.6.18 "Errata"** and **v2.6.17 "Terminus"** and **v2.6.16 "Interlock"** and **v2.6.15 "Warrant"** and **v2.6.14 "Docket"** and **v2.6.13 "Slack"** and **v2.6.12 "Groundwork"** and **v2.6.11 "Exposure"** and **v2.6.10 "Inference"** and **v2.6.9 "Abeyance"** and **v2.6.8 "Arrears"** and **v2.6.7 "Detent"** and **v2.6.6 "Chassis"** and **v2.6.5 "Muster"** and **v2.6.4 "Rubric"** and **v2.6.3 "Mainspring"** and **v2.6.2 "Witness"** and **v2.6.1 "Interleave"** and **v2.6.0 "Assay"** and **v2.5.9 "Overture"** and **v2.5.8 "Blanking"** and **v2.5.7 "Collimation"** and **v2.5.6 "Vestige"** and **v2.5.5 "Raster"** and **v2.5.4 "Escapement"** and **v2.5.3 "Hysteresis"** and **v2.5.2 "Dormant"** and **v2.5.1 "Retrace"** and **v2.5.0 "Rungwork"** and **v2.4.9 "Plumbline II"** and **v2.4.8 "Palimpsest"** and **v2.4.7 "Keystone"** and **v2.4.6 "Abacus"** and **v2.4.5 "Compass"** and **v2.4.4 "Ignition"** and **v2.4.3 "Touchstone"** and **v2.4.2 "Cairn"** and **v2.4.1 "Fabric"**, which also carries the never-tagged v2.4.0 "Concordance". RustyNES ships from `main` on a +The current release is **v3.1.0 "Bellwether"**. Built on **v3.0.1 "Mortar"** and **v3.0.0 "Cornerstone"** and **v2.9.9 "Ballast"** and **v2.9.8 "Vanguard"** and **v2.9.7 "Tandem"** and **v2.9.6 "Roster"** and **v2.9.5 "Caliper"** and **v2.9.4 "Plumb"** and **v2.9.3 "Handset"** and **v2.9.2 "Candidate"** and **v2.9.1 "Hone"** and **v2.9.0 "Survey"** and **v2.8.4 "Tether"** and **v2.8.3 "Rivet"** and **v2.8.2 "Solder"** and **v2.8.1 "Gasket"** and **v2.8.0 "Bulkhead"** and **v2.7.6 "Recount"** and **v2.7.5 "Tally"** and **v2.7.4 "Pocket"** and **v2.7.3 "Hearth"** and **v2.7.2 "Bankroll"** and **v2.7.1 "Keepsake"** and **v2.7.0 "Palisade"** and **v2.6.23 "Pulse"** and **v2.6.22 "Rigging"** and **v2.6.21 "Steward"** and **v2.6.20 "Telltale"** and **v2.6.19 "Accession"** and **v2.6.18 "Errata"** and **v2.6.17 "Terminus"** and **v2.6.16 "Interlock"** and **v2.6.15 "Warrant"** and **v2.6.14 "Docket"** and **v2.6.13 "Slack"** and **v2.6.12 "Groundwork"** and **v2.6.11 "Exposure"** and **v2.6.10 "Inference"** and **v2.6.9 "Abeyance"** and **v2.6.8 "Arrears"** and **v2.6.7 "Detent"** and **v2.6.6 "Chassis"** and **v2.6.5 "Muster"** and **v2.6.4 "Rubric"** and **v2.6.3 "Mainspring"** and **v2.6.2 "Witness"** and **v2.6.1 "Interleave"** and **v2.6.0 "Assay"** and **v2.5.9 "Overture"** and **v2.5.8 "Blanking"** and **v2.5.7 "Collimation"** and **v2.5.6 "Vestige"** and **v2.5.5 "Raster"** and **v2.5.4 "Escapement"** and **v2.5.3 "Hysteresis"** and **v2.5.2 "Dormant"** and **v2.5.1 "Retrace"** and **v2.5.0 "Rungwork"** and **v2.4.9 "Plumbline II"** and **v2.4.8 "Palimpsest"** and **v2.4.7 "Keystone"** and **v2.4.6 "Abacus"** and **v2.4.5 "Compass"** and **v2.4.4 "Ignition"** and **v2.4.3 "Touchstone"** and **v2.4.2 "Cairn"** and **v2.4.1 "Fabric"**, which also carries the never-tagged v2.4.0 "Concordance". RustyNES ships from `main` on a rolling patch cadence rather than maintaining long-lived release branches, so security fixes land in the next patch release rather than being backported. Report against the latest release or `main`. diff --git a/SUPPORT.md b/SUPPORT.md index ee55055e3..452be26ba 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -94,11 +94,11 @@ A: RustyNES is a cycle-accurate NES emulator written in pure Rust, clearing the **Q: Can I use RustyNES now?** -A: Yes. RustyNES is well past its first stable release — the current release is **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). **No hardware has run any bitstream.** +A: Yes. RustyNES is well past its first stable release — the current release is **v3.1.0 "Bellwether"** — the AccuracyCoin re-sync and an honest 146/146, the CPU overclock and the sprite-limit option in movies and netplay, PAL emphasis, the alternate MMC3, rewind and run-ahead on the Vs. cabinet, and the MiSTer core matched to all of it. Built on **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). **No hardware has run any bitstream.** **Q: How accurate is RustyNES?** -A: AccuracyCoin 100.00% (144/144). The 2026-09 upstream re-sync grew the battery from 141 to 144 assigned tests, and v2.6.18 closed the last outstanding entry (`Advanced Sprite Evaluation :: Frozen OAM2 Increment`). That includes the two older upstream PPU tests ("ALE + Read", "Hybrid Addresses"), which the v2.0.3 2-cycle-ALE PPU-fetch promotion closed — `nestest` 0-diff, and the blargg / kevtris suites green, validated by a byte-identical commercial-ROM regression oracle. See [docs/STATUS.md](docs/STATUS.md) for the authoritative pass-count matrix. +A: AccuracyCoin 100.00% (146/146) at upstream `f5f41dc2`, since v3.1.0. That re-sync also showed that the 144/144 reported from v2.6.18 to v3.0.1 was overstated, because the older ROM's `Misaligned OAM behavior` fail path fell through to a pass and hid a real failure; v3.1.0 fixed it. Before it, the 2026-09 upstream re-sync grew the battery from 141 to 144 assigned tests, and v2.6.18 closed the last outstanding entry (`Advanced Sprite Evaluation :: Frozen OAM2 Increment`). That includes the two older upstream PPU tests ("ALE + Read", "Hybrid Addresses"), which the v2.0.3 2-cycle-ALE PPU-fetch promotion closed — `nestest` 0-diff, and the blargg / kevtris suites green, validated by a byte-identical commercial-ROM regression oracle. See [docs/STATUS.md](docs/STATUS.md) for the authoritative pass-count matrix. **Q: How can I contribute?** diff --git a/VERSION-PLAN.md b/VERSION-PLAN.md index 4cff5b920..54a4f7892 100644 --- a/VERSION-PLAN.md +++ b/VERSION-PLAN.md @@ -1,6 +1,6 @@ # RustyNES Version Plan -**Current release: v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. **No hardware has run any bitstream.** +**Current release: v3.1.0 "Bellwether"** — the AccuracyCoin re-sync and an honest 146/146, the CPU overclock and the sprite-limit option in movies and netplay, PAL emphasis, the alternate MMC3, rewind and run-ahead on the Vs. cabinet, and the MiSTer core matched to all of it. Built on **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. **No hardware has run any bitstream.** RustyNES follows [Semantic Versioning 2.0.0](https://semver.org/). @@ -73,7 +73,6 @@ day.) | Version | Scope | Plan | |---------|-------|------| -| v3.1.0 | The AccuracyCoin re-sync; the CPU overclock and sprite-limit options in movies and netplay; PAL emphasis; opt-in composite artifacts; the NEC MMC3 option; rewind and run-ahead in Vs. dual mode; an epoch fingerprint gate. MiSTer: small RTL items, the self-hosted runner, submission documents | [`v3.1.0-plan.md`](to-dos/plans/v3.1.0-plan.md) | | v3.2.0 | Mapper breadth by real titles, the dump corpus, KNOWN_BLANK triage, tier promotions. MiSTer F1: options and about ten cheap families, paddle, Four Score, cheats | [`v3.2.0-plan.md`](to-dos/plans/v3.2.0-plan.md) | | v3.3.0 | Phi2 write placement and the sprite-0 stale shifter; wgpu 31 / egui 0.37. MiSTer F2: the SDRAM arbiter, DDR3, save states, rewind; the off-die build becomes the headline | [`v3.3.0-plan.md`](to-dos/plans/v3.3.0-plan.md) | | v3.4.0 | Hosted netplay and the browser RA proxy on Cloudflare, RA hardcore compliance, native 3-4 player netplay. MiSTer F3a: MMC2/4, FME-7/5B, VRC2/4, the Zapper | [`v3.4.0-plan.md`](to-dos/plans/v3.4.0-plan.md) | @@ -166,7 +165,8 @@ The 1.x line was **additive / off-by-default** — every release stayed byte-ide | **v2.7.5 "Tally"** | Every audit claim closed with a measurement or a reason -- the sixth and last release of the v2.7.x audit line; the core and frontend ledgers have no open row. Of the core audit's twelve hot-path proposals, ten were measured here before any was built, IMP-12 had been measured in v2.3.1, and the phase-`match` fold was closed by reasoning: one combined ceiling probe (seven proposals at their maximum, knowingly breaking correctness) bounded those seven at zero on two clean runs, and IMP-04 run alone twice also measured zero; IMP-05 (CPU inlining + cold-path outlining, whose premise was true) and the branchless palette got their own runs, also zero; IMP-12 was already v2.3.1's G10. **One adopted:** `BlipBuf::drain_all` keeps its capacity between frames, -0.89% frame time on two runs of a frame-then-drain workload (the stock benches never drain audio, which review on #553 exposed after the probe had first been credited with it); it helps the mobile bridge and the wasm build. 18 dead `rustynes_cpu::Bus` methods (found by the compiler: 5 of the audit's 22 were live) and `ApuBus` are `#[deprecated]`, removal decided at v2.9.0. An HD pack refunds the budget of an image that fails after its header. MOB-06 is blocked on UniFFI releasing mutable borrowed bytes. No emulation behaviour change; AccuracyCoin 144/144, nestest 0-diff. | | **v2.7.6 "Recount"** | The v2.7.5 deletions measured one at a time -- a measurement release between the v2.7.x and v2.8.x audit lines. v2.7.5 bounded six of the core audit's proposals (§3.1 A/B/C, the IMP-06 stores, §3.5b, §3.6) only in one combined ceiling; each was re-run alone, twice, with the A/B/A control. Five are zero; §3.5b had never been reached (the bench ROMs are silent, 0 sweep-mute checks per frame), and on `spritecans.nes` bounds at about 0.2%, with the cheap byte-identical candidate 0.67% slower, so it is rejected. Three of the IMP-06 stores re-wrote fields the fast path's guard already requires, and are now a `debug_assert!` pinned by a unit test (no ROM reaches the violating state). §3.1 B and C are dead work that goes with the v2.9.0 deprecation decision. Also ships #554 (contributed): the macOS buildbot jobs build again, and 32-bit Windows, 32-bit Linux and webOS targets were added. Plan: `to-dos/plans/v2.7.6-recount-plan.md` | | **v2.8.0 "Bulkhead"** | The libretro core stops a fault at its own boundary -- the first release of the v2.8.x audit line, from the libretro audit's FFI-safety findings (L-1.1 to L-1.5, L-2.1, L-2.2, L-2.4), each shown failing in a new C-ABI harness that drives the core as a frontend does. The core was built `panic = "abort"`, so its handlers were dead code; it now unwinds on every supported build path (+0.1% instructions per frame; a direct `cargo build --release` still aborts, and now warns and logs it), and every emulating callback contains a panic, stops the game and keeps the console so RetroArch's memory pointers stay valid. `serialize_size` reserves room for the largest expansion device on both ports (a Zapper no longer breaks every later save) and the reader treats an all-zero tail as padding; unload withdraws the memory maps; the vendored `rust-libretro-sys` writes out `retro_game_info`, so the core loads without `GET_GAME_INFO_EXT`. The save state gains the 2A03 internal data bus (older states still load), and the schema audit now covers the bus. No emulation behaviour change; AccuracyCoin 144/144, nestest 0-diff. Plan: `to-dos/plans/v2.8.0-bulkhead-plan.md` | -| **v3.0.1 "Mortar"** (current) | A maintenance release on v3.0.0, plan [`v3.0.1-mortar-plan.md`](to-dos/plans/v3.0.1-mortar-plan.md). Mapper 45's CHR-RAM is unbanked, so *Famicom Yarou Vol.1* draws its menu (T-GA23C-CHRRAM; `EMULATION_EPOCH` 2 refuses v3.0.0 movies and netplay peers). The MiSTer core's dot-0 A12 rule is gated on whether cycle 0 was rendering, found by a new odd-frame stimulus (`mapper4mmc3oddskip080`, plus an /IRQ-rise comparison); new bitstreams at seed 2 / 261007, a release candidate, not hardware-verified. Rust 1.99 and every dependency at its newest, the libretro buildbot included once test pipeline 119614 passed 15/15. Every bot review left unanswered since PR #1 answered (473 verdicts, 80 fixes). The shared Bisqwit NTSC pass recorded as derived; the TriCNES source moved out of the repository. The plan to v4.0.0 written from 29 maintainer decisions | +| **v3.0.1 "Mortar"** | A maintenance release on v3.0.0, plan [`v3.0.1-mortar-plan.md`](to-dos/plans/v3.0.1-mortar-plan.md). Mapper 45's CHR-RAM is unbanked, so *Famicom Yarou Vol.1* draws its menu (T-GA23C-CHRRAM; `EMULATION_EPOCH` 2 refuses v3.0.0 movies and netplay peers). The MiSTer core's dot-0 A12 rule is gated on whether cycle 0 was rendering, found by a new odd-frame stimulus (`mapper4mmc3oddskip080`, plus an /IRQ-rise comparison); new bitstreams at seed 2 / 261007, a release candidate, not hardware-verified. Rust 1.99 and every dependency at its newest, the libretro buildbot included once test pipeline 119614 passed 15/15. Every bot review left unanswered since PR #1 answered (473 verdicts, 80 fixes). The shared Bisqwit NTSC pass recorded as derived; the TriCNES source moved out of the repository. The plan to v4.0.0 written from 29 maintainer decisions | +| **v3.1.0 "Bellwether"** (current) | The first release of the v3.1 to v4.0 line, plan [`v3.1.0-plan.md`](to-dos/plans/v3.1.0-plan.md). AccuracyCoin re-synced to upstream `f5f41dc2`, 146 of 146; the new ROM showed that every earlier 100% included a `Misaligned OAM behavior` failure the old ROM hid, and both defects it found are fixed (`EMULATION_EPOCH` 3, BUS section 3, `PPU_SNAPSHOT_VERSION` 13, `.rnm` 6, netplay 7: v3.0.1 states, movies and peers are refused). The CPU overclock and the sprite-limit option work and ride in `HardwareOptions`; PAL and Dendy emphasis swap red and green; the alternate MMC3 is selectable and asserts on a `$C001` reload to 0; differential phase in the NTSC decode; rewind and run-ahead on the Vs. cabinet; an epoch fingerprint gate. The MiSTer core matches the re-synced battery on all 146 entries (the misaligned X mask, the DMC reload edge), matches the emulator on a `$2006` copy landing on every dot (RTL-1, provisional until a board settles it), gates 30 of 34 AccuracyCoin sub-test ROMs, and moves its pin to v3.1.0. **No hardware has run any bitstream.** | | **v3.0.0 "Cornerstone"** | The API major -- the first release of the v3 line (ADR 0043). It gathers every break since v2.x in one place (the header-free game identity, refused older states and movies, the protocol and API changes of v2.8.x-v2.9.9) and adds the last: a core timing epoch that movies (`.rnm` format 5) and netplay (protocol 6, `"RNE6"`) carry, so a version that emulates differently is refused with a reason (ADR 0045); `Cartridge`, `BoardDescription`, `HardwareOptions` and `Movie` `#[non_exhaustive]`; `MapperError::Truncated` renamed `WrongLength`. T-MMC3-BG-A12 closes blargg's `4-scanline_timing` (13/13) in the emulator and on the MiSTer core (`PPU_SNAPSHOT_VERSION` 12). Also: the spectator buffer bounded, mapper 45's power-on CHR-AND (two *Famicom Yarou* menus), the mobile apps on the workspace version, and `CI success` that cannot pass untested. The MiSTer bitstreams ship as a release candidate, not hardware-verified; verification moves to v3.x | | **v2.9.9 "Ballast"** | The release candidate for v3.0.0 -- the tenth release of the v2.9.x line and the last of the line to v3.0.0. All four audit scopes re-run (0 rows regressed; NC-09..17, NF-11..23, NL-11..15, NR-13..17 fixed or decided, NC-17 and NR-14 by the maintainer). T-ORACLE-001 fixed to `4-scanline_timing` sub-test 9: the oracle's `$C001` reload discriminator was the late IRQ; the NESdev rule plus a one-cycle IRQ output deferral replace it (MMC3 section v4). T-MMC5-8X8-SET fixed and wider than the ticket: the MMC5 decodes `$2000`/`$2001` itself and follows the page's CHR table and `$2007` rule, which also fixed *Uchuu Keibitai SDF* (T-COMMERCIAL-GARBLE closed; MMC5 section v6). Audio exact across a save state (APU v5, NL-12). Movie format 4 (NC-10). Mobile save keys migrated to the v2.9.8 identity (NF-21). The sibling's oracle pin moved v2.9.2 -> v2.9.9 with every changed golden attributed, the APU `$4017` / scanline-0 parity in RTL, a frame-sequencer stall found and fixed, and `apu_test` 1-10, `mapper4mmc3irq065` and four checkpoint streams gated. RC bitstreams at seed 6 (261005), byte-identical double compiles. T-GA23C-POWERON searched and left open. | | **v2.9.8 "Vanguard"** | The preparation release for v3.0.0 -- the ninth release of the v2.9.x line. v3.0.0's planned breaks landed early at the maintainer's direction (ADR 0042/0043 amendments of 2026-10-01; the number stays v2.9.x): `Nes::rom_sha256` hashes the bytes after the header, so earlier saves, cheats and movies are not found; `.rns` epoch 3 and BUS section 2 refuse older states with every legacy reader removed; `.rnm` format 3 and the netplay handshake carry every emulation option (ADR 0044) and Four Score P3/P4; `SystemBus` (was `LockstepBus`), the 2.7.5 deprecations, `serialize_header` and the dead /NMI detector removed, `Header` and `FrameInput` `#[non_exhaustive]`; the Vs. database keyed by the new identity. Every one of 748 staged dumps booted and looked at: fixed from their documentation were the game database rewriting correct NES 2.0 headers (10 games), the iNES 1.0 dirty-tail mapper nibble, mappers 19, 64, 78, 105 (`$4017` inhibit), 153, 191 (non-power-of-two images), 218 and 226, Vs. work RAM and the Goonies palette, and PAL regions now reach iNES 1.0 games. The game database's corrections now reach Android, iOS and libretro; power-on options apply before a game's first frame; Power Cycle keeps the PPU/APU settings; an opt-in Famicom console model; the documented NTSC emphasis model. Performance: the /NMI removal -4.9% to -6.1% on three workloads, three campaign candidates adopted (-1.8% to -4.3% together); end to end only shipped `nestest_fast` is established (-5% to -9% vs v2.9.7), the rest a v2.9.9 lead. MiSTer: aspect/integer-scale/crop/2C03-palette menu options (not seen on hardware), a palette-gate, seed 2 kept after re-sweeps. `cargo test --release --workspace --features test-roms,commercial-roms` 3,367 passed / 0 failed; AccuracyCoin 144/144, nestest 0-diff; co-simulation 175/0/1 on-die, 176/0/1 off-die. **No hardware has run any bitstream.** Plan: `to-dos/plans/v2.9.8-vanguard-plan.md` | @@ -201,7 +201,7 @@ The 1.x line was **additive / off-by-default** — every release stayed byte-ide ## Accuracy milestones (met) -- `nestest` 0-diff, blargg / kevtris suites green, **AccuracyCoin 100.00%** -- 141/141 from **v2.0.3** through v2.6.16, and **144/144** from v2.6.18 once the re-synced catalog's last entry closed (139/139 at the v1.0.0 cut; the v2.0.1 oracle re-sync grew the catalog to 141 assigned tests and briefly opened two PPU gaps, so v2.0.1–v2.0.2 shipped an honest 139/141 until the v2.0.3 2-cycle-ALE promotion closed them), and a byte-identical 60-ROM commercial regression oracle. As of v2.3.0 the AccuracyCoin gate is pinned to an **exact 141/141** (zero failing tests), so a single-test regression — e.g. in the hybrid-address model — fails CI. `docs/STATUS.md` is the authoritative pass-count source. +- `nestest` 0-diff, blargg / kevtris suites green, **AccuracyCoin 100.00%** -- 141/141 from **v2.0.3** through v2.6.16, **144/144** from v2.6.18 once the re-synced catalog's last entry closed (overstated: the older ROM hid a `Misaligned OAM behavior` failure), and **146/146** from v3.1.0 on upstream `f5f41dc2` (139/139 at the v1.0.0 cut; the v2.0.1 oracle re-sync grew the catalog to 141 assigned tests and briefly opened two PPU gaps, so v2.0.1–v2.0.2 shipped an honest 139/141 until the v2.0.3 2-cycle-ALE promotion closed them), and a byte-identical 60-ROM commercial regression oracle. As of v2.3.0 the AccuracyCoin gate is pinned to an **exact 141/141** (zero failing tests), so a single-test regression — e.g. in the hybrid-address model — fails CI. `docs/STATUS.md` is the authoritative pass-count source. ## Git tagging diff --git a/crates/rustynes-libretro/rustynes_libretro.info b/crates/rustynes-libretro/rustynes_libretro.info index 39129e6a2..c5c2500c0 100644 --- a/crates/rustynes-libretro/rustynes_libretro.info +++ b/crates/rustynes-libretro/rustynes_libretro.info @@ -5,7 +5,7 @@ supported_extensions = "nes|fds|unf|unif" corename = "RustyNES" license = "GPLv3+" permissions = "" -display_version = "v3.0.1" +display_version = "v3.1.0" categories = "Emulator" # Hardware Information diff --git a/crates/rustynes-test-harness/golden/epoch_fingerprint.tsv b/crates/rustynes-test-harness/golden/epoch_fingerprint.tsv new file mode 100644 index 000000000..b31460365 --- /dev/null +++ b/crates/rustynes-test-harness/golden/epoch_fingerprint.tsv @@ -0,0 +1,15 @@ +# T-EPOCH-FINGERPRINT: what the panel in tests/epoch_fingerprint.rs produces +# under the epoch below. Generated; re-bless with +# RUSTYNES_BLESS_EPOCH_FINGERPRINT=1. `last_release_epoch` is the epoch +# the last release shipped, set by hand at each release cut: output may +# move only while EMULATION_EPOCH is above it. +# epoch 3 +# last_release_epoch 3 +# rom frames framebuffer_fnv audio_fnv ram_fnv cpu_cycles +accuracycoin/AccuracyCoin.nes 3200 e5edb57f59c38538 47e9857638ba5d28 b93b7feacb471f53 95268158 +blargg/apu_mixer/triangle.nes 600 1b6dac56c42f267a 87f291ef58273cac 983e19fe15e197d6 17838587 +blargg/sprite_overflow_tests/3.Timing.nes 300 1ff4fd9a87d46e5a f7f5b37c7a51cc3d d7e9312891460c14 8904391 +nes-test-roms/dmc_tests/latency.nes 240 b112947dcb56f813 2b4cacae717f270b 286ebb8ad39d4642 7117579 +blargg/mmc3_test_2/4-scanline_timing.nes 120 c1423fdfef9a85fa cceb1ba4374551ca 73baf7b42fa5860e 3543898 +nes-test-roms/sprdma_and_dmc_dma/sprdma_and_dmc_dma.nes 120 bb655ab4bc5277ac 9a8fbf6de707b966 a0779243e8ce2902 3543900 +assorted/flowing_palette.nes 120 72990514f3e3bfb3 8a91bbbb1ead50e8 27337e564377faba 3543884 diff --git a/crates/rustynes-test-harness/tests/snapshots/external_coverage__mapper_146_Sachen_NINA_Millionaire_Sachen.snap b/crates/rustynes-test-harness/tests/snapshots/external_coverage__mapper_146_Sachen_NINA_Millionaire_Sachen.snap index 9d6950645..dd1fdbbb9 100644 --- a/crates/rustynes-test-harness/tests/snapshots/external_coverage__mapper_146_Sachen_NINA_Millionaire_Sachen.snap +++ b/crates/rustynes-test-harness/tests/snapshots/external_coverage__mapper_146_Sachen_NINA_Millionaire_Sachen.snap @@ -9,5 +9,5 @@ fb_bytes=245760 cycles=37902150 audio_samples=1005322 audio_fnv1a64=f087542b1f027e9a -checkpoint f900 fb_fnv1a64=a71d0c1c59be672e +checkpoint f900 fb_fnv1a64=7b6fa7c63d09e90e checkpoint f1100 fb_fnv1a64=02035ffbeb95f997 diff --git a/docs/STATUS.md b/docs/STATUS.md index 5744c484d..7678a5a55 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,6 +1,6 @@ # RustyNES — Project Status Matrix -> **Current release: v3.0.1** (2026-10-07) — **"Mortar"**, a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** (2026-10-06) — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** (2026-10-04) — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** (2026-10-02) — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** (2026-09-30) — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** (2026-09-30) — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** (2026-09-29) — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** (2026-09-29) — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** (2026-09-29) — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. The mobile device runs and the SuperStation One board session move after v3.0.0 (maintainer, 2026-09-29). Built on **v2.9.2 "Candidate"** (2026-09-28) — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** (2026-09-27) — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** (2026-09-26) — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** (2026-09-26) — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** (2026-09-25) — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** (2026-09-25) — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. Built on **v2.8.1 "Gasket"** (2026-09-25) — the libretro core fits the frontends around it: four-player games work through a Four Score option, a controller works again after its port leaves the Zapper, RetroArch no longer reads past the core's input-descriptor list, expansion audio no longer clips, the core declares UNIF images, and the Makefile honours PREFIX, platform=win, DEBUG and CARGO_TARGET_DIR. Built on **v2.8.0 "Bulkhead"** (2026-09-25) — the libretro core stops a fault at its own boundary: an internal error no longer closes RetroArch, save states survive plugging in a Zapper, closing a game withdraws its memory maps, the core loads from any libretro frontend, and the save state now carries the 2A03 internal data bus. Built on **v2.7.6 "Recount"** (2026-09-24) — the v2.7.5 deletions measured one at a time: the six performance proposals v2.7.5 bounded only together were each measured alone, where the benchmarks reach them: five are zero, and the sixth, the pulse sweep-mute check, bounds at about 0.2%, and the cheap byte-identical way to take it measured slower; the fast render path now asserts a rendering-history invariant it used to re-write; and the libretro buildbot builds macOS again and gains 32-bit Windows, 32-bit Linux and webOS targets. Built on **v2.7.5 "Tally"** (2026-09-24) — every audit claim closed with a measurement or a reason: the core audit's twelve performance proposals were closed, eleven of them by measurement, and one adopted (the audio buffer keeps its capacity between frames); 18 dead bus methods and the unused ApuBus trait are deprecated; and the core and frontend ledgers have no open row. Built on **v2.7.4 "Pocket"** (2026-09-24) — the mobile apps survive what a phone does to them: an internal error no longer closes the Android or iOS app, battery saves persist on both, the apps pause and give up audio when they should, and saves are written so a dying phone keeps the last good one. Built on **v2.7.3 "Hearth"** (2026-09-23) — the desktop and web frontends keep what they are given: battery saves persist on the desktop, a Lua script can no longer hang or exhaust the emulator, script HTTP cannot reach local services by default, and audio survives a device change. Built on **v2.7.2 "Bankroll"** (2026-09-23) — cartridge memory, every bank a cartridge has and nothing it has not: MMC1 reaches SUROM / SXROM, MMC5 banks its PRG-RAM, Namco 163 selects nametables, and `$6000-$7FFF` reads open bus where a board has nothing there. Built on **v2.7.1 "Keepsake"** (2026-09-23) — six cartridge boards now hand RetroArch their battery save instead of an empty one, every user file the frontend writes is written atomically, and three mapper files are now recorded as derived from Mesen2 and puNES. Built on **v2.7.0 "Palisade"** (2026-09-23) — a corrupt or hand-edited save state now fails at restore with a typed error instead of crashing the emulator one tick later, pulse 1 no longer mutes on the `$4001 = $08` sweep idiom, and the save-state fuzz target can finally reach what it exists to find. Built on **v2.6.23 "Pulse"** (2026-09-20) — the access does not increment, it pulses the load already there. A `$2007` access during rendering pulses the rendering pipeline's existing load rather than computing a private increment, and resolving against that same value is what closed the CHR-during-rendering divergence in the MiSTer sibling: `chrram-live` went from **32,861 of 61,440 differing pixels**, red for eight releases, to "All 61440 pixels match", and `chrram-fetch` from 21,941 to 18. Eleven prior variants had been measured correctly and the conclusion drawn from them was wrong — they swept what the `$2007` arm computes and never what it computes it from. The emulation core is unchanged, so **AccuracyCoin 144/144 and nestest 0-diff hold by construction** and were re-run anyway. The bitstream is re-cut at fitter seed 4 (+0.421 ns setup / +0.112 ns hold) and **no hardware has run it**. The board session is now **v2.9.2** and the hardware-verified core **v3.0.0**, after three audit lines ([ADR 0041](adr/0041-hardware-release-is-v3.0.0.md)). Built on **v2.6.22 "Rigging"** — the instruments for the board, built before the board. **No hardware has run any bitstream**; the session with the board (then planned as "Shakedown"; since ADR 0041, v2.9.2) is the hardware half and this is the non-hardware half of it, cut separately so that name keeps meaning what it says. AccuracyCoin now reads back from hardware as BYTES rather than as a photograph — a mirror ROM copies its result vector into the battery-backed save window, with a byte budget that proves the patch moved nothing and a simulation control that proves it changed no answer. The catalog turned out to have **149 rows and 144 results**: five `Power On State` rows share upstream's omit-sentinel, and counting them made the headline a function of *when the run was sampled*. **144/144 and nestest 0-diff are unchanged** — the count stopped depending on the sampling instant. And the `46199ae4` re-sync turned out to have been half-done: three goldens described a ROM no longer in the tree, which no gate could see because `rom_sha256` was written into every manifest and compared to nothing. Built on **v2.6.21 "Steward"** — the board arrives, and the core is not ready for it. Battery saves exist for the first time; the CHR-during-rendering gate the retrospective audit asked for is added and is **RED at 32,861 of 61,440 pixels**, with the obvious fix measured insufficient at 715 recovered; two gates existed that nothing ran; and the deploy loop the runbook prescribed was built, which found two defects in its own spec. The emulation core is unchanged, so **AccuracyCoin 144/144 and nestest 0-diff hold by construction**. Built on **v2.6.20 "Telltale"** — the counter had no reader, and two knobs turned out to be one decision. The FPGA core's OAM2 address counter was write-only for a release, so `$2004`'s post-fetch rest value was a hardcoded index 0; making it observable took the DUT to **AccuracyCoin 149 of 149** and the co-simulation ladder to **150 of 150, 0 failed**. Restoring the increment window v2.6.19 had narrowed then broke sprite rendering by six pixels, because the window and the deleted dot-257 latch are ONE decision with two self-consistent answers and only one of them is the hardware's. The shipped configuration restores the latch: window 256, the dot-257 freeze latch back and consumed by sprite fetch, and `$2004` reading the live counter. A CHR-RAM write gate was added. **v2.6.21 corrects this sentence: it did NOT close the coverage the audit named.** The audit named writes *while rendering is enabled*, and PROGRAM53 writes with `PPUMASK = 0` and renders afterwards. The real case is gated at v2.6.21 and is RED -- 32,861 of 61,440 pixels. The emulation core is unchanged, so **144/144 and nestest 0-diff hold by construction**. Built on **v2.6.19 "Accession"** — the DUT absorbs two releases of oracle behaviour, and the seed rule catches something for the first time. The FPGA core implements the OAM2 address counter it never had and stops acting on a `$2001` change that lands during dot 256 -- AccuracyCoin on the DUT **148 of 149**, the ladder **147 of 147**. The fitter pin is re-derived over ten seeds and stays at 3. (The contemporaneous claim that seed 1 stopped closing is withdrawn -- that sweep recompiled in place and was order-dependent; seed 1 closes, and seed 8 is the one that does not.) The emulation core is unchanged, so **AccuracyCoin 144/144 and nestest 0-diff hold by construction**. Built on **v2.6.18** (2026-09-12) — **"Errata"**, the recorded cause was wrong in three ways, and the last AccuracyCoin entry closes at 144/144 -- a `$2001` change during dot N must not act on dot N, at the dot-256 vertical increment and the dot-339 OAM2 reset. Built on **v2.6.17** (2026-09-11) — **"Terminus"**, the accuracy battery grows to 144 assigned tests and one of the new ones names the dots a write lands on -- this core is two dots early, measured twice on two independent `$2001` writes, and moving it is refuted by the gate this version wrote in advance, so the documented compensation stays and 143 of 144 ships. Built on **v2.6.16 "Interlock"** (2026-09-04) — the arbiter's numbers describe a stimulus, not the console -- and the console meets the CPU's deadline at zero margin, on every one of 240,303 requests, where the gate said it missed by four. Built on **v2.6.15 "Warrant"** (2026-09-04) — the claims v2.7.0 will make become checkable, and the instrument pays the oracle back. Built on **v2.6.14 "Docket"** (2026-09-03) — the submission checklist becomes auditable, and auditing it finds five boxes already true, two ticked on evidence that expired, and one that could never have been ticked honestly. v2.7.0 IS the submission, so the list in `to-dos/mister/contribution-checklist.md` decides whether the core is ready -- and it had 30 boxes, 16 unticked, and FOURTEEN OF THOSE SIXTEEN saying nothing at all about why. That ambiguity is the defect: an unticked box with no reason cannot be told apart from work outstanding, work blocked outside this repository, and WORK ALREADY DONE AND NEVER TICKED, and the third case occurred five times -- the provenance CI job, the SPDX sweep, the firewall statement, the AccuracyCoin vector and the preservation-value case were all true and all unticked, so the list reported the project as further from submission than it is by a fifth of its own length. ONE BOX COULD NEVER HAVE BEEN TICKED HONESTLY: it asked that `docs/provenance.md` state "that no NES core was ever opened", and that same document's section Do not self-certify forbids exactly that class of finished claim, so satisfying the box required writing the one sentence the provenance rules exist to prevent -- a wrong requirement rather than a missing tick, and only reading every item found it. RE-MEASURING THE TICKED HALF found two more, which is v2.6.9's lesson in a different document: `RustyNES.sdc` still said "there is exactly one core clock", true of v2.6.3 and false since v2.6.13, which added a 4x SDRAM clock and a phase-shifted pin clock that the shipped timing report names alongside it; and the `.qsf` entry said all 109 pin assignments come from `sys/sys.tcl`, where 109 is what that script supplies before stopping short of the I/O board, so a core must also source one of two 36-assignment variants -- 145 in total, from two scripts. THE ALARMING READING OF THE FIRST WAS CHECKED BEFORE IT WAS WRITTEN DOWN: it looks as though the framework's `set_clock_groups -exclusive` might be cutting the console-to-SDRAM crossing and leaving ADR 0039's safety argument unfalsifiable, and it is not -- exclusive cuts paths BETWEEN groups, all three outputs match one glob, and v2.6.13's own -24.769 ns measurement of that crossing is only observable because those paths are analysed. THE NAMING DIVERGENCE IS MEASURED RATHER THAN PARAPHRASED, from `Main_MiSTer/file_io.cpp` instead of the wiki: `get_display_name` searches for the literal underscore-two-zero and TRUNCATES THE DISPLAY NAME THERE, taking the rest as a datecode, and `DirentComp` groups by that truncated name -- so a version-named bitstream makes every release a SEPARATE CORE ENTRY, named for the version rather than the core, ordered alphabetically, which puts v2.6.9 after v2.6.13. The fix is one line and is NOT taken here, because version-naming is a maintainer decision with a stated rationale and reversing it is not an audit's call. THE TASK BOARD HAD THE SAME DEFECT AND ONE ROW WORSE: four delivered items never ticked, a hardware row still naming a version seven releases past, and an SDRAM row whose precondition -- "after a board exists" -- v2.6.13 simply did not follow, having accepted the controller against a behavioural model written from the datasheet instead; that is rung 7's own recorded lesson repeating one row below where it is written, since the blocker applied to hardware ACCEPTANCE rather than to building the thing. A CLAIM v2.6.13 SHIPPED IS RETRACTED: answering a review finding, I wrote that MMC1 was the sixth approved family and not yet implemented, and the cartridge decodes mapper 1 at three sites with a registered gate green since rung 7 opened -- asserted from memory inside a reply correcting somebody else's reading of the same line. The gate that keeps all this true asserts a SHAPE rather than a judgement and is demonstrated by five mutations, one of which proves the continuation-line folding load-bearing by producing nine false violations without it. The bitstream is BYTE-IDENTICAL to v2.6.13's, which is the point: the only sibling change is a comment, and an identical artifact demonstrates it. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on **v2.6.13 "Slack"** (2026-09-03) — the cartridge outgrows the die, and three consumers want the same bus. An SDR SDRAM controller, a behavioural part model, a four-way arbiter and a console bridge, all written from the AS4C32M16SB-7 datasheet revision 1.4 -- no third-party controller read, ADR 0037 applying. The budget the previous step worked to was a single figure read off the fetch structure and never measured; `nes_top`'s `CHR_LAT` sweep asks the console directly, and there are THREE answers: a background or sprite fetch tolerates 28 cycles and uses 17, the PPUDATA data port tolerates 8, and the CPU sampling at mc7 has 24 -- four cycles of every budget going to the console-domain crossing, which is not optional, because publishing a runtime modulo combinationally into an 11.64 ns domain costs -24.769 ns of setup. The PPUDATA port could never fit, half its budget being the crossing, so it leaves the shared bus entirely: `ppu2c02` gains `BUFFER_HANDSHAKE` and fills its read buffer through a port of its own on the arbiter, affordable because the CPU does not read that buffer until its next PPUDATA access. THAT FIX SHIPPED A DEFECT ONLY A BANKED CARTRIDGE COULD SEE -- the request carried the RAW fourteen bits the PPU presents, which is right by coincidence on NROM because the mapper's translation is the identity there, and reads BANK ZERO on everything else; `ppu-misc-2007-stress` passed off-die throughout while the two CNROM gates read a zero byte where the oracle reads one, one and two. The fix REMOVES the address rather than correcting it: `cart.sv` already publishes the translation for the fetch path, so the request carries none and there is one source of truth for where CHR lives instead of two that agree only on NROM. A DELETION WAS THEN REFUTED BY ONE CYCLE: with the address fixed, a control issuing at a flat +7 passed both gates, which read as the anti-contention deferral buying nothing, so it was removed -- and the deployed code issued at +6, and both gates failed again. The control and the code differed by a single cycle, which is the measurement of how thin the CPU's off-die deadline is and the concrete argument for scheduling the bus rather than arbitrating it. Two INFERRED LATCHES that Verilator cannot see: `ppu2c02` assigned two signals only under `BUFFER_HANDSHAKE`, so in the shipped on-die build the only assignment either reached was the reset branch, and a variable holding its previous value on every live path is a latch -- Quartus said so twice while the lint gate stayed green, and the comment beside them asserted the defect as a virtue ("outside BUFFER_HANDSHAKE neither signal ever moves", which is true and is exactly the condition). And a defect in the HARNESS: `USE_SDRAM` reaches Verilator as `-G` rather than as a file, so make ran ONE binary under both configurations' names and a log labelled on-die was the off-die build, reproducing the off-die failures exactly -- closed with a stamp-file prerequisite, demonstrated by mutation. THE OPEN ROW: accesses no longer auto-precharge, a hit costs 6 cycles against 10 for a miss, tRAS's 120 us MAXIMUM is respected by an early close in idle, and two defects came out of the rewrite -- re-entering idle with the request still asserted issued every access TWICE, and subtracting one from CAS the way the other waits are computed broke every read to all zeroes, CAS being "data appears at cycle N" rather than a command-to-command gap. Two MiSTer tickets close: `sdram_sz` is consumed VALIDITY BIT FIRST, so `absent` is deliberately not `!present` and a power-on all zeroes cannot read as "no board" (gated exhaustively over all 65,536 values), and `status_menumask` is computed rather than tied off, greying Reset whenever the console is already held in reset. OFF THE DIE THE CONSOLE PASSES 142 OF 142, every gate the on-die build passes, with better timing margin and 384 fewer M10K blocks -- and it still SHIPS ON the die, because an off-die core cannot run at all without the SDRAM add-on while rung 7's five mapper families fit on the die at 468 of 553 blocks. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on **v2.6.12 "Groundwork"** (2026-09-02) — the bitstream was an NROM-only console. Rung 7 landed five mapper families and 142 co-simulation gates verify them, and the layer that turns that RTL into a bitstream was never told: `rtl/emu.sv` left `cart_mapper`, `cart_prg_16k_banks` and `cart_chr_8k_banks` unconnected, so Quartus tied all three to GND -- mapper 0 for EVERY cartridge, `prg_8k_count = 0` collapsing PRG to an 8 KiB window, and CHR forced to RAM. The declared 256 KiB PRG and 128 KiB CHR were implemented as 8 KiB each; connecting three wires takes block memory from 666,061 to 3,680,717 bits and timing still closes at all four corners. NOTHING COULD HAVE CAUGHT IT: simulation cannot, because `emu.sv` is not in the testbench file list and the harness drives those ports itself, so all 142 gates exercised a correctly-configured cartridge; and Quartus DID say so three times, in messages that cite an INSTANCE path rather than a file and are absent from the "0 errors, N warnings" tally, so the existing checker read 0 of 125. Two gates close that -- one fails on an unconnected pin of any module this repository declares, the other pins the warning SET rather than its count -- and both are demonstrated to fail by mutation. The `hps_io` tie-off audit that followed raised nine `T-MISTER-*` tickets, and annotating each with a blocker turned "none of these landed" into a measurement: not one is blocked on EFFORT, so the list is the rung-6 agenda rather than a backlog. `T-MISTER-SAVE` was attempted and refuted -- every save route terminates in `hps_io`, which no gate here instantiates. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on **v2.6.11 "Exposure"** (2026-09-02) — a picture is a gate the ladder did not have. All 141 co-simulation gates THEN IN THE SUITE were green (it ends this release at 142, the one it added) and TWO OF SIX commercial games rendered wrong -- a CHR-RAM write was taking the shared-pin composite address built for FETCHES instead of `v`, so the layout was right and the tiles were scrambled. The split is exactly CHR-ROM against CHR-RAM, which named the mechanism before any tracing, and a CONTROL says it is not v2.6.10's regression: the pre-M10K-fix RTL differs by the IDENTICAL 16,565 pixels, so the defect dates from the cartridge landing in v2.6.9. It was not UNREACHED -- the DUT asserts `chr_wr` 9,600 times in the Battletoads run -- it was UNCOMPARED: only THREE of the 141 gates compare a framebuffer, all three ship CHR-ROM, every other gate is CPU-side, and AccuracyCoin, the widest gate in the suite, is CHR-ROM too. The rung-7 gates' own comment says what they are for -- "these gates are about BANKING and nothing else" -- and it was accurate, and it was the whole coverage. Six commercial titles now render byte-identically to the oracle over all 61,440 pixels, published as a montage built by a script that REFUSES to publish a tile that differs from the oracle. The v2.6.10 bitstream carries the defect and a published version is immutable, so the corrected `.rbf` ships here. The same release finds EIGHT release leads describing v2.6.10 with v2.6.9's summary, and v2.6.9 gone from the lineage entirely, with `docs/STATUS.md` naming v2.6.10 under the codename "Abeyance" -- every existing check passing CORRECTLY, because they pin the version TOKEN and the token was right. Prose cannot be audited; an ORDERING can, so two gates are added and both are demonstrated to fail by mutation. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Rung 6 does NOT close -- no DE10-Nano and no SuperStation One are attached to this machine, confirmed by checking rather than assumed. Built on **v2.6.10 "Inference"** (2026-09-01) — the cartridge meets the synthesiser. Five cartridge boards verified across 141 co-simulation gates had **never been through Quartus**, and Analysis & Synthesis refused the design: `chr` was written from TWO `always_ff` blocks, which cannot infer as one M10K, so 128 KB of CHR stayed in flip-flops -- **1,048,576 registers against roughly 166,000**. Simulation cannot ask this question: Verilator accepts both forms without complaint. It is v2.6.6's finding one layer out -- that release established that an M10K read is REGISTERED, this one that a correctly registered memory still will not infer with two writers. The fitter was also throttling itself under Auto Fit while the `.qsf` carried no optimisation assignments at all: at full effort **all six seeds close** where two had failed, so the effort settings move the whole distribution across zero and the seed only picks where in it you land -- and the project had been pinned to seed 4, the WORST of the six. Pinned at seed 3, +0.531 ns setup and +0.099 ns hold, byte-identical across two independent compiles. The bitstream v2.6.9 could not produce ships here. Built on **v2.6.9 "Abeyance"** (2026-08-31) — an exclusion hides improvement as well as regression, and both denied co-simulation streams close. The larger one was never the console: `apuconflict039` had been carried for seven releases as a declared diagnostic whose bus surface "carries nine divergences BY DESIGN", and the nine were a defect in the HARNESS -- on a cycle the CPU is held, the testbench built its record's bus data from a stale local rather than from the RTL's own latch. Taking it from the latch makes the stream IDENTICAL on all 357,361 overlapping cycles and all 88 checkpoints, and the local is now dead and deleted. The phrase "by design" is what stopped anyone re-checking it, because it reads as a property of the thing under test when it was a property of the instrument reading it. The other stream differs on EXACTLY ONE cycle, a documented and attributed OAM-corruption asymmetry -- and carrying that needed an instrument the suite did not have, because the PLANNED mechanism was refuted by its own mutation pass: an allowance by checkpoint index cannot work on a rolling hash, since one divergent cycle poisons every checkpoint after it, so allowing the first differing window simply moved the failure to the next one and allowing the rest is the all-or-nothing deny it was meant to replace. A per-cycle nine-field comparator with a scoped allowance costs ONE cycle of coverage instead of seventy-one checkpoints -- 357,360 of 357,361, against nothing at all before -- and it fails BOTH ways, so a DUT that improves cannot leave a stale allowance quietly hiding coverage; six mutations confirm it, including a cycle outside the compared window being REFUSED rather than allowed to match nothing. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction and were re-run anyway. Rung 6 does NOT close -- no DE10-Nano and no SuperStation One are attached to this machine, confirmed by checking rather than assumed. Built on **v2.6.8 "Arrears"** (2026-08-31) — the gates the previous release fixed and never widened. A deny list is an assertion about the THING UNDER TEST, so v2.6.7 changing both the DUT and the harness re-opened every exclusion -- and nothing re-measured one. FOUR of the six denied checkpoint streams were passing (`irqlat048`, `ppuvbl023`, `ppuvbl024`, `ppuvbl025`), and THREE of those were not run by the suite at all, so removing them from the deny list was INERT until they were also iterated. The nestest gate compared 265,000 cycles against a 5,062,688-cycle golden -- correct while caveat C6 was open, left behind the moment it closed -- and all 5,062,680 overlapping cycles now match, with the window DERIVED from the manifest. CAVEAT C4 CLOSES by demonstration: nestest raises `nmi_line` on 3,592 cycles and had no nine-field comparison at all, so wiring it makes an `o.nmi_line` mutation CAUGHT at checkpoint 28 of 1,237 while the bus gate passes the identical run. Suite 128 passed, 0 failed, 0 skipped; nine-field comparisons 51 -> 57. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Rung 6 does NOT close -- no board is attached, confirmed by checking. Built on **v2.6.7 "Detent"** (2026-08-30) — the bitstream becomes a published release artifact and a one-cycle disagreement is pinned to the cycle it happens on. Every release from here ships a `.rbf` -- committed to the sibling's `releases/` and attached to the GitHub release on BOTH repositories -- reversing v2.6.6, which produced one and withheld it because no hardware had run it: the MiSTer distribution mechanism reads that path out of the REPOSITORY, so an empty `releases/` describes an undistributable core rather than a cautious one, and the caution moves from an absence into a disclosure naming what the ladder cannot reach by construction (the PPU gate compares the pre-palette index and the APU gate per-channel integer levels, so the palette, the video timing constants, the audio absolute level and its band-limiting all sit downstream of every gate). The build is REPRODUCIBLE and that is now measured rather than argued -- a from-scratch compile and an incremental one produce a byte-identical bitstream -- which is also how v2.6.6's published slack figures came to be WITHDRAWN: no corner of a clean rebuild reproduces them, the innocent explanation (a different timing corner) was checked first and refuted, and the correct pair is +0.108 ns setup and +0.042 ns hold at the binding corner. THE RELEASE GATE WAS READING THE WRONG CORNER -- Slow 100C is not the binding one on this design, so a bitstream failing at Slow -40C would have passed while the gate reported three times the real margin -- and the checker that reads it was wrong twice before mutation found both: it first extracted ZERO rows from both summary tables and reported that as "no negative slack", then, once fixed, reported FOURTEEN clocks from a report emptied of its data, having run past the closing rule into the next tables. Caveat C2 splits in two. The first residual was a TRACE OBSERVATION POINT -- the harness built its record after eight of a CPU cycle's twelve master clocks while the oracle reads at end-of-cycle, and the frame-counter interrupt asserts on the final edge -- and closing it took checkpoint comparisons from 3 to 11 and failures at one checkpoint from 30 to 3. The second is REAL, and the FIRST fix for it was REFUTED in a way that found the right one: moving all four effects of the write one cycle later to match the oracle drops blargg from 11/11 to 4 of 11, one of the seven being the ROM written to probe exactly that timing. Read as a measurement that PROVES the sequencer's maturation is correctly placed, which leaves only the other effects the same write schedules -- so separating ONLY the interrupt clear lands it at write+3, the documented cycle, while the frame counter's zeroing stays put. Checkpoint streams go from 11 identical to 52 of 58 and the suite from 87 to 122, with blargg still 11/11 and the bus still matching on all 2,680,239 overlapping cycles. The checkpoint gate is registered over 51 comparisons and STATES ITS BLIND SPOT: not one gated golden ever raises an NMI, so it cannot catch an nmi_line defect, and the attempt to close that hole found a FOURTH divergence cluster that three goldens had been hiding inside a "26 skipped" tally line. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Rung 6 does NOT close -- no DE10-Nano and no SuperStation One are attached to this machine, confirmed by checking rather than assumed. Built on **v2.6.6 "Chassis"** (2026-08-29) — the console becomes a MiSTer core -- `sys/` vendored byte-identical against `Template_MiSTer@3ea1134c` (57 files, 0 content differences), a top level, a clock, a palette, video sync and an audio mixer, compiled by Quartus 17.0.2 into a Cyclone V bitstream with 0 errors, timing CLOSED, and a warning count taken from 111 to THREE -- all three inside the vendored framework or Quartus's own megafunction, none of them citing this project's RTL (worst setup +0.086 ns and worst hold +0.096 ns at the binding corners, seed 3 -- v2.6.7 also withdraws the +0.363/+0.245 v2.6.6 published, which a clean rebuild of that configuration reproduces at no corner, TNS 0.000 on every clock; the console's own clock +13.514 ns at an Fmax of 30.26 MHz against the 21.477272 MHz it needs). The emulation core is unchanged, so the co-simulation suite is an ACCEPTANCE CRITERION rather than a formality -- 87 passed, 0 failed -- and it earned that immediately, because the cartridge memories had to be rewritten: an M10K read is REGISTERED, so 40 KiB of asynchronously-read cartridge was 393,216 registers against roughly 166,000 available, under a comment claiming it inferred block RAM from the source style alone, and the README had stated the correct rule since v2.4.3. Two defects were found only by asking whether the outputs would work on real hardware: the audio would have been a full-scale DC rail, because the mixer output is unipolar with silence at zero and the framework maps unsigned silence to -32768, and two OSD scanline options did nothing because VGA_SL was tied to zero. And a convention enforced by a glob has no error message: sys_top.sdc groups the core clock by matching the hierarchical name pattern *|pll|pll_inst|altera_pll_i|*, so a differently-named PLL matched no group at all and every crossing to the framework audio, HDMI and HPS domains was analysed as synchronous -- -13.901 ns of slack and -422,601 ns of TNS on a design whose Fmax was already above requirement, with the compile succeeding and the Assembler reporting 0 errors and 0 warnings throughout. Built on **v2.6.5 "Muster"** (2026-08-29) — rung 5 closes — the AccuracyCoin status vector is identical entry for entry across all 146 entries, with 146 of 146 executed on both sides and none NotRun, where the same gate read 5 of 146 at the version's start. A muster is a roll call where every name is called AND answered, which is the two-clause acceptance exactly. Five PPU defects close the last six differing entries and four were invisible to every gate that existed when the version opened: the background shift registers' RELOAD and their shift clock need SEPARATE gates (with one shared gate the serial-in test was not merely failing but ARITHMETICALLY UNREACHABLE, since reload dots are absolute and the reload discards the low seven bits, so a serial-in one can never reach bit 7 on any alignment — and modelling both structures reproduces BOTH measured shifter values); the sprite X counters are NOT gated on rendering, which AccuracyCoin states outright and the ROM that states it passes either way, because it expects no hit at X=254 and a sprite shoved 18 dots right is also off the line; the PPUADDR second-write v-copy is DELAYED, as the wiki says inside the write sequence itself, swept 1 to 4 dots against a control at 8 and 12 that fails; and the pre-render line CLEARS secondary OAM, without which scanline 0 draws what scanline 239 left — no sprite can ever render on scanline 0, because OAM Y is one less than the display row, and a sprite-0 probe over the full 134 M-cycle battery found 24 hits with four of them there; and the octal latch holding across the read dot, which is verified by exactly ONE gate and was unverifiable until the v-copy delay landed, the two composing the hybrid address together and neither producing it alone. A DIAGNOSIS IS RETRACTED: the residual was read as a two-dot CPU/PPU alignment error from comparing dot spans across two instruments, and at the committed alignment the two consoles execute identical pc, bus_addr and bus_access for 1,695,131 cycles while a two-dot shift moves the first fork back to 593,228 and takes the differing share from 5.13% to 66.80%. The oracle changes on the default path, so AccuracyCoin 141/141 (RAM decoder) and nestest 0-diff are VERIFIED, not asserted. Built on **v2.6.4 "Rubric"** (2026-08-26) — OAM DMA lands and all nine AccuracyCoin disagreements close, every rule that closed the last three stated by the test ROM and by neither nesdev page — and then the gate that certified them is measured to cover 88 of 146 entries. The emulation core is unchanged. Built on **v2.6.3 "Mainspring"** (2026-08-25) — the DUT runs on one master clock, and four enables that were never enabling — plus AccuracyCoin end to end and a status vector that names its disagreements by test. The emulation core is unchanged. Built on **v2.6.2 "Witness"** (2026-08-24) — rung 4 closes: blargg APU battery 11/11 on the co-simulation DUT, six defects no self-written gate could see, and a suite that had been asserting nothing for five minor releases. The emulation core is unchanged. Built on **v2.6.1 "Interleave"** (2026-08-24) — the DMC and its DMA cycle steal in the MiSTer co-simulation DUT, cycle-exact on the bus. The emulation core is unchanged. Built on **v2.6.0 "Assay"** (2026-08-24) — the triangle, the noise channel and the sweep unit **in the MiSTer co-simulation DUT** — and an audit of how much of the APU was fitted to the oracle rather than derived from documentation. The emulation core is unchanged. Built on **v2.5.9 "Overture"** (2026-08-24) — rung 4 opens: the two pulse channels, the frame counter, and four ROM defects the stimulus measurement found first. Built on **v2.5.8 "Blanking"** (2026-08-24) — VBlank, NMI and the PPUSTATUS race close rung 3 — and both fixes were deletions. Built on **v2.5.7 "Collimation"** (2026-08-24) — sprite rendering closes exact — the phase was wrong by two dots, and every window was compensating. Built on **v2.5.6 "Vestige"** (2026-08-23) — Sprite evaluation closes: all 59,993 overlapping cycles match, nine of nine behavioural mutants caught and two proved inert (announced as seven of eight at the cut), and the fix is a byte index that outlives the walk that set it. Built on **v2.5.5 "Raster"** (2026-08-23) — the first full frame, and three blind spots in the stimulus that fed it. Built on **v2.5.4 "Escapement"** (2026-08-23) — the background fetch pipeline, and an access two dots early that five gates could not see. Built on **v2.5.3 "Hysteresis"** (2026-08-23) — toggling rendering takes effect three dots after the write, and four instruments to prove it. Built on **v2.5.2 "Dormant"** (2026-08-23) — the 2C02 register file, and a gate that passed while testing nothing. Built on **v2.5.1 "Retrace"** (2026-08-23) — the interrupt sweep closes rung 2, and a gate reported a pass it could not have earned. Built on **v2.5.0 "Rungwork"** (2026-08-23) — the 6502 rung, and the two gates it cannot reach. Built on **v2.4.9 "Plumbline II"** (2026-08-23) — the bus half of rung 2, and what it found the day it existed. Built on **v2.4.8 "Palimpsest"** (2026-08-23) — read-modify-write, and a gate that cannot see its own subject. Built on **v2.4.7 "Keystone"** (2026-08-23) — the stack closes, and a dead line proves itself dead. Built on **v2.4.6 "Abacus"** (2026-08-22) — the core learns arithmetic. Built on **v2.4.5 "Compass"** (2026-08-22) — the core reaches memory, and chooses. Built on **v2.4.4 "Ignition"** (2026-08-22) — the first real RTL -- the 6502's eight-cycle reset and the implied opcode group, matching the oracle on all seven CPU fields (29 +> **Current release: v3.1.0** (2026-10-08) — **"Bellwether"**, the AccuracyCoin re-sync and an honest 146/146, the CPU overclock and the sprite-limit option in movies and netplay, PAL emphasis, the alternate MMC3, rewind and run-ahead on the Vs. cabinet, and the MiSTer core matched to all of it. Built on **v3.0.1 "Mortar"** (2026-10-07) — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** (2026-10-06) — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** (2026-10-04) — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** (2026-10-02) — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** (2026-09-30) — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** (2026-09-30) — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** (2026-09-29) — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** (2026-09-29) — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** (2026-09-29) — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. The mobile device runs and the SuperStation One board session move after v3.0.0 (maintainer, 2026-09-29). Built on **v2.9.2 "Candidate"** (2026-09-28) — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** (2026-09-27) — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** (2026-09-26) — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** (2026-09-26) — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** (2026-09-25) — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** (2026-09-25) — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. Built on **v2.8.1 "Gasket"** (2026-09-25) — the libretro core fits the frontends around it: four-player games work through a Four Score option, a controller works again after its port leaves the Zapper, RetroArch no longer reads past the core's input-descriptor list, expansion audio no longer clips, the core declares UNIF images, and the Makefile honours PREFIX, platform=win, DEBUG and CARGO_TARGET_DIR. Built on **v2.8.0 "Bulkhead"** (2026-09-25) — the libretro core stops a fault at its own boundary: an internal error no longer closes RetroArch, save states survive plugging in a Zapper, closing a game withdraws its memory maps, the core loads from any libretro frontend, and the save state now carries the 2A03 internal data bus. Built on **v2.7.6 "Recount"** (2026-09-24) — the v2.7.5 deletions measured one at a time: the six performance proposals v2.7.5 bounded only together were each measured alone, where the benchmarks reach them: five are zero, and the sixth, the pulse sweep-mute check, bounds at about 0.2%, and the cheap byte-identical way to take it measured slower; the fast render path now asserts a rendering-history invariant it used to re-write; and the libretro buildbot builds macOS again and gains 32-bit Windows, 32-bit Linux and webOS targets. Built on **v2.7.5 "Tally"** (2026-09-24) — every audit claim closed with a measurement or a reason: the core audit's twelve performance proposals were closed, eleven of them by measurement, and one adopted (the audio buffer keeps its capacity between frames); 18 dead bus methods and the unused ApuBus trait are deprecated; and the core and frontend ledgers have no open row. Built on **v2.7.4 "Pocket"** (2026-09-24) — the mobile apps survive what a phone does to them: an internal error no longer closes the Android or iOS app, battery saves persist on both, the apps pause and give up audio when they should, and saves are written so a dying phone keeps the last good one. Built on **v2.7.3 "Hearth"** (2026-09-23) — the desktop and web frontends keep what they are given: battery saves persist on the desktop, a Lua script can no longer hang or exhaust the emulator, script HTTP cannot reach local services by default, and audio survives a device change. Built on **v2.7.2 "Bankroll"** (2026-09-23) — cartridge memory, every bank a cartridge has and nothing it has not: MMC1 reaches SUROM / SXROM, MMC5 banks its PRG-RAM, Namco 163 selects nametables, and `$6000-$7FFF` reads open bus where a board has nothing there. Built on **v2.7.1 "Keepsake"** (2026-09-23) — six cartridge boards now hand RetroArch their battery save instead of an empty one, every user file the frontend writes is written atomically, and three mapper files are now recorded as derived from Mesen2 and puNES. Built on **v2.7.0 "Palisade"** (2026-09-23) — a corrupt or hand-edited save state now fails at restore with a typed error instead of crashing the emulator one tick later, pulse 1 no longer mutes on the `$4001 = $08` sweep idiom, and the save-state fuzz target can finally reach what it exists to find. Built on **v2.6.23 "Pulse"** (2026-09-20) — the access does not increment, it pulses the load already there. A `$2007` access during rendering pulses the rendering pipeline's existing load rather than computing a private increment, and resolving against that same value is what closed the CHR-during-rendering divergence in the MiSTer sibling: `chrram-live` went from **32,861 of 61,440 differing pixels**, red for eight releases, to "All 61440 pixels match", and `chrram-fetch` from 21,941 to 18. Eleven prior variants had been measured correctly and the conclusion drawn from them was wrong — they swept what the `$2007` arm computes and never what it computes it from. The emulation core is unchanged, so **AccuracyCoin 144/144 and nestest 0-diff hold by construction** and were re-run anyway. The bitstream is re-cut at fitter seed 4 (+0.421 ns setup / +0.112 ns hold) and **no hardware has run it**. The board session is now **v2.9.2** and the hardware-verified core **v3.0.0**, after three audit lines ([ADR 0041](adr/0041-hardware-release-is-v3.0.0.md)). Built on **v2.6.22 "Rigging"** — the instruments for the board, built before the board. **No hardware has run any bitstream**; the session with the board (then planned as "Shakedown"; since ADR 0041, v2.9.2) is the hardware half and this is the non-hardware half of it, cut separately so that name keeps meaning what it says. AccuracyCoin now reads back from hardware as BYTES rather than as a photograph — a mirror ROM copies its result vector into the battery-backed save window, with a byte budget that proves the patch moved nothing and a simulation control that proves it changed no answer. The catalog turned out to have **149 rows and 144 results**: five `Power On State` rows share upstream's omit-sentinel, and counting them made the headline a function of *when the run was sampled*. **144/144 and nestest 0-diff are unchanged** — the count stopped depending on the sampling instant. And the `46199ae4` re-sync turned out to have been half-done: three goldens described a ROM no longer in the tree, which no gate could see because `rom_sha256` was written into every manifest and compared to nothing. Built on **v2.6.21 "Steward"** — the board arrives, and the core is not ready for it. Battery saves exist for the first time; the CHR-during-rendering gate the retrospective audit asked for is added and is **RED at 32,861 of 61,440 pixels**, with the obvious fix measured insufficient at 715 recovered; two gates existed that nothing ran; and the deploy loop the runbook prescribed was built, which found two defects in its own spec. The emulation core is unchanged, so **AccuracyCoin 144/144 and nestest 0-diff hold by construction**. Built on **v2.6.20 "Telltale"** — the counter had no reader, and two knobs turned out to be one decision. The FPGA core's OAM2 address counter was write-only for a release, so `$2004`'s post-fetch rest value was a hardcoded index 0; making it observable took the DUT to **AccuracyCoin 149 of 149** and the co-simulation ladder to **150 of 150, 0 failed**. Restoring the increment window v2.6.19 had narrowed then broke sprite rendering by six pixels, because the window and the deleted dot-257 latch are ONE decision with two self-consistent answers and only one of them is the hardware's. The shipped configuration restores the latch: window 256, the dot-257 freeze latch back and consumed by sprite fetch, and `$2004` reading the live counter. A CHR-RAM write gate was added. **v2.6.21 corrects this sentence: it did NOT close the coverage the audit named.** The audit named writes *while rendering is enabled*, and PROGRAM53 writes with `PPUMASK = 0` and renders afterwards. The real case is gated at v2.6.21 and is RED -- 32,861 of 61,440 pixels. The emulation core is unchanged, so **144/144 and nestest 0-diff hold by construction**. Built on **v2.6.19 "Accession"** — the DUT absorbs two releases of oracle behaviour, and the seed rule catches something for the first time. The FPGA core implements the OAM2 address counter it never had and stops acting on a `$2001` change that lands during dot 256 -- AccuracyCoin on the DUT **148 of 149**, the ladder **147 of 147**. The fitter pin is re-derived over ten seeds and stays at 3. (The contemporaneous claim that seed 1 stopped closing is withdrawn -- that sweep recompiled in place and was order-dependent; seed 1 closes, and seed 8 is the one that does not.) The emulation core is unchanged, so **AccuracyCoin 144/144 and nestest 0-diff hold by construction**. Built on **v2.6.18** (2026-09-12) — **"Errata"**, the recorded cause was wrong in three ways, and the last AccuracyCoin entry closes at 144/144 -- a `$2001` change during dot N must not act on dot N, at the dot-256 vertical increment and the dot-339 OAM2 reset. Built on **v2.6.17** (2026-09-11) — **"Terminus"**, the accuracy battery grows to 144 assigned tests and one of the new ones names the dots a write lands on -- this core is two dots early, measured twice on two independent `$2001` writes, and moving it is refuted by the gate this version wrote in advance, so the documented compensation stays and 143 of 144 ships. Built on **v2.6.16 "Interlock"** (2026-09-04) — the arbiter's numbers describe a stimulus, not the console -- and the console meets the CPU's deadline at zero margin, on every one of 240,303 requests, where the gate said it missed by four. Built on **v2.6.15 "Warrant"** (2026-09-04) — the claims v2.7.0 will make become checkable, and the instrument pays the oracle back. Built on **v2.6.14 "Docket"** (2026-09-03) — the submission checklist becomes auditable, and auditing it finds five boxes already true, two ticked on evidence that expired, and one that could never have been ticked honestly. v2.7.0 IS the submission, so the list in `to-dos/mister/contribution-checklist.md` decides whether the core is ready -- and it had 30 boxes, 16 unticked, and FOURTEEN OF THOSE SIXTEEN saying nothing at all about why. That ambiguity is the defect: an unticked box with no reason cannot be told apart from work outstanding, work blocked outside this repository, and WORK ALREADY DONE AND NEVER TICKED, and the third case occurred five times -- the provenance CI job, the SPDX sweep, the firewall statement, the AccuracyCoin vector and the preservation-value case were all true and all unticked, so the list reported the project as further from submission than it is by a fifth of its own length. ONE BOX COULD NEVER HAVE BEEN TICKED HONESTLY: it asked that `docs/provenance.md` state "that no NES core was ever opened", and that same document's section Do not self-certify forbids exactly that class of finished claim, so satisfying the box required writing the one sentence the provenance rules exist to prevent -- a wrong requirement rather than a missing tick, and only reading every item found it. RE-MEASURING THE TICKED HALF found two more, which is v2.6.9's lesson in a different document: `RustyNES.sdc` still said "there is exactly one core clock", true of v2.6.3 and false since v2.6.13, which added a 4x SDRAM clock and a phase-shifted pin clock that the shipped timing report names alongside it; and the `.qsf` entry said all 109 pin assignments come from `sys/sys.tcl`, where 109 is what that script supplies before stopping short of the I/O board, so a core must also source one of two 36-assignment variants -- 145 in total, from two scripts. THE ALARMING READING OF THE FIRST WAS CHECKED BEFORE IT WAS WRITTEN DOWN: it looks as though the framework's `set_clock_groups -exclusive` might be cutting the console-to-SDRAM crossing and leaving ADR 0039's safety argument unfalsifiable, and it is not -- exclusive cuts paths BETWEEN groups, all three outputs match one glob, and v2.6.13's own -24.769 ns measurement of that crossing is only observable because those paths are analysed. THE NAMING DIVERGENCE IS MEASURED RATHER THAN PARAPHRASED, from `Main_MiSTer/file_io.cpp` instead of the wiki: `get_display_name` searches for the literal underscore-two-zero and TRUNCATES THE DISPLAY NAME THERE, taking the rest as a datecode, and `DirentComp` groups by that truncated name -- so a version-named bitstream makes every release a SEPARATE CORE ENTRY, named for the version rather than the core, ordered alphabetically, which puts v2.6.9 after v2.6.13. The fix is one line and is NOT taken here, because version-naming is a maintainer decision with a stated rationale and reversing it is not an audit's call. THE TASK BOARD HAD THE SAME DEFECT AND ONE ROW WORSE: four delivered items never ticked, a hardware row still naming a version seven releases past, and an SDRAM row whose precondition -- "after a board exists" -- v2.6.13 simply did not follow, having accepted the controller against a behavioural model written from the datasheet instead; that is rung 7's own recorded lesson repeating one row below where it is written, since the blocker applied to hardware ACCEPTANCE rather than to building the thing. A CLAIM v2.6.13 SHIPPED IS RETRACTED: answering a review finding, I wrote that MMC1 was the sixth approved family and not yet implemented, and the cartridge decodes mapper 1 at three sites with a registered gate green since rung 7 opened -- asserted from memory inside a reply correcting somebody else's reading of the same line. The gate that keeps all this true asserts a SHAPE rather than a judgement and is demonstrated by five mutations, one of which proves the continuation-line folding load-bearing by producing nine false violations without it. The bitstream is BYTE-IDENTICAL to v2.6.13's, which is the point: the only sibling change is a comment, and an identical artifact demonstrates it. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on **v2.6.13 "Slack"** (2026-09-03) — the cartridge outgrows the die, and three consumers want the same bus. An SDR SDRAM controller, a behavioural part model, a four-way arbiter and a console bridge, all written from the AS4C32M16SB-7 datasheet revision 1.4 -- no third-party controller read, ADR 0037 applying. The budget the previous step worked to was a single figure read off the fetch structure and never measured; `nes_top`'s `CHR_LAT` sweep asks the console directly, and there are THREE answers: a background or sprite fetch tolerates 28 cycles and uses 17, the PPUDATA data port tolerates 8, and the CPU sampling at mc7 has 24 -- four cycles of every budget going to the console-domain crossing, which is not optional, because publishing a runtime modulo combinationally into an 11.64 ns domain costs -24.769 ns of setup. The PPUDATA port could never fit, half its budget being the crossing, so it leaves the shared bus entirely: `ppu2c02` gains `BUFFER_HANDSHAKE` and fills its read buffer through a port of its own on the arbiter, affordable because the CPU does not read that buffer until its next PPUDATA access. THAT FIX SHIPPED A DEFECT ONLY A BANKED CARTRIDGE COULD SEE -- the request carried the RAW fourteen bits the PPU presents, which is right by coincidence on NROM because the mapper's translation is the identity there, and reads BANK ZERO on everything else; `ppu-misc-2007-stress` passed off-die throughout while the two CNROM gates read a zero byte where the oracle reads one, one and two. The fix REMOVES the address rather than correcting it: `cart.sv` already publishes the translation for the fetch path, so the request carries none and there is one source of truth for where CHR lives instead of two that agree only on NROM. A DELETION WAS THEN REFUTED BY ONE CYCLE: with the address fixed, a control issuing at a flat +7 passed both gates, which read as the anti-contention deferral buying nothing, so it was removed -- and the deployed code issued at +6, and both gates failed again. The control and the code differed by a single cycle, which is the measurement of how thin the CPU's off-die deadline is and the concrete argument for scheduling the bus rather than arbitrating it. Two INFERRED LATCHES that Verilator cannot see: `ppu2c02` assigned two signals only under `BUFFER_HANDSHAKE`, so in the shipped on-die build the only assignment either reached was the reset branch, and a variable holding its previous value on every live path is a latch -- Quartus said so twice while the lint gate stayed green, and the comment beside them asserted the defect as a virtue ("outside BUFFER_HANDSHAKE neither signal ever moves", which is true and is exactly the condition). And a defect in the HARNESS: `USE_SDRAM` reaches Verilator as `-G` rather than as a file, so make ran ONE binary under both configurations' names and a log labelled on-die was the off-die build, reproducing the off-die failures exactly -- closed with a stamp-file prerequisite, demonstrated by mutation. THE OPEN ROW: accesses no longer auto-precharge, a hit costs 6 cycles against 10 for a miss, tRAS's 120 us MAXIMUM is respected by an early close in idle, and two defects came out of the rewrite -- re-entering idle with the request still asserted issued every access TWICE, and subtracting one from CAS the way the other waits are computed broke every read to all zeroes, CAS being "data appears at cycle N" rather than a command-to-command gap. Two MiSTer tickets close: `sdram_sz` is consumed VALIDITY BIT FIRST, so `absent` is deliberately not `!present` and a power-on all zeroes cannot read as "no board" (gated exhaustively over all 65,536 values), and `status_menumask` is computed rather than tied off, greying Reset whenever the console is already held in reset. OFF THE DIE THE CONSOLE PASSES 142 OF 142, every gate the on-die build passes, with better timing margin and 384 fewer M10K blocks -- and it still SHIPS ON the die, because an off-die core cannot run at all without the SDRAM add-on while rung 7's five mapper families fit on the die at 468 of 553 blocks. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on **v2.6.12 "Groundwork"** (2026-09-02) — the bitstream was an NROM-only console. Rung 7 landed five mapper families and 142 co-simulation gates verify them, and the layer that turns that RTL into a bitstream was never told: `rtl/emu.sv` left `cart_mapper`, `cart_prg_16k_banks` and `cart_chr_8k_banks` unconnected, so Quartus tied all three to GND -- mapper 0 for EVERY cartridge, `prg_8k_count = 0` collapsing PRG to an 8 KiB window, and CHR forced to RAM. The declared 256 KiB PRG and 128 KiB CHR were implemented as 8 KiB each; connecting three wires takes block memory from 666,061 to 3,680,717 bits and timing still closes at all four corners. NOTHING COULD HAVE CAUGHT IT: simulation cannot, because `emu.sv` is not in the testbench file list and the harness drives those ports itself, so all 142 gates exercised a correctly-configured cartridge; and Quartus DID say so three times, in messages that cite an INSTANCE path rather than a file and are absent from the "0 errors, N warnings" tally, so the existing checker read 0 of 125. Two gates close that -- one fails on an unconnected pin of any module this repository declares, the other pins the warning SET rather than its count -- and both are demonstrated to fail by mutation. The `hps_io` tie-off audit that followed raised nine `T-MISTER-*` tickets, and annotating each with a blocker turned "none of these landed" into a measurement: not one is blocked on EFFORT, so the list is the rung-6 agenda rather than a backlog. `T-MISTER-SAVE` was attempted and refuted -- every save route terminates in `hps_io`, which no gate here instantiates. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on **v2.6.11 "Exposure"** (2026-09-02) — a picture is a gate the ladder did not have. All 141 co-simulation gates THEN IN THE SUITE were green (it ends this release at 142, the one it added) and TWO OF SIX commercial games rendered wrong -- a CHR-RAM write was taking the shared-pin composite address built for FETCHES instead of `v`, so the layout was right and the tiles were scrambled. The split is exactly CHR-ROM against CHR-RAM, which named the mechanism before any tracing, and a CONTROL says it is not v2.6.10's regression: the pre-M10K-fix RTL differs by the IDENTICAL 16,565 pixels, so the defect dates from the cartridge landing in v2.6.9. It was not UNREACHED -- the DUT asserts `chr_wr` 9,600 times in the Battletoads run -- it was UNCOMPARED: only THREE of the 141 gates compare a framebuffer, all three ship CHR-ROM, every other gate is CPU-side, and AccuracyCoin, the widest gate in the suite, is CHR-ROM too. The rung-7 gates' own comment says what they are for -- "these gates are about BANKING and nothing else" -- and it was accurate, and it was the whole coverage. Six commercial titles now render byte-identically to the oracle over all 61,440 pixels, published as a montage built by a script that REFUSES to publish a tile that differs from the oracle. The v2.6.10 bitstream carries the defect and a published version is immutable, so the corrected `.rbf` ships here. The same release finds EIGHT release leads describing v2.6.10 with v2.6.9's summary, and v2.6.9 gone from the lineage entirely, with `docs/STATUS.md` naming v2.6.10 under the codename "Abeyance" -- every existing check passing CORRECTLY, because they pin the version TOKEN and the token was right. Prose cannot be audited; an ORDERING can, so two gates are added and both are demonstrated to fail by mutation. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Rung 6 does NOT close -- no DE10-Nano and no SuperStation One are attached to this machine, confirmed by checking rather than assumed. Built on **v2.6.10 "Inference"** (2026-09-01) — the cartridge meets the synthesiser. Five cartridge boards verified across 141 co-simulation gates had **never been through Quartus**, and Analysis & Synthesis refused the design: `chr` was written from TWO `always_ff` blocks, which cannot infer as one M10K, so 128 KB of CHR stayed in flip-flops -- **1,048,576 registers against roughly 166,000**. Simulation cannot ask this question: Verilator accepts both forms without complaint. It is v2.6.6's finding one layer out -- that release established that an M10K read is REGISTERED, this one that a correctly registered memory still will not infer with two writers. The fitter was also throttling itself under Auto Fit while the `.qsf` carried no optimisation assignments at all: at full effort **all six seeds close** where two had failed, so the effort settings move the whole distribution across zero and the seed only picks where in it you land -- and the project had been pinned to seed 4, the WORST of the six. Pinned at seed 3, +0.531 ns setup and +0.099 ns hold, byte-identical across two independent compiles. The bitstream v2.6.9 could not produce ships here. Built on **v2.6.9 "Abeyance"** (2026-08-31) — an exclusion hides improvement as well as regression, and both denied co-simulation streams close. The larger one was never the console: `apuconflict039` had been carried for seven releases as a declared diagnostic whose bus surface "carries nine divergences BY DESIGN", and the nine were a defect in the HARNESS -- on a cycle the CPU is held, the testbench built its record's bus data from a stale local rather than from the RTL's own latch. Taking it from the latch makes the stream IDENTICAL on all 357,361 overlapping cycles and all 88 checkpoints, and the local is now dead and deleted. The phrase "by design" is what stopped anyone re-checking it, because it reads as a property of the thing under test when it was a property of the instrument reading it. The other stream differs on EXACTLY ONE cycle, a documented and attributed OAM-corruption asymmetry -- and carrying that needed an instrument the suite did not have, because the PLANNED mechanism was refuted by its own mutation pass: an allowance by checkpoint index cannot work on a rolling hash, since one divergent cycle poisons every checkpoint after it, so allowing the first differing window simply moved the failure to the next one and allowing the rest is the all-or-nothing deny it was meant to replace. A per-cycle nine-field comparator with a scoped allowance costs ONE cycle of coverage instead of seventy-one checkpoints -- 357,360 of 357,361, against nothing at all before -- and it fails BOTH ways, so a DUT that improves cannot leave a stale allowance quietly hiding coverage; six mutations confirm it, including a cycle outside the compared window being REFUSED rather than allowed to match nothing. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction and were re-run anyway. Rung 6 does NOT close -- no DE10-Nano and no SuperStation One are attached to this machine, confirmed by checking rather than assumed. Built on **v2.6.8 "Arrears"** (2026-08-31) — the gates the previous release fixed and never widened. A deny list is an assertion about the THING UNDER TEST, so v2.6.7 changing both the DUT and the harness re-opened every exclusion -- and nothing re-measured one. FOUR of the six denied checkpoint streams were passing (`irqlat048`, `ppuvbl023`, `ppuvbl024`, `ppuvbl025`), and THREE of those were not run by the suite at all, so removing them from the deny list was INERT until they were also iterated. The nestest gate compared 265,000 cycles against a 5,062,688-cycle golden -- correct while caveat C6 was open, left behind the moment it closed -- and all 5,062,680 overlapping cycles now match, with the window DERIVED from the manifest. CAVEAT C4 CLOSES by demonstration: nestest raises `nmi_line` on 3,592 cycles and had no nine-field comparison at all, so wiring it makes an `o.nmi_line` mutation CAUGHT at checkpoint 28 of 1,237 while the bus gate passes the identical run. Suite 128 passed, 0 failed, 0 skipped; nine-field comparisons 51 -> 57. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Rung 6 does NOT close -- no board is attached, confirmed by checking. Built on **v2.6.7 "Detent"** (2026-08-30) — the bitstream becomes a published release artifact and a one-cycle disagreement is pinned to the cycle it happens on. Every release from here ships a `.rbf` -- committed to the sibling's `releases/` and attached to the GitHub release on BOTH repositories -- reversing v2.6.6, which produced one and withheld it because no hardware had run it: the MiSTer distribution mechanism reads that path out of the REPOSITORY, so an empty `releases/` describes an undistributable core rather than a cautious one, and the caution moves from an absence into a disclosure naming what the ladder cannot reach by construction (the PPU gate compares the pre-palette index and the APU gate per-channel integer levels, so the palette, the video timing constants, the audio absolute level and its band-limiting all sit downstream of every gate). The build is REPRODUCIBLE and that is now measured rather than argued -- a from-scratch compile and an incremental one produce a byte-identical bitstream -- which is also how v2.6.6's published slack figures came to be WITHDRAWN: no corner of a clean rebuild reproduces them, the innocent explanation (a different timing corner) was checked first and refuted, and the correct pair is +0.108 ns setup and +0.042 ns hold at the binding corner. THE RELEASE GATE WAS READING THE WRONG CORNER -- Slow 100C is not the binding one on this design, so a bitstream failing at Slow -40C would have passed while the gate reported three times the real margin -- and the checker that reads it was wrong twice before mutation found both: it first extracted ZERO rows from both summary tables and reported that as "no negative slack", then, once fixed, reported FOURTEEN clocks from a report emptied of its data, having run past the closing rule into the next tables. Caveat C2 splits in two. The first residual was a TRACE OBSERVATION POINT -- the harness built its record after eight of a CPU cycle's twelve master clocks while the oracle reads at end-of-cycle, and the frame-counter interrupt asserts on the final edge -- and closing it took checkpoint comparisons from 3 to 11 and failures at one checkpoint from 30 to 3. The second is REAL, and the FIRST fix for it was REFUTED in a way that found the right one: moving all four effects of the write one cycle later to match the oracle drops blargg from 11/11 to 4 of 11, one of the seven being the ROM written to probe exactly that timing. Read as a measurement that PROVES the sequencer's maturation is correctly placed, which leaves only the other effects the same write schedules -- so separating ONLY the interrupt clear lands it at write+3, the documented cycle, while the frame counter's zeroing stays put. Checkpoint streams go from 11 identical to 52 of 58 and the suite from 87 to 122, with blargg still 11/11 and the bus still matching on all 2,680,239 overlapping cycles. The checkpoint gate is registered over 51 comparisons and STATES ITS BLIND SPOT: not one gated golden ever raises an NMI, so it cannot catch an nmi_line defect, and the attempt to close that hole found a FOURTH divergence cluster that three goldens had been hiding inside a "26 skipped" tally line. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Rung 6 does NOT close -- no DE10-Nano and no SuperStation One are attached to this machine, confirmed by checking rather than assumed. Built on **v2.6.6 "Chassis"** (2026-08-29) — the console becomes a MiSTer core -- `sys/` vendored byte-identical against `Template_MiSTer@3ea1134c` (57 files, 0 content differences), a top level, a clock, a palette, video sync and an audio mixer, compiled by Quartus 17.0.2 into a Cyclone V bitstream with 0 errors, timing CLOSED, and a warning count taken from 111 to THREE -- all three inside the vendored framework or Quartus's own megafunction, none of them citing this project's RTL (worst setup +0.086 ns and worst hold +0.096 ns at the binding corners, seed 3 -- v2.6.7 also withdraws the +0.363/+0.245 v2.6.6 published, which a clean rebuild of that configuration reproduces at no corner, TNS 0.000 on every clock; the console's own clock +13.514 ns at an Fmax of 30.26 MHz against the 21.477272 MHz it needs). The emulation core is unchanged, so the co-simulation suite is an ACCEPTANCE CRITERION rather than a formality -- 87 passed, 0 failed -- and it earned that immediately, because the cartridge memories had to be rewritten: an M10K read is REGISTERED, so 40 KiB of asynchronously-read cartridge was 393,216 registers against roughly 166,000 available, under a comment claiming it inferred block RAM from the source style alone, and the README had stated the correct rule since v2.4.3. Two defects were found only by asking whether the outputs would work on real hardware: the audio would have been a full-scale DC rail, because the mixer output is unipolar with silence at zero and the framework maps unsigned silence to -32768, and two OSD scanline options did nothing because VGA_SL was tied to zero. And a convention enforced by a glob has no error message: sys_top.sdc groups the core clock by matching the hierarchical name pattern *|pll|pll_inst|altera_pll_i|*, so a differently-named PLL matched no group at all and every crossing to the framework audio, HDMI and HPS domains was analysed as synchronous -- -13.901 ns of slack and -422,601 ns of TNS on a design whose Fmax was already above requirement, with the compile succeeding and the Assembler reporting 0 errors and 0 warnings throughout. Built on **v2.6.5 "Muster"** (2026-08-29) — rung 5 closes — the AccuracyCoin status vector is identical entry for entry across all 146 entries, with 146 of 146 executed on both sides and none NotRun, where the same gate read 5 of 146 at the version's start. A muster is a roll call where every name is called AND answered, which is the two-clause acceptance exactly. Five PPU defects close the last six differing entries and four were invisible to every gate that existed when the version opened: the background shift registers' RELOAD and their shift clock need SEPARATE gates (with one shared gate the serial-in test was not merely failing but ARITHMETICALLY UNREACHABLE, since reload dots are absolute and the reload discards the low seven bits, so a serial-in one can never reach bit 7 on any alignment — and modelling both structures reproduces BOTH measured shifter values); the sprite X counters are NOT gated on rendering, which AccuracyCoin states outright and the ROM that states it passes either way, because it expects no hit at X=254 and a sprite shoved 18 dots right is also off the line; the PPUADDR second-write v-copy is DELAYED, as the wiki says inside the write sequence itself, swept 1 to 4 dots against a control at 8 and 12 that fails; and the pre-render line CLEARS secondary OAM, without which scanline 0 draws what scanline 239 left — no sprite can ever render on scanline 0, because OAM Y is one less than the display row, and a sprite-0 probe over the full 134 M-cycle battery found 24 hits with four of them there; and the octal latch holding across the read dot, which is verified by exactly ONE gate and was unverifiable until the v-copy delay landed, the two composing the hybrid address together and neither producing it alone. A DIAGNOSIS IS RETRACTED: the residual was read as a two-dot CPU/PPU alignment error from comparing dot spans across two instruments, and at the committed alignment the two consoles execute identical pc, bus_addr and bus_access for 1,695,131 cycles while a two-dot shift moves the first fork back to 593,228 and takes the differing share from 5.13% to 66.80%. The oracle changes on the default path, so AccuracyCoin 141/141 (RAM decoder) and nestest 0-diff are VERIFIED, not asserted. Built on **v2.6.4 "Rubric"** (2026-08-26) — OAM DMA lands and all nine AccuracyCoin disagreements close, every rule that closed the last three stated by the test ROM and by neither nesdev page — and then the gate that certified them is measured to cover 88 of 146 entries. The emulation core is unchanged. Built on **v2.6.3 "Mainspring"** (2026-08-25) — the DUT runs on one master clock, and four enables that were never enabling — plus AccuracyCoin end to end and a status vector that names its disagreements by test. The emulation core is unchanged. Built on **v2.6.2 "Witness"** (2026-08-24) — rung 4 closes: blargg APU battery 11/11 on the co-simulation DUT, six defects no self-written gate could see, and a suite that had been asserting nothing for five minor releases. The emulation core is unchanged. Built on **v2.6.1 "Interleave"** (2026-08-24) — the DMC and its DMA cycle steal in the MiSTer co-simulation DUT, cycle-exact on the bus. The emulation core is unchanged. Built on **v2.6.0 "Assay"** (2026-08-24) — the triangle, the noise channel and the sweep unit **in the MiSTer co-simulation DUT** — and an audit of how much of the APU was fitted to the oracle rather than derived from documentation. The emulation core is unchanged. Built on **v2.5.9 "Overture"** (2026-08-24) — rung 4 opens: the two pulse channels, the frame counter, and four ROM defects the stimulus measurement found first. Built on **v2.5.8 "Blanking"** (2026-08-24) — VBlank, NMI and the PPUSTATUS race close rung 3 — and both fixes were deletions. Built on **v2.5.7 "Collimation"** (2026-08-24) — sprite rendering closes exact — the phase was wrong by two dots, and every window was compensating. Built on **v2.5.6 "Vestige"** (2026-08-23) — Sprite evaluation closes: all 59,993 overlapping cycles match, nine of nine behavioural mutants caught and two proved inert (announced as seven of eight at the cut), and the fix is a byte index that outlives the walk that set it. Built on **v2.5.5 "Raster"** (2026-08-23) — the first full frame, and three blind spots in the stimulus that fed it. Built on **v2.5.4 "Escapement"** (2026-08-23) — the background fetch pipeline, and an access two dots early that five gates could not see. Built on **v2.5.3 "Hysteresis"** (2026-08-23) — toggling rendering takes effect three dots after the write, and four instruments to prove it. Built on **v2.5.2 "Dormant"** (2026-08-23) — the 2C02 register file, and a gate that passed while testing nothing. Built on **v2.5.1 "Retrace"** (2026-08-23) — the interrupt sweep closes rung 2, and a gate reported a pass it could not have earned. Built on **v2.5.0 "Rungwork"** (2026-08-23) — the 6502 rung, and the two gates it cannot reach. Built on **v2.4.9 "Plumbline II"** (2026-08-23) — the bus half of rung 2, and what it found the day it existed. Built on **v2.4.8 "Palimpsest"** (2026-08-23) — read-modify-write, and a gate that cannot see its own subject. Built on **v2.4.7 "Keystone"** (2026-08-23) — the stack closes, and a dead line proves itself dead. Built on **v2.4.6 "Abacus"** (2026-08-22) — the core learns arithmetic. Built on **v2.4.5 "Compass"** (2026-08-22) — the core reaches memory, and chooses. Built on **v2.4.4 "Ignition"** (2026-08-22) — the first real RTL -- the 6502's eight-cycle reset and the implied opcode group, matching the oracle on all seven CPU fields (29 > records, `RustyNES_MiSTer@7f092bd`). The oracle settled a question our own > prose could not: reset is EIGHT cycles, and `docs/cpu-6502.md` said both > seven and eight. The emulation core is untouched. @@ -1097,7 +1097,7 @@ > `installDebug`→`installFossDebug` alias. Ad / RevenueCat glue stayed dormant and was > later removed entirely (ADR 0035 — RustyNES is permanently open-source and > income-free); on-device dual-flavor verification and F-Droid submission remain a -> forward step. See `docs/android.md` + `to-dos/v1.8.x-on-device-verification.md`. android.yml CI (the +> forward step. See `docs/android.md` + `docs/mobile-v2.9.3-run-sheet.md` (the v1.8.x checklist folded into it at v3.1.0). android.yml CI (the > NDK cross-build + both-flavor Gradle package) is the compile gate for this change. > > **The preceding release: v2.0.0 "Timebase"** (2026-07-03) — the **one-clock, @@ -1881,9 +1881,9 @@ without panicking (no `$6000` status protocol). | `mmc5` (smoke) | 3 | — | — | 3 | `mapper_mmc5test_v1.nes`, `mapper_mmc5test_v2.nes`, `mapper_mmc5exram.nes` from `christopherpow/nes-test-roms/mmc5test/`. Visual-only; smoke-tested. Deep features (split-screen ExGrafix, audio extension) tested via in-tree mapper unit tests. | | `holy_mapperel` | 19 | — | — | 19 | Damian Yerrick / tepples cartridge-PCB-assembly test (zlib license). 17 release ROMs across mappers 0/1/2/3/4/7/9/10/34/66/69, plus two 512 KiB MMC1 images (SUROM `M1_P512K_CR8K_S8K`, SXROM `M1_P512K_CR8K_S32K`) built from the v0.02 source tag in v2.7.2, whose build reproduces all 17 release ROMs byte-for-byte. Each screen is pinned by one combined framebuffer snapshot, with settled and non-blank guards, and each was read as `PASS 0000` by eye when it was pinned; there is no status-byte assertion, so none of the 19 is a strict pass by this table's definition (the column said 17 until v2.7.2, against this row's own "smoke-tested only"). Track B1. | | `vrc24test` | — | — | — | — | **Skipped (Track B1)**: link rot. AWJ's original forum attachment (id=10017 on forums.nesdev.org/viewtopic.php?p=203716) is auth-walled; the deletion is documented at archive.nes.science. No GitHub mirror found. | -| `AccuracyCoin` | 1 | 1 | — | — | 100thCoin / Chris Siebert single-NROM accuracy battery (MIT license, 149 tests across 22 suites + 5 visual-only `Power On State` tests sharing `$03FF`; the v2.0.1 upstream re-sync grew the catalog from 144 to 146 rows / 139 to 141 assigned tests, adding the PPU "ALE + Read" and "Hybrid Addresses" tests, and the **2026-09 re-sync** to upstream `69c8860` grew it again to 149 rows / 144 assigned across 22 suites — two new pages, `Advanced Background Evaluation` and `Advanced Sprite Evaluation`, which re-home eleven existing PPU tests that had outgrown `PPU Misc.`, plus three genuinely new tests). Interactive (D-Pad menu); the harness presses `START` to "run all tests on the ROM" then takes two parallel measurements. **(1) Framebuffer decoder** reads the 10×16 on-screen result grid by exact-pixel colour (5-colour palette: `#64A0FF` = pass, `#4F1000` = fail, `#DC834C` = partial-pass, `#4C4C4C` = no-test / not-run, `#FFFFFF` = border); this is the legacy path and has a known grid-stride bug that under-samples by ~31 cells. **(2) RAM-direct decoder** reads each test's result byte from its fixed CPU-RAM address (catalogued from upstream `AccuracyCoin.asm` in `crates/rustynes-test-harness/src/accuracy_coin_catalog.rs` and `tests/roms/AccuracyCoin/SOURCE_CATALOG.tsv` — 149 `(suite, name, addr)` triples, regenerated by `scripts/accuracycoin-build/extract_catalog.py`) and decodes per-test pass/fail/error-code names + per-suite breakdowns. This is the authoritative path. **Current measured pass rate (RAM-direct): 100.00% (144/144)** on the default build — the two upstream PPU tests "ALE + Read" and "Hybrid Addresses" (briefly the only gaps at 139/141 after the v2.0.1 catalog re-sync) were **closed in v2.0.3** by promoting the 2-cycle-ALE PPU fetch model to the unconditional default. The `90.65%`, `84.17%` and the trajectory figures below are historical engine-lineage milestones (the pre-promotion v1.0.0-rc2 / Session-26 era), retained as history. Historical trajectory: `64.03%` (post-D2 baseline) → `67.63%` (post-D3, 7 6502 bus-pattern fixes) → `69.06%` (post-Phase-3 OAM DMA parity fix, +1 strict test flipped) → `69.78%` (post-FSM-fix recovery, +1 sprite-related sub-test flipped as a side-benefit of the `crates/rustynes-ppu/src/ppu.rs` dot-64 reset removal) → `76.98%` (post-Cascade-B DMC DMA scheduler, commit `9b0c81c` — closes all 8 tests in the `APU Registers and DMA tests` suite + 3 net elsewhere as side-benefits; +11 tests flipped) → `78.42%` (post-Cascade-A OAMADDR-during-rendering reset, commit `f29f7ca` — hardware-accurate per nesdev: OAMADDR is reset to 0 during dots 257-320 of every rendered scanline; +2 tests flipped — Sprite overflow behavior PASSES, Sprite 0 Hit advances from error 1 → error 13) → `79.14%` (post-session-7 OAMADDR-walks-during-eval + $4-aligned `$2004` write, commit `c230489` — closes `Address $2004 behavior` with code 16; +1 net flip) → `79.86%` (post-session-7 RMW ABS,X/Y unfixed-address dummy read, commit `32d5b18` — 18 RMW opcodes get the canonical cycle-4 unfixed-address dummy; flips `APU Tests :: Controller Clocking` and advances `Implied Dummy Reads` 2→3 + `Frame Counter IRQ` 6→7 via the SLO $4015,X bracket; +1 net flip) → `82.73%` (post-session-8 BG-pipeline cycle-9 reload + post-emit shift, commit `086ce4d` — fixes the long-standing 1-column BG pixel off-by-one identified in `docs/audit/cascade-a-investigation-2026-05-19.md`; flips `Sprite 0 Hit behavior` + `Sprite overflow behavior` + `Suddenly Resize Sprite` + `$2007 read w/ rendering`; +4 net flips, +2.87pp) → `83.45%` (post-session-24 Controller Strobing M2-low-defer write, Session-24 Phase 3 — deferred `$4016` commit buffer on `LockstepBus` mirrors Mesen2's `NesControlManager::ProcessWrites`; flips `APU Tests :: Controller Strobing` from `[error 4]` to PASS; +1 net flip) → **`84.17%` (post-session-26 Sprint 2 iter 5 Frame-Counter-IRQ split, 2026-05-23 — separates `FrameCounter::irq_flag` ($4015 bit 6 visibility) from `FrameCounter::irq_line_active` (CPU IRQ source driver) so Tests I/J/K/L/M/N/O all PASS without spuriously asserting the CPU IRQ line on inhibited frame-counter cycles; flips `APU Tests :: Frame Counter IRQ` from `[error 19]` to PASS; +1 net flip)**. Session-26 Sprint 2 iter 4 (APU Register Activation OAM-DMA chip-select gate) advanced the same suite's APU Register Activation entry internally from `[error 4]` to `[error 6]` but did not flip the catalog-headline metric. The previous `75.93%` headline reflected the framebuffer decoder's stride bug, not real accuracy. Strict floor in CI is **60%** — see `crates/rustynes-test-harness/tests/accuracycoin.rs::MIN_PASS_RATE`. the v0.9.x 80% target and the v1.0.0 90% gate were both cleared, and the default build now measures **100.00% (144/144)** — the master-clock core is the default, the former C1 + sub-cycle residuals are closed, and the two v2.0.1 PPU tests were closed in v2.0.3 (see "Accuracy residuals" below). **No open AccuracyCoin gap, as of v2.6.18.** The 2026-09 re-sync opened two, both in the new `Advanced Sprite Evaluation` page. `Misaligned OAM2 Address` ($0495) closed in v2.6.17 by modelling `OAM2Address` as a live counter through sprite fetch. `Frozen OAM2 Increment` ($0493) closed in **v2.6.18**, and the long diagnosis this row used to carry is **RETRACTED in all three of its claims** — it said a `$2001` enable on dot 256 took effect a dot early (all four writes the ROM names are in force from the START of the dot it names), it blamed test 2 or 3 (the failure was test **4**, the false-positive guard, and the ROM omits an `INC 144 assigned tests across two new pages, and `46199ae4` changes the catalog not at all; the last of them, diff --git a/docs/accuracy-ledger.md b/docs/accuracy-ledger.md index e3732ce1b..958dced8d 100644 --- a/docs/accuracy-ledger.md +++ b/docs/accuracy-ledger.md @@ -9,7 +9,7 @@ this ledger is the approximation map. The remediation line is **v2.1.0 "Fathom"** (accuracy work, shipping ahead of the v2.2.0 mobile store launch). **Headline:** every suite on the oracle path is green, with **no named -exception** — AccuracyCoin **144/144 (100.00%, RAM decoder)** as of v2.6.18, +exception** — AccuracyCoin **146/146 (100.00%, RAM decoder)** at upstream `f5f41dc2` since v3.1.0, whose re-sync showed that the 144/144 reported from v2.6.18 to v3.0.1 was overstated, because the older ROM's `Misaligned OAM behavior` fail path fell through to a pass and hid a real failure. Before that: **144/144** as of v2.6.18, which closed `Advanced Sprite Evaluation :: Frozen OAM2 Increment`, the last entry the 2026-09 upstream re-sync left open. `KNOWN_FAILING` is now empty; it is retained, because it fails BOTH ways and is what caught this closure. @@ -39,7 +39,7 @@ disposition under the v2.1.0 "Fathom" accuracy-remediation line | Optional OAM decay (F2.3) | Dynamic-RAM sprite decay when rendering stays off: per-8-byte-row 3000-CPU-cycle refresh model (Mesen2 `ReadSpriteRam`/`WriteSpriteRam`), rows refreshed by sprite-eval + `$2004`/DMA, decayed rows read `((a&3)==2)?a&0xE3:a` | No decay test ROM (Mesen2 code-level parity); NTSC/Dendy-only | **Landed on `main`** (Unreleased, next tag; F2.3) — **default-off** so the golden vectors / AccuracyCoin / commercial oracle (all decay-off) stay byte-identical; a real core feature (affects the framebuffer when ON), deterministic off `dot_counter`, save-state v7 tail (relative-age) | | NTSC generated palette (F1.4) | Optional composite-model base palette synthesized in-core (`rustynes_ppu::generate_base_palette`) in place of the hand table | `full_palette` visual corpus + Mesen2/ares baselines (visual only) | **Shipped** (v2.1.2) — **default-off**, golden-locked cross-target; select via Settings → Palette → "Generated NTSC". Default build byte-identical | | NTSC composite shader ladder (F2.2) | Display-only GPU post-passes: simplified blur (`Ntsc`) → LMP88959 composite → Bisqwit per-dot (`CompositeRt`) | No pass/fail ROM (visual only); `visual_regression` stays byte-identical with any filter active | **Shipped** (v2.1.2) — three-rung ladder verified end-to-end, live emulator-synced dot-crawl now on both composite passes (`Lmp88959` phase wired), palette↔pass split documented; no separable-kernel rung (LMP covers that tier). See `docs/frontend.md` | -| Vs. `DualSystem` second screen | Core modeled + `sub_framebuffer()` exposed; desktop frontend now presents both screens (side-by-side / stacked), routes P1-P4 + coin, plays the main console's audio | Synth harness + boot of the 4 DualSystem titles (real-cabinet boot stays fixture-limited — see below) | **Shipped** (v2.1.2 F2.1, desktop) — advanced features (run-ahead / rewind / netplay / TAS / dual save-state) scoped out in dual mode per ADR 0032; wasm/mobile deferred | +| Vs. `DualSystem` second screen | Core modeled + `sub_framebuffer()` exposed; desktop frontend now presents both screens (side-by-side / stacked), routes P1-P4 + coin, plays the main console's audio | Synth harness + boot of the 4 DualSystem titles (real-cabinet boot stays fixture-limited — see below) | **Shipped** (v2.1.2 F2.1, desktop) — netplay / TAS scoped out in dual mode per ADR 0032; dual save-state since v2.9.7, rewind and run-ahead since v3.1.0 (the ADR's amendments). Since v2.9.7 the browser's wasm-winit build runs the cabinet and presents both screens (the `wasm-canvas` embed runs the main console only; the browser run is a manual check), and the mobile bridge carries the cabinet since v2.9.7, with a screen switch rather than two screens; its on-device rows (run sheet T8-T10) are NOT RUN and the Swift half is uncompiled | | NSF non-60 Hz playback + NSFe | **Done** (F4.1/F4.2): the play-speed divider (`$6E-$6F`/`$78-$79`) is parsed and a non-standard rate (PAL 50 Hz / custom µs) drives `play` via a mapper cycle-timer IRQ (frame-IRQ-disabled); standard 60 Hz keeps the byte-identical vblank-NMI path. `NSFE` chunked container parsed (INFO/DATA/BANK/auth). | `nsf` unit + core integration tests | **Done** (F4.1/F4.2) | | FDS medium model (F4.3) | Byte-stream wire medium: gap / `$80` mark / block / **CRC-16-KERMIT** per block, with **per-block CRC re-emitted on write** (`resynth_block_crc`) and an opt-in **continuous belt-velocity head-seek** model (distance-proportional re-seek, default-off) replacing the fixed-cycle window | **CI-verifiable (synthetic):** `medium_write_verify` BIOS-free oracle — write via the register path, re-walk the wire, assert every block's CRC-16 + gap/mark framing round-trips (`fds::tests::synthetic_write_verify_*`). **Local-only:** the real-BIOS write-CRC path (BIOS recomputes CRC in its own RAM → `$4024`) needs a copyright `disksys.rom`, kept in gitignored `tests/roms/external/` and out of CI | **Shipped (v2.2.0 "Capstone")** — additive: default (model-off, non-writing) `.fds` run is **byte-identical**; new state round-trips the **v4** save-state tail. AccuracyCoin has no FDS ROM, so 141/141 is unaffected | | Famicom microphone ($4016.2) | Not modeled | Built-in controller-2 mic bit surfaced on `$4016` D2 (`Nes::set_microphone`); `famicom_microphone_drives_4016_bit2` bus unit test | **Shipped (v2.2.0)** — additive / default-off (mic released ⇒ `$4016` byte-identical); a `$4016`-only signal (never touches `$4017`). No pass/fail mic ROM exists (real-cart local test only) | diff --git a/docs/adr/0032-vs-dualsystem-desktop-presentation.md b/docs/adr/0032-vs-dualsystem-desktop-presentation.md index dbea0d5f0..74bf5afd6 100644 --- a/docs/adr/0032-vs-dualsystem-desktop-presentation.md +++ b/docs/adr/0032-vs-dualsystem-desktop-presentation.md @@ -78,3 +78,16 @@ The gate: Netplay and TAS (`T-PS-dual-netplay`), the debugger and HD packs stay out of dual mode. + +**Outcome (v3.1.0).** Implemented as decided. The cabinet owns a rewind ring +of whole RVSD containers, framebuffers included, so a step back restores both +screens without the re-render a single console's slim ring needs. +`VsDualSystem::restore_quiet` is the restore that keeps the ring, for +run-ahead's rollback and a rewind step; `restore` (a loaded state) and +`power_cycle` empty it. Both gate clauses are tests that mutation shows can +fail: `vs_dualsystem_rewind.rs` (a slim sub block in the capture, and a loud +restore in the step back, each caught) and `runahead.rs` +`cabinet_runahead_matches_a_plain_run_on_both_screens` at depths 1 and 2 (no +rollback, and capture left on across hidden frames, each caught). The stimulus +is a cart built for it: the protocol cart renders nothing, so a framebuffer +comparison against it is blind. diff --git a/docs/adr/0044-movies-and-netplay-carry-the-emulation-options.md b/docs/adr/0044-movies-and-netplay-carry-the-emulation-options.md index 3e5909303..b4885c87e 100644 --- a/docs/adr/0044-movies-and-netplay-carry-the-emulation-options.md +++ b/docs/adr/0044-movies-and-netplay-carry-the-emulation-options.md @@ -111,3 +111,21 @@ What is recorded and what is not, knob by knob, is the survey table in - **v3.0.0 raises it to format 5 for the core timing epoch.** That adds no option; it records which emulator behaviour a movie was recorded under. See [ADR 0045](0045-a-core-timing-epoch-guards-movies-and-netplay.md). + +## Amendment (2026-10-07, v3.1.0): two more options, format 6, protocol 7 + +- **`HardwareOptions` gains `cpu_overclock`** (`T-CPU-OVERCLOCK`, encoded after + the extra-scanline count) and, in the same release, the sprite-limit option + (`T-SPRITE-LIMIT`), decided together as D22 with one format and one + protocol bump for both. +- **`.rnm` format 6, minimum 6.** A format-5 options record is shorter and + would decode as garbage, so it is refused as too old, by this ADR's own rule. +- **Netplay protocol 7, magic `"RNE7"`.** The `Sync` layout is protocol 6's; + the configuration hash's input changed, so the magic changes, and a + protocol-6 peer is refused as another emulator version (with its epoch) + rather than as a settings mismatch. +- **The CPU overclock is carried, not held at stock.** The extra-scanline + overclock (v2.9.7) is forced to stock while a movie records and under + netplay, because it predates this ADR's carriage. The CPU overclock is + recorded as set and replayed with it, and peers must match: the carriage + this ADR exists for, applied as intended. diff --git a/docs/adr/0045-a-core-timing-epoch-guards-movies-and-netplay.md b/docs/adr/0045-a-core-timing-epoch-guards-movies-and-netplay.md index 97e4b4d53..ca187c6a3 100644 --- a/docs/adr/0045-a-core-timing-epoch-guards-movies-and-netplay.md +++ b/docs/adr/0045-a-core-timing-epoch-guards-movies-and-netplay.md @@ -90,6 +90,22 @@ entirely, because the bytes do not change. would enforce the rule. If such a check proves practical, it is added as part of this decision's implementation; its coverage limit (the panel is not every game) is stated where it lives. + + **Amendment, 2026-10-07 (v3.1.0, `T-EPOCH-FINGERPRINT`): the check exists.** + `crates/rustynes-test-harness/tests/epoch_fingerprint.rs` fingerprints + seven committed test ROMs (every frame's framebuffer, all audio, end RAM + and the CPU cycle count) against `golden/epoch_fingerprint.tsv`, which + records the epoch and `last_release_epoch`, the epoch the last release + shipped. Output that moves while `EMULATION_EPOCH` equals + `last_release_epoch` fails, and a re-bless is refused in that state; once + the epoch is raised, a re-bless records the new output, and a second + change in the same release needs only another re-bless. It runs in CI's + `test-roms` job. Shown on two seeded mutants (this release's own two + accuracy fixes, reverted): each fails, its bless is refused, and raising + the epoch then re-blessing passes. **Coverage limit:** a change that moves + nothing on the panel passes; the commercial snapshot suites stay the wider + net. **The release cut must set `last_release_epoch`** to the shipped epoch + (`docs/agents/ci-and-release.md`), or the gate stops guarding. - **Libretro is unaffected.** RetroArch netplay compares serialized states, which already differ between core versions. diff --git a/docs/agents/ci-and-release.md b/docs/agents/ci-and-release.md index e82c5a75e..d28d8adb0 100644 --- a/docs/agents/ci-and-release.md +++ b/docs/agents/ci-and-release.md @@ -35,7 +35,7 @@ - **The standard is zero warnings, verified by reading the logs, not the check marks.** A green run hid: rustup's auto-install deprecation, a cargo-ndk NDK-path mismatch, UniFFI's ktlint warning, two Kotlin warnings in generated bindings, 13 MkDocs dead links, a Node DEP0040 inside `deploy-pages`, a Homebrew tap-trust warning on iOS, and criterion's "Unable to complete N samples" on every bench. The sweep that found them: annotations from `repos/{o}/{r}/check-runs/{job}/annotations` for every job of the last 150 runs, plus `gh run view --log` of the latest run of each workflow grepped for `warning|error|deprecat`, minus lines that merely echo a script's own `::error::` text. Suppress only what is in third-party code, by the narrowest switch (`--disable-warning=DEP0040` on one step), and say where it goes away. - **`gh pr ready` seconds after a push loses the ready run (2026-09-23, #548).** The push starts a `synchronize` run whose payload still says `draft: true`, so its heavy jobs skip; `gh pr ready` starts a `ready_for_review` run beside it, and the concurrency group cancelled the READY one. What was left was a green `CI success` that had run no `test`, no `test-roms`, no Android and no Pages build. The gate script caught it only because it listed every job's conclusion instead of reading the rollup. **Push, wait until that push's runs have started, then `gh pr ready`.** If the ready-mode runs show `cancelled`, `gh run rerun ` re-runs them with their original `ready_for_review` payload. That is how #548's ready run was recovered: every job passed on attempt 2. - **A pull request tests Linux only unless its branch is `release/*` (2026-10-03, v2.9.8).** `select matrix` in `ci.yml` gives an ordinary PR `ubuntu-26.04` and `ubuntu-26.04-arm`; macOS and Windows test legs run only on `main`, `release/*` PRs, dispatch and the weekly cron. v2.9.8's release PR was `feat/v2.9.8-cadence`: 36 checks green, merged, then `main` failed on `test (windows-latest)` alone, so Auto Release skipped and the release had no tag. The cause was the checkout, not the code: CRLF on the Windows runner stopped the source-shape tests' split on `"\n#[cfg(test)]\nmod tests {"`, so `every_load_path_installs_a_dual_system_cabinet` counted its own strings (3 against 1). Fixed by `*.rs text eol=lf` in `.gitattributes` (#582, itself on a `release/*` branch so its PR ran Windows). **Name every release PR's branch `release/*`.** -- **A release whose goldens or commercial snapshots moved must raise `rustynes_core::EMULATION_EPOCH` (v3.0.0, ADR 0045).** The epoch is what tells a movie or a netplay peer which emulator *behaviour* it expects. `.rnm` format 5 records it, protocol 6 sends it, and a mismatch is refused, naming both epochs. Forget the bump and a movie recorded on the old behaviour replays its inputs on the new timing, and a mixed-version session desyncs, both silently: the exact failure the epoch was added to prevent. **The trigger is mechanical:** any re-blessed golden, moved `.snap`, or changed AccuracyCoin/blargg/commercial result between the last release and this one. It does not move for byte-identical refactors or performance work, frontend features, or new mapper families (no earlier output exists to differ from). Check it at the release cut, beside the CHANGELOG. +- **A release whose goldens or commercial snapshots moved must raise `rustynes_core::EMULATION_EPOCH` (v3.0.0, ADR 0045).** The epoch is what tells a movie or a netplay peer which emulator *behaviour* it expects. `.rnm` format 5 records it, protocol 6 sends it, and a mismatch is refused, naming both epochs. Forget the bump and a movie recorded on the old behaviour replays its inputs on the new timing, and a mixed-version session desyncs, both silently: the exact failure the epoch was added to prevent. **The trigger is mechanical:** any re-blessed golden, moved `.snap`, or changed AccuracyCoin/blargg/commercial result between the last release and this one. It does not move for byte-identical refactors or performance work, frontend features, or new mapper families (no earlier output exists to differ from). Check it at the release cut, beside the CHANGELOG. **Since v3.1.0 a test enforces it** (`tests/epoch_fingerprint.rs`, ADR 0045 amendment): a moved panel fingerprint fails unless the epoch is above the table's `last_release_epoch`. **At every release cut, set `# last_release_epoch` in `crates/rustynes-test-harness/golden/epoch_fingerprint.tsv` to the epoch being shipped**, in the release commit; skip it and the next release can move output under an unchanged epoch without the gate noticing. - **A runner outage made `CI success` green with nothing tested (2026-10-05, the #585 merge, run 37373901726).** No GitHub-hosted runner ever acquired `detect code changes` ("The job was not acquired by Runner of type hosted even after multiple attempts": no runner name, zero steps, cancelled after 15 minutes), so every gate downstream was skipped. The API reported the job `cancelled`, yet the aggregate's `contains(needs.*.result, 'cancelled')` step was itself skipped, so `needs` did not carry that value. `CI success`, `main`'s only required check, then reported success. **Fixed in v3.0.0** with a step that requires `changes` to have succeeded, and `setup` to have succeeded whenever `changes` found code. A docs-only change still passes, because `setup` skips exactly when `code` is false. The CodeQL "Push on main" run lost `Analyze (rust)` to the same outage. **A default-setup CodeQL run cannot be re-run**: the run and job retry endpoints answer 403, and re-applying the default-setup configuration returns the last run's id rather than starting one. The next push to `main` (or the weekly schedule) is what re-analyses. - **HOLD THE AUTO RELEASE BY CANCELLING MAIN'S CI RUN (v3.0.0).** `release-auto.yml` fires on `workflow_run: CI completed` on `main`, requires `conclusion == 'success'`, and tags that run's `head_sha`. When the maintainer asked for a full docs sweep after #586 had merged, `gh run cancel` on the main CI run of the merge commit left it `cancelled`, so no tag or release was made. The sweep then goes in as its own PR, and that PR's green main run is what tags v3.0.0, on the swept commit. Confirm with `gh release view vX` and `git ls-remote --tags origin vX`. The cancelled run's own `CI success` shows red, which is the outage gate working, not a failure. - **THE FIRST MAJOR BUMP FOUND TWO `bump_release.py` BLIND SPOTS (v3.0.0).** (1) Internal path dependencies carried `version = "2.0.0"`, a caret range no 3.x satisfies, so after the bump `cargo update -w` failed to resolve the workspace. No minor or patch bump can see it. `bump_internal_requirements` now moves them on every MAJOR, and run `cargo update -w` in BOTH workspaces (`crates/rustynes-cosim` has its own lock) after any bump. (2) A CHAIN anchor with its own description (`vX "C" released — . Built on ...`, root `ROADMAP.md`) kept the OLD release's description under the new name; CodeRabbit caught "v3.0.0 ... the tenth release of the v2.9.x line". The selftest had that exact shape and asserted only where the insertion landed. A described head now takes `--lead`, and the old text moves into the chain beside its own release. diff --git a/docs/agents/review-bots.md b/docs/agents/review-bots.md index 2e8ea5ec8..013e4061c 100644 --- a/docs/agents/review-bots.md +++ b/docs/agents/review-bots.md @@ -32,5 +32,5 @@ - **AGY RE-FILES REFUTED FINDINGS EVERY ROUND, SO THE CEREMONY NEEDS A STOPPING RULE (2026-09-23, #545 / sibling #30 / #31).** Four rounds on three docs PRs. By round 4 the Antigravity reviewer was re-raising, sometimes as **BLOCKING**, claims already refuted with line numbers in rounds 1–3: "`$G` may be undefined" (`tb/regress.sh:28` defines it), "`$VERIFY` is unbound under `set -u`" (`tb/fetch-goldens.sh:123` defaults it, three times), and "TSV `#` lines crash the loop" (`:240` skips them). It re-reviews on every push and does not carry forward the rebuttals, so every fix push buys another round with the same claims in it. **Rule:** fix what is real, reply to everything with evidence, and when a round brings only repeats and nits, reply to them and merge on green rather than pushing again. Every push restarts CI (the `test-roms` job alone is tens of minutes) *and* the reviewer. The rounds still paid: they found an ADR consequence describing a rename that did not happen, a `BLARGG_DIR` default ignoring `GOLDEN_DIR` (and the same bug in `mutate_apu.sh`), and a sentence ("measured on the console") that read as a hardware claim. Declining is only safe after checking each claim, because the first "blocking" finding of a round is not always the repeat it looks like. - **COPILOT REVIEWS ONCE PER PR HERE, AND DOCS7 FLAGS THE PRIVATE SIBLING AS DEAD (2026-09-23).** On all three PRs Copilot posted a single review, on the first push, and never re-reviewed later pushes. So the merge gate can only check that it *has* posted, not that it reviewed the head; agy's comment timestamp is the per-push signal. Context7's **Docs7** bot (a `context7[bot]` issue comment plus a NEUTRAL check) link-checks changed files as an **anonymous** client. Every `https://github.com/doublegate/RustyNES_MiSTer/...` link is therefore reported dead, because that repository is **private** (`gh repo view --json visibility` returns `PRIVATE`). Those are correct links, not findings. Its other catch was real: two pre-existing links into `to-dos/` directories that had moved to `to-dos/archive/`. -- **CODERABBIT SKIPS A PR OVER 100 FILES; REVIEW IT AS SLICES, AND EXPECT CROSS-SLICE FALSE FINDINGS (v2.9.8, v3.0.0).** "Review skipped: 163 files exceed the limit of 100." Both MAJOR release PRs were reviewed as review-only slice PRs (`review/vX-{a,b}` from `main`, one path partition each, built with `git diff --binary -- | git apply --index`, and proved equal to the head on their paths with `git diff --quiet -- `). A slice does not build alone, so it is committed with `--no-verify` and says so. Keep the slices in sync on every release-branch push, and close them unmerged. The trap: a slice lacks the other half's changes. On #587 CodeRabbit rated Major that `rustynes-core` still built the now-`#[non_exhaustive]` `Cartridge` by literal; that change was in the other slice. Shown the release head's lines, it withdrew. It is also rate-limited hourly on this plan, so schedule the re-request rather than re-posting. +- **CODERABBIT SKIPS A PR OVER 100 FILES; REVIEW IT AS SLICES, AND EXPECT CROSS-SLICE FALSE FINDINGS (v2.9.8, v3.0.0).** "Review skipped: 163 files exceed the limit of 100." Both MAJOR release PRs were reviewed as review-only slice PRs (`review/vX-{a,b}` from `main`, one path partition each, built with `git diff --binary -- | git apply --index`, and proved equal to the head on their paths with `git diff --quiet -- `). A slice does not build alone, so it is committed with `--no-verify` and says so. Keep the slices in sync on every release-branch push, and close them unmerged. The trap: a slice lacks the other half's changes. On #587 CodeRabbit rated Major that `rustynes-core` still built the now-`#[non_exhaustive]` `Cartridge` by literal; that change was in the other slice. Shown the release head's lines, it withdrew. It is also rate-limited hourly on this plan, so schedule the re-request rather than re-posting. **A slice is also RED in CI by construction (v3.0.1, #591)**: feature-gated tests that live in one slice and depend on the other (`holy_mapperel.rs`, the provenance audit reading files the other slice changes) fail there and nowhere else. Say so on the slice PR at once, with the named tests, and judge CI on the release PR only. - **AGY'S BLOCKING FINDINGS CAN CITE CODE THAT DOES NOT EXIST (v3.0.0, #586).** One round filed a BLOCKING "state desync in `SpectatorSession::pop_frame`... `push_frame` relies on `relative_idx`"; none of the three exists in the spectator (`git grep` found only the movie attestation builder's `push_frame`). The same round cited `crates/rustynes-core/src/netplay.rs` and `scripts/bump_release.py`, neither of which exists. Another round called `let _ = socket.send_to(..)` a silent failure, against the module's documented contract and four sibling sends. agy edits ONE comment in place, so these sat in the folded "Earlier review rounds" block. `git grep` every cited symbol and `ls` every cited path before acting; refute with the command and the line numbers. diff --git a/docs/agents/tooling-traps.md b/docs/agents/tooling-traps.md index cd6a2c781..e67cd3a4a 100644 --- a/docs/agents/tooling-traps.md +++ b/docs/agents/tooling-traps.md @@ -31,4 +31,5 @@ - **`set -- $var` DOES NOT WORD-SPLIT IN zsh EITHER (v2.9.8).** Same root cause as the `for x in $var` entry above: a loop `for rb in "repo branch" ...; do set -- $rb; gh api -X DELETE .../$1/.../$2` passed the whole string as `$1` and got three HTTP 404s. Call the command once per item with literal arguments. - **A 1PASSWORD OUTAGE BLOCKS CHERRY-PICKS, NOT ONLY COMMITS (v2.9.8).** Integrating an agent's branch re-signs each picked commit, so the whole integration stops until 1Password is back. Nothing is lost: the agent's commits stay on its branch, and staged work stays staged. Ask the maintainer to unlock it; never pass `--no-gpg-sign`. - **PARALLEL WORKTREE AGENTS NEED THE TIP'S MOVES TOLD TO THEM (v2.9.8).** Five agents worked from one base while their siblings' commits landed. Each time the branch moved (a rename, a removed API), the still-running agents were sent the new tip and the breaking facts and told to rebase before their final commit. Integration then needed only CHANGELOG and ADR conflict resolution. To relieve a loaded host, an agent can be paused at a safe point: let its current cargo job finish, write a status file, end its turn, and resume it later by message. +- **`pgrep -f ` IN A WAIT LOOP MATCHES THE LOOP ITSELF (v3.0.1).** `until ! pgrep -f quartus_fit; do sleep 30; done` never ended: the pattern appears in the loop shell's own command line, so `pgrep` always finds one process. Three background loops stayed alive this way after the jobs they waited for had finished, and had to be stopped with TaskStop. Wait on a PID (`while kill -0 $pid`), on a file the job writes last, or use the bracket trick (`pgrep -f '[q]uartus_fit'`). The same mechanism is why `pkill -f` killed the tool shell (memory, and the trap below). - **A WORKTREE-ISOLATED AGENT CANNOT RUN GIT HERE, AND ITS WORKTREE STARTS FROM `main` (v3.0.1).** Two forks were given `isolation: worktree` on 2026-10-07. Both worktrees were created from `main` (`9c23715b`), not from the release branch the session was on. The first agent could still run git and reset its own clean worktree to the right commit. The second could not run git at all: the RTK hook rewrites every `git` into `rtk git`, and the worktree-isolation guard refuses a command that "runs rtk with a git command among its operands" (plain `git`, `git -C`, `command git` and `rtk git` were all refused). It made no change, and the harness removed the unchanged worktree itself. For documents-only work, run the fork in the main tree instead, stage by explicit path, and make no commits from the session until it reports. Otherwise, check the agent's first `git log` before trusting anything it writes. diff --git a/docs/apu-2a03.md b/docs/apu-2a03.md index 29aa4561f..d46035a58 100644 --- a/docs/apu-2a03.md +++ b/docs/apu-2a03.md @@ -326,6 +326,25 @@ why the predicate is `put_cycle` rather than `!put_cycle`). The behavior is end-to-end via `dmc_tests/latency.nes` (a deterministic DMC fetch-latency audio signature) and the strictly-passing `sprdma_and_dmc_dma` alignment ROM. +#### A load DMA refused by a write takes four cycles (v3.1.0) + +RDY cannot halt a write. When a pending LOAD DMA reaches the get half on which +it would enter and that cycle is a CPU write, the load is refused and enters +on the very next read **whichever half that is**: refused by one write it +lands on a put half and takes four cycles (`[Put (halt)] [Get] [Put] [Get]`); +refused by two consecutive writes it lands on a get half and takes three. The +get-half deferral above therefore does not apply a second time to a +write-refused load. The bus records the refusal in a one-shot latch +(`dmc_load_write_delayed`, `crates/rustynes-core/src/bus.rs`), set in +`Bus::write`, consumed by the DMC entry in `unified_dma_cycle_impl` and cleared +by the next CPU read; it is in the BUS save-state section (version 3), because +the refusing write is the last cycle of a store and a snapshot can fall between +it and the next opcode fetch. Written from AccuracyCoin `DMA Landing on Write` +test 9 (upstream `f5f41dc2`) and its cycle comments, and confirmed by a +black-box per-cycle comparison with TriCNES's output at the test's +`STA $5000`: before the fix the CPU ran the opcode fetch the hardware spends +halted, one cycle ahead from then on. + ### Mixer Per `ref-docs/research-report.md` §APU Mixer, two implementations: diff --git a/docs/compatibility.md b/docs/compatibility.md index 6209de55b..67c9794f6 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -144,10 +144,13 @@ screens via `main_framebuffer()` and `sub_framebuffer()`. As of v2.1.2 "Fathom" (F2.1) the **desktop frontend presents both screens** — side-by-side (512×240, default) or stacked (256×480) via `[graphics] dual_screen_layout` — with P1/P2 → main, P3/P4 → sub, coin (F10) → main acceptor, and the main console's audio (ADR -0032). The advanced single-`Nes` features (run-ahead / rewind / netplay / TAS / -dual save-state), the debugger, and HD-pack are **scoped out in dual mode**; -libretro + wasm + mobile presentation remain deferred; real-cabinet boot stays -fixture-limited (maincpu-half dumps). The non-DualSystem games (Excitebike, Clu +0032). Netplay, TAS, the debugger, and HD-pack are **scoped out in dual mode**; +save states work there since v2.9.7, and rewind and run-ahead since v3.1.0, on +the whole cabinet. Libretro presents both screens since v2.1.10 (512×240, +`docs/libretro/advanced_features.md`), the browser since v2.9.7, and the mobile +bridge carries the cabinet since v2.9.7 (its device rows are T1-T12 of +`docs/mobile-v2.9.3-run-sheet.md`; the Swift half has not been compiled). +Real-cabinet boot stays fixture-limited (maincpu-half dumps). The non-DualSystem games (Excitebike, Clu Clu Land, Castlevania, Pinball, Gradius, Goonies, Ice Climber, Golf, Super Mario Bros.) boot and render with their correct 2C04 palette. **PlayChoice-10's second-screen instruction menu and its Z80 coprocessor are out @@ -211,7 +214,7 @@ unfinished); the fix is a faithful port of TriCNES's eval-pointer model — the corrupted index is the live secondary-OAM evaluation pointer (`OAM2Address`), captured at the disable edge during dots 1-64 and committed on re-enable. Default builds stay byte-identical for games without such a split; AccuracyCoin -OAM-Corruption (0x047B) + 139/141 (the two newest upstream PPU tests are known gaps), nestest 0-diff, and blargg/kevtris remain +OAM-Corruption (0x047B) + 139/141 at the time (the two newest upstream PPU tests were then known gaps; 146/146 at upstream `f5f41dc2` since v3.1.0, `docs/STATUS.md`), nestest 0-diff, and blargg/kevtris remain green; `repro_smb3 --movie` idle drop count went 63-80/240 → 0. See `ppu-2c02.md` edge-case 2 and `crates/rustynes-test-harness/src/bin/{repro_smb3,smb3_dma_trace}.rs`. @@ -220,10 +223,10 @@ edge-case 2 and `crates/rustynes-test-harness/src/bin/{repro_smb3,smb3_dma_trace | Mapper audio | Status | Notes | |--------------|--------|-------| | MMC5 (2 pulse + raw PCM) | **Landed** (`mapper-audio`, Track C2 / Phase 2.3) | Castlevania III JP, Just Breed, Laser Invasion | -| VRC6 (3 channels) | Phase 4 | Akumajou Densetsu, Madara, Esper Dream 2 | +| VRC6 (3 channels) | **Landed** (`m024_vrc6.rs`, `Mapper::mix_audio`) | Akumajou Densetsu, Madara, Esper Dream 2 | | VRC7 (FM, 6 channels) | **Landed** — clean-room `emu2413` port (`crates/rustynes-apu/src/opll.rs`, MIT); ADR 0006 supersedes ADR 0004 | Lagrange Point (JP) plays with in-game audio. | -| Sunsoft 5B (3 channels) | Phase 4 | Gimmick! | -| Namco 163 (1-8 channels) | Phase 4 | Several Japanese RPGs | +| Sunsoft 5B (3 channels) | **Landed** (`m069_sunsoft_fme7.rs`, `Mapper::mix_audio`) | Gimmick! | +| Namco 163 (1-8 channels) | **Landed** (`m019_namco163.rs`, `Mapper::mix_audio`) | Several Japanese RPGs | | FDS (wavetable + envelope) | **Landed** | 2C33 — 64-entry wavetable + 32-step modulation + envelopes + master volume; behind `mapper-audio` | ## Game compatibility goals (v1.0) @@ -320,8 +323,9 @@ This section supersedes the early "Out-of-scope" list above where they disagree punching bag (`Nes::set_bandai_hyper_shot`, the 8-sensor `$4016`-bit-1- multiplexed read, unit-verified against the `NESdev` "Exciting Boxing Punching Bag" page). All are additive, default-off `InputDevice` overlays, so - `ExpansionDevice::None` keeps every read byte-identical.) The microphone - remains deferred. (DMC-DMA controller-bit corruption is **modelled** as of + `ExpansionDevice::None` keeps every read byte-identical.) The Famicom + microphone shipped in v2.2.0 (`Nes::set_microphone`, `$4016` D2; + `docs/accuracy-ledger.md`). (DMC-DMA controller-bit corruption is **modelled** as of v1.4.0 — see `Bus::dmc_dma_read`, gated by `dmc_dma_during_read4/dma_4016_read`, `sprdma_and_dmc_dma`, and `read_joy3/count_errors`.) - **Vs. System / PlayChoice-10 (2C03/04/05 RGB PPUs) — game-verified.** @@ -339,11 +343,22 @@ This section supersedes the early "Out-of-scope" list above where they disagree accepted only when: (a) there is concrete user demand or a notable title that needs it, **and** (b) a redistributable test fixture or a well-specified nesdev page exists, **and** (c) it carries NES 2.0 metadata for unambiguous - detection — weighed against maintenance cost. MMC5's >8 KiB multi-chip PRG-RAM - configs fall under this policy (no corpus fixture; out of scope until one - appears). + detection — weighed against maintenance cost. (MMC5's >8 KiB multi-chip + PRG-RAM configs were named here as falling under this policy; v2.7.2 + implemented them from `MMC5.xhtml` as the wiki's 64 KiB superset, + `docs/audits/core-disposition.md` §5.3.) ## Open questions +Checked at v3.1.0 against the code, and recorded together as backlog item +FE-11, which the v3.1 → v4.0 line plan does not schedule: + +- the **CRC32 question is half-answered**: the per-game correction database + (`crates/rustynes-gamedb`, vendored from TetaNES) is CRC32-keyed, while the + Vs. System database (`crates/rustynes-core/src/vs_db.rs`) and save-state + naming key on SHA-256; +- the **region override is still open**: no desktop, mobile or libretro + frontend overrides the region the header declares. + - **CRC32 vs. SHA-256 for ROM identification.** We use SHA-256 for save state directory naming (lower collision risk). For ROM compatibility databases, CRC32 is the community standard; we may add it as a secondary key. - **Region override.** Some users want to play PAL versions of NTSC games at NTSC speed. Plan: expose a region override in the settings UI; warn that this may break timing-sensitive ROMs. diff --git a/docs/dev/TESTING.md b/docs/dev/TESTING.md index 7793b4ede..1be159404 100644 --- a/docs/dev/TESTING.md +++ b/docs/dev/TESTING.md @@ -19,7 +19,7 @@ RustyNES employs a comprehensive testing strategy combining unit tests, integrat ### Testing Goals -- **AccuracyCoin 144/144 (100.00%)** since v2.6.18, on the catalog re-synced at v2.6.17 (144 assigned tests). It read 139/139 before the v2.0.1 re-sync grew it to 141, and 141/141 from v2.0.3 +- **AccuracyCoin 146/146 (100.00%)** since v3.1.0, on the catalog re-synced to upstream `f5f41dc2` (146 scored tests); the 144/144 reported from v2.6.18 to v3.0.1 was overstated, because the older ROM's `Misaligned OAM behavior` fail path fell through to a pass and hid a real failure. It read 144/144 from v2.6.18, on the catalog re-synced at v2.6.17 (144 assigned tests). It read 139/139 before the v2.0.1 re-sync grew it to 141, and 141/141 from v2.0.3 - **Unit test coverage** for all components - **Integration tests** for component interactions - **Regression tests** for mapper edge cases (191 mapper families) diff --git a/docs/frontend.md b/docs/frontend.md index 46119a8aa..ecda55cfa 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -740,6 +740,20 @@ waterfall/dither transparency tricks. Off by default (a deliberate visual choice, re-blessed like the generated palette); the default framebuffer + `visual_regression` corpus stay byte-identical. +**Differential phase distortion (v3.1.0, `T-COMPOSITE-ARTIFACTS`, ACC-02).** The +signal-decode pass's sixth knob, `diff_phase` (degrees per palette row, default +**0 = off**), rotates the demodulated hue by that angle times the centre pixel's +palette row (0-3), in the direction of a delay. NESdev "NTSC video": the PPU's +level-dependent output impedance delays the chroma phase more at brighter +levels, "about 2.5° (2C02E) or 5° (2C02G) of additional rotation for each row of +the palette". Greys carry no chroma and are unaffected. The other composite +artifact the page describes, colour error between neighbouring pixels (8 +clocks per pixel against a 12-clock colour cycle), is what this pass already +reproduces by decoding the true signal, so v3.1.0 adds only the distortion. +Presentation only: no emulated byte changes, so no epoch rise. The mobile and +web hosts write the knob as 0 and do not expose it; the CPU Bisqwit filter +does not model it. + **Vs. `DualSystem` two-screen presentation (v2.1.2 F2.1).** A loaded Vs. `DualSystem` cabinet (Balloon Fight / Wrecking Crew / Tennis / Baseball) runs both cross-wired consoles and presents them together. The core dual engine @@ -760,10 +774,20 @@ both consoles. Every load path makes the same decision through command-line path installed a cabinet image as a single console, which runs the main CPU alone and never completes the boot handshake. The single-console path is byte-identical (the dual path is a parallel branch at each chokepoint). **Scoped out in dual mode (ADR 0032):** -run-ahead, rewind, netplay, TAS, the debugger, and HD-pack — they snapshot a -single `Nes`. **Save states work in dual mode since v2.9.7**, through the -cabinet's own "RVSD" snapshot: `EmuCore::save_state_blob` / -`restore_state_blob` (ADR 0032's amendment). Real-cabinet boot stays fixture-limited (the circulating +netplay, TAS, the debugger, and HD-pack. **Save states work in dual mode since +v2.9.7**, through the cabinet's own "RVSD" snapshot: `EmuCore::save_state_blob` +/ `restore_state_blob` (ADR 0032's first amendment). **Rewind and run-ahead +work in dual mode since v3.1.0** (`T-PS-dual-runahead`, the second amendment), +on the whole cabinet and never on one console, because the two share a WRAM +and drive each other's `/IRQ`. The cabinet has its own rewind ring +(`VsDualSystem::enable_rewind_with`, sized from `[rewind]` by `rewind_budget` +like a single console's), whose entries are whole RVSD containers with both +framebuffers, so a step back (`VsDualSystem::rewind_step_back`) restores both +screens exactly without re-rendering. Run-ahead is +`RunAhead::run_cabinet_ahead` / `finish_cabinet`: the single-console cycle on +the cabinet, rolled back with `VsDualSystem::restore_quiet`, which keeps the +ring. `produce_dual_frame` routes `rewind_held` and the run-ahead depth to +them; the cabinet still keeps stock timing (no overclock). Real-cabinet boot stays fixture-limited (the circulating dumps are the MAME maincpu half only). **Present-path parity (v2.1.10 "Web Parity").** The **libretro** core @@ -1025,8 +1049,9 @@ Per-tab content the panel sections render (`debugger/settings_panel.rs`): netplay drive sites call `force_stock_timing` before a tick, because every peer must run the same timeline. A Vs. DualSystem cabinet keeps stock timing (ADR 0032 scopes enhancements out of dual mode). The test harness builds its - own `Nes` and never sets it. **Disable sprite limit** is still inert: the core - has no hook for it. The **Accuracy** group above it carries OAM decay and, + own `Nes` and never sets it. **Disable sprite limit** reaches the core since + v3.1.0 (`Nes::set_sprite_limit_disabled`, applied beside the v3.1.0 CPU + overclock outside a movie session; until then the setting was inert). The **Accuracy** group above it carries OAM decay and, from v2.9.8, **Famicom console (PPU leaves reset early)** — `[emulation] famicom_console` (default `false`, the NES model, byte-identical). `console_model_for` maps it to `rustynes_core::ConsoleModel`; @@ -1210,8 +1235,9 @@ builds stay byte-identical and AccuracyCoin holds 139/141 (the two newest upstre Frontend-only, additive, English-by-default — with the default locale every label is byte-identical to v1.6.0. (At v1.7.0 AccuracyCoin held 139/141; the two -PPU gaps closed at v2.0.3, and the re-synced catalog has held 144/144 since -v2.6.18.) See +PPU gaps closed at v2.0.3, the re-synced catalog read 144/144 from v2.6.18, +and 146/146 from v3.1.0, whose re-sync showed the 144/144 had hidden a +masked failure.) See ADR 0023 for the rationale (why a hand-rolled catalog over Fluent/ICU/`rust-i18n` and the wasm size budget). @@ -2461,7 +2487,12 @@ says which emulator *behaviour* it was recorded on as well as which options and board. A format-4 movie is refused as too old, and a format-5 movie from another epoch fails with `MovieError::EpochMismatch`, naming both epochs. The epoch is raised whenever a change alters emulated output; the bump rule is in -ADR 0045. +ADR 0045. **v3.1.0 moved it to format 6** (`MOVIE_FORMAT_VERSION` 6, minimum +6): the options record gains the CPU-multiplier overclock and the sprite-limit +option, so a format-5 movie is refused as too old (every one of them was +recorded under epoch 1 or 2, which v3.1.0 refuses anyway). Unlike the +extra-scanline overclock, which a recording holds at stock, the CPU +overclock is recorded as set and replayed with it. - **Playback applies the options before frame 0** (`Movie::seek_to_start`), so the replay does not depend on the player's settings, and the desktop and mobile @@ -2510,8 +2541,13 @@ ADR 0045. The `Sync` handshake carries a `rustynes_netplay::SessionIdentity`: the emulation epoch, the ROM hash, and `rustynes_core::config_digest`, SHA-256 over -the region, the board and the options (`PROTOCOL_VERSION` 6, magic `"RNE6"`, -ADR 0045). +the region, the board and the options (`PROTOCOL_VERSION` 7, magic `"RNE7"`, +since v3.1.0; protocol 6 / `"RNE6"` from v3.0.0, ADR 0045). Protocol 7 keeps +protocol 6's 72-byte `Sync` and changes the magic because the options encoding +under the configuration hash gained two fields: a v3.0.x peer with the same +settings would otherwise hash differently and be refused as a settings +mismatch, the wrong reason. Its `"RNE6"` is now one of RustyNES's older magics, +refused as another emulator version naming its epoch. - **Another epoch is refused first**, as another emulator version: `DisconnectReason::EmulatorMismatch` / `NetplayError::EmulatorMismatch` / diff --git a/docs/ios-v1.9.9-readiness.md b/docs/ios-v1.9.9-readiness.md index bf71e1eb5..c3d107835 100644 --- a/docs/ios-v1.9.9-readiness.md +++ b/docs/ios-v1.9.9-readiness.md @@ -104,6 +104,12 @@ advertised `.fds` / `.nsf`, which would fail on selection. v1.9.9 trims the pick document types to NES (+ `.zip`) for honesty; FDS + NSF on mobile are a post-v2.0.0 carryover. +> **Later (pointer added v3.1.0, records item DOC-09; this record is historical +> and is otherwise left as written).** The carryover shipped in **v2.9.7**: the +> mobile bridge loads FDS (with a host-supplied BIOS), NSF and the Vs. +> DualSystem cabinet. The Swift half has not been compiled, and the device rows +> are T1-T12 of [`mobile-v2.9.3-run-sheet.md`](mobile-v2.9.3-run-sheet.md). + ## 4. Completeness-critic findings No merge-blocking defects. Polish items found and dispositioned: diff --git a/docs/libretro/UPSTREAM_SYNC.md b/docs/libretro/UPSTREAM_SYNC.md index cf1b3efe8..27c492326 100644 --- a/docs/libretro/UPSTREAM_SYNC.md +++ b/docs/libretro/UPSTREAM_SYNC.md @@ -155,11 +155,18 @@ At this point, you can safely navigate to your repository settings on GitHub and --- -## Sync for v3.0.0 (prepared 2026-10-04, not submitted) +## Sync for v3.0.0 (prepared 2026-10-04, submitted 2026-10-06) Both forks were synced to upstream `master` (`libretro-super` `a7054054af`, -`docs` `36e9222824`) and carry a branch `rustynes-v3.0.0-sync`. No pull request -is open; v3.0.0 submits them (ADR 0043). +`docs` `36e9222824`) and carry a branch `rustynes-v3.0.0-sync`. + +**Status (checked 2026-10-07, v3.1.0 records item DOC-06).** Both were +submitted after the v3.0.0 tag. The `.info` PR, +[libretro/libretro-super#2131](https://github.com/libretro/libretro-super/pull/2131), +**merged** on 2026-10-06 (`9c08e5e6f3`). The docs page PR, +[libretro/docs#1215](https://github.com/libretro/docs/pull/1215), is **open**, +with its one review finding fixed and answered. Until v3.1.0 this section said +no pull request was open. | repo | branch commit | change | | --- | --- | --- | diff --git a/docs/mappers.md b/docs/mappers.md index 55eafc8d8..15eea5301 100644 --- a/docs/mappers.md +++ b/docs/mappers.md @@ -30,6 +30,9 @@ pub trait Mapper: Send { fn save_state(&self) -> Vec; fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError>; + + // v3.1.0 (`T-SPRITE-LIMIT`): `false` when a CHR read changes the board. + fn chr_reads_are_pure(&self) -> bool { true } } // `#[non_exhaustive]` since v3.0.0: outside `rustynes-mappers`, build one with @@ -56,6 +59,19 @@ pub struct Cartridge { pub enum Mirroring { Horizontal, Vertical, SingleScreenA, SingleScreenB, FourScreen, MapperControlled } ``` +**`chr_reads_are_pure` (v3.1.0).** The PPU's "disable sprite limit" option +makes extra, display-only pattern reads, and only on boards that report +`true`. Five report `false` because a CHR read changes them: MMC2 (9) and +MMC4 (10) switch a CHR latch on tiles `$FD` / `$FE`, the J.Y. ASIC (35, 90, +209, 211) clocks an IRQ counter on PPU reads and mapper 209 latches CHR, +Bandai 96 follows the last PPU address for its inner CHR bank, and Nanjing 163 +latches PPU A13. **A new board whose `ppu_read` writes `self` must override +it.** `every_board_that_claims_pure_chr_reads_has_them` (in `mapper.rs`) reads +all of CHR on every constructible mapper id that claims purity and fails if +`save_state` moved; it also pins the impure set, so the list here and the code +cannot drift apart. The set came from a scan of every `ppu_read` body for +writes to `self`. + `rustynes_mappers::parse(&[u8]) -> Result<(Cartridge, Box), RomError>` parses an iNES or NES 2.0 file (see `cartridge-format.md`), constructs the appropriate concrete mapper, and returns the metadata header together with @@ -279,7 +295,7 @@ Sorted by number of commercial titles using each mapper. | 1 | 1-5 | MMC1 (SUROM, SXROM, etc.) | 2 | — | — | landed (Phase 2) | Serial 5-write protocol; consecutive-write bug. On boards with at most 8 KiB of CHR (v2.7.2, from `nesdev_wiki/MMC1.xhtml`), the CHR bank register's bit 4 selects the 256 KiB PRG half for the whole window, fixed bank included (SUROM / SXROM), and bits 3-2 select the 8 KiB PRG-RAM bank (SOROM: bit 3; SXROM: bit 3 = A14, bit 2 = A13). In 4 KiB CHR mode the driving register is the one the last CHR fetch selected. SNROM's bit-4 RAM enable applies only to <= 256 KiB PRG with <= 8 KiB RAM. holy_mapperel `M1_P512K_CR8K_S8K` / `_S32K` pass `0000`. SZROM is not modelled. | | 2 | 0-2 | UxROM | 2 | — | — | landed (Phase 2) | UNROM, UOROM, etc. CHR-RAM only. | | 3 | 0-2 | CNROM | 2 | — | — | landed (Phase 2) | Bus conflict required. | -| 4 | 0-3 | MMC3 (and MMC6, sub 1) | 4 | — | A12 | landed (Phase 4 / S1) | Sharp vs NEC IRQ revision; default Sharp. The IRQ line is raised at the first per-cycle hook after the A12 rise (v2.9.9, pitfall 2). `mmc3_test/5-MMC3` and `mmc3_test_2/5-MMC3` pass; both `4-scanline_timing` ROMs pass all 13 sub-tests since T-MMC3-BG-A12, a PPU change (`docs/ppu-2c02.md` pitfall 4); they failed at sub-test 9 from v2.9.9. | +| 4 | 0-3 | MMC3 (and MMC6, sub 1) | 4 | — | A12 | landed (Phase 4 / S1) | Sharp vs NEC IRQ revision; default Sharp. v3.1.0: `Nes::set_mmc3_revision_override` selects the alternate (NEC) revision for any mapper-4 ROM, and its `$C001` reload to 0 now asserts as documented, so `mmc3_test_2/6-MMC3_alt` passes under it. The IRQ line is raised at the first per-cycle hook after the A12 rise (v2.9.9, pitfall 2). `mmc3_test/5-MMC3` and `mmc3_test_2/5-MMC3` pass; both `4-scanline_timing` ROMs pass all 13 sub-tests since T-MMC3-BG-A12, a PPU change (`docs/ppu-2c02.md` pitfall 4); they failed at sub-test 9 from v2.9.9. | | 5 | — | MMC5 | 4 | yes (landed) | scanline | v0+v1 landed (Phase 4 / S4) | Banking + scanline IRQ + ExRAM modes 10/11 + multiplier (v0). Fill mode (`$5106`/`$5107`), dual sprite/BG CHR registers used for sprite tile fetches, ExGrafix per-tile attribute + CHR override (mode 01) (v1). Vertical split-screen (`$5200-$5202`) via `bg_split_state` (Castlevania III J status bar) and the MMC5 audio extension (two pulse + 7-bit PCM, `$5000-$5015`, behind the default-on `mapper-audio` feature) landed. PRG-RAM banking (v2.7.2, `nesdev_wiki/MMC5.xhtml` §"PRG-RAM configurations"): `$5113` and RAM-mode `$5114-$5116` page the RAM by the bank value's low three bits over the wiki's 64 KiB "compatible superset for all games", because PRG-RAM sizes in headers are unreliable (*L'Empereur*'s NES 2.0 dump under-declares its ETROM board); a 16 KiB window takes A13 from the CPU. `sram()` is the battery-backed part of the header's declared RAM: all of it, except ETROM's 16 KiB, where only the first chip is saved. | | 7 | 0-2 | AxROM | 2 | — | — | landed (Phase 2) | Single-screen mirroring control. | | 9 | — | MMC2 | 4 | — | — | landed (Phase 4 / S2) | Punch-Out; latched CHR per fetch ($FD/$FE). | @@ -1008,7 +1024,21 @@ chunked `NSFE` containers; the FDS-style `$5FF6/$5FF7` RAM banking remains defer ## Open questions -- **MMC3 default revision** when iNES (no submapper) is detected. Plan: default Sharp (MMC3A) since Star Trek requires it; expose a config override. -- **Mapper #5 (MMC5) audio scope.** Implementing the 2 extra pulse + raw PCM channels is non-trivial; defer behind a `mmc5-audio` cargo feature. +Re-checked against the code at v3.1.0 (records item DOC-03): the first two +and the fourth are answered, and are kept with their answers rather than +deleted. + +- **MMC3 default revision** when iNES (no submapper) is detected. *Answered:* + the default is Sharp (the "normal" IRQ behaviour); NES 2.0 submapper 4 selects + the alternate one (MMC3A and the non-Sharp MMC3B), and since v3.1.0 any + mapper-4 game can be forced either way (`Mapper::set_mmc3_revision_override`, + desktop `[emulation] mmc3_irq_revision`, `T-MMC3-NEC-OVERRIDE`). This line + used to call the Sharp default "MMC3A", which is the other revision. +- **Mapper #5 (MMC5) audio scope.** *Answered:* the two extra pulses and the + raw PCM channel landed behind `mapper-audio` (Track C2 / Phase 2.3; the audio + table in `docs/compatibility.md`). - **VRC7 FM audio.** YM2413-derived; only Lagrange Point uses it commercially. Banking + IRQ landed in Track C2 / Phase 2.4 (mapper 85; same `mapper-audio` feature flag as VRC6 / Sunsoft 5B / Namco 163 / MMC5). **The FM synthesizer landed** via a clean-room pure-Rust port of `emu2413 v1.5.9` (MIT) at `crates/rustynes-apu/src/opll.rs`; ADR 0006 (`docs/adr/0006-vrc7-audio-landed.md`) supersedes the ADR 0004 deferral. *Lagrange Point* plays with in-game audio (mixed via the `mapper-audio` slot). -- **Pirate / multicart mappers.** 60+ exist; none in initial scope. Architecture supports adding them but no commitment. +- **Pirate / multicart mappers.** *Answered by policy:* none were in the initial + scope; many are in now (191 families, `docs/STATUS.md`), each admitted under + the long-tail policy in `docs/compatibility.md` (demand, a fixture or a + specific NESdev page, and NES 2.0 detection). diff --git a/docs/mobile-v2.9.3-run-sheet.md b/docs/mobile-v2.9.3-run-sheet.md index 893a4bb8e..65184b8a9 100644 --- a/docs/mobile-v2.9.3-run-sheet.md +++ b/docs/mobile-v2.9.3-run-sheet.md @@ -162,6 +162,42 @@ identity, returns a pre-v2.9.9 entry for the same file instead of adding one) an platform: cloud save-state records (Play Games snapshots, CloudKit), which the next upload writes under the new key. +## Rows folded in from the v1.8.x Android checklist (v3.1.0, decision D25) + +`to-dos/v1.8.x-on-device-verification.md` was the standing Android checklist +from v1.8.x to the v2.1.0 launch plan. v3.1.0 folds it in here and deletes it, +so one document carries the device runs. Only its rows that no row above +already covers are kept, rewritten for the current tree; its history is in git. +Dropped as covered: battery SRAM across a restart (A1), audio focus, PiP and +background (A3-A6), rotate and PiP return (A7), save-state slots (A10) and +multi-touch (A13). One row was dropped because it is now **wrong**: "the +picker offers iNES / NES 2.0 only". FDS and NSF shipped in v2.9.7 (T1-T7). Its +accuracy paragraph ("AccuracyCoin 139/141") is replaced by the rule it stated: +the device runs the host's byte-identical core, so AccuracyCoin (146/146 at +v3.1.0) is measured on the host and never on a phone, and a device that +diverges from the host's frames has a shell bug, not a core one. + +| # | Platform | Step | Expect | Result | +| --- | --- | --- | --- | --- | +| G1 | Android | Import a `.nes` through the Storage Access Framework picker | It appears in the library and boots | NOT RUN | +| G2 | Android | Import a NES 2.0 ROM; open its ROM info | Mapper and region read correctly | NOT RUN | +| G3 | Android | Online, open the library | Box art auto-matches a grid entry (skip on `foss` offline) | NOT RUN | +| G4 | Android | Re-open a previously imported ROM from the library | It resumes cleanly | NOT RUN | +| G5 | Android | Hold rewind, then release | The picture runs backward smoothly, then forward | NOT RUN | +| G6 | Android | Load a `.rns` written by the desktop build of the SAME release, and write one back | Each loads on the other side | NOT RUN | +| G7 | Android | Load a `.rns` or `.rnm` from v3.0.1 or earlier | Refused with a clean message naming the reason (state layout, or emulation epoch for a movie), never a crash | NOT RUN | +| G8 | Android | Pair a hardware controller (Xbox, DualSense or MFi, Bluetooth or USB-OTG) | Binds to P1: South=A, West=B, Start, Select, D-pad | NOT RUN | +| G9 | Android | Pair two to four controllers | Each binds to its own port (the Four Score path) | NOT RUN | +| G10 | Android | Disconnect and reconnect a controller mid-game | No crash and no stuck input | NOT RUN | +| G11 | Android | Plug and unplug headphones while playing | Audio keeps playing; the audio thread does not wedge | NOT RUN | +| G12 | Android | *Super Mario Bros.*: World 1-1 through the first screen | Status bar, sprites and scroll correct, no flicker | NOT RUN | +| G13 | Android | *The Legend of Zelda*: title, overworld, then an in-game save and reload | Renders correctly; the save round-trips | NOT RUN | +| G14 | Android | From one save state, play the same inputs twice | Identical results (the determinism contract on the device) | NOT RUN | +| G15 | Android | On a tablet or unfolded foldable, then a phone | Two-pane and compact layouts both correct; the image letterboxes at any aspect | NOT RUN | +| G16 | Android | Fold and unfold mid-game; on Android TV, navigate with the D-pad | No restart on fold; every TV control reachable, and the app boots to its TV banner | NOT RUN | +| G17 | Android | `foss` flavor: inspect the merged manifest and Settings | No `AD_ID` permission, no Play Services metadata, no Billing, Play Games or Cast surface (ADR 0025) | NOT RUN | +| G18 | Android | `play` flavor (`./gradlew :app:installPlayDebug`): boot a ROM, then open Settings | Installs and plays like `foss`; its Play-Services surface (Play Games, cloud save, Cast, in-app updates) appears, and a device without Play Services still boots and plays | NOT RUN | + ## Android EMULATOR is what the `Pixel_8_API_34` emulator showed on this build, before diff --git a/docs/nesdev-hardware-emulation-checklist.md b/docs/nesdev-hardware-emulation-checklist.md index f2d5f757e..7fe8afc9e 100644 --- a/docs/nesdev-hardware-emulation-checklist.md +++ b/docs/nesdev-hardware-emulation-checklist.md @@ -65,7 +65,7 @@ Primary source clusters: | Reset sequence | Suppress reset stack writes but decrement S by 3 and fetch $FFFC/$FFFD | Implemented; cold-boot SP regression tests exist | | Power-up register state | A/X/Y normally 0 on tested hardware; RAM is unreliable; reset preserves most state | Deterministic test mode (default) + seeded randomized developer mode (`Nes::from_rom_with_power_on_seed`) both landed v1.5.0 (T-72-002) | | Unofficial opcodes | Implement all NMOS 6502 unofficial opcodes including unstable store family | Implemented; **SH\* pass 6/6 in AccuracyCoin under R1** — the deeper internal/external data-bus split is coarse but no test demands more | -| Interrupt polling | NMI edge-sensitive, IRQ level-sensitive, BRK/IRQ/NMI vector hijacking | **Closed under the R1 master clock:** `cpu_interrupts_v2` 5/5 strict; the only residual is `mmc3_test_2/4` #3 (ADR-0002 axis, deferred) | +| Interrupt polling | NMI edge-sensitive, IRQ level-sensitive, BRK/IRQ/NMI vector hijacking | **Closed under the R1 master clock:** `cpu_interrupts_v2` 5/5 strict. The last residual, `mmc3_test_2/4` #3, closed in v3.0.0 (`T-MMC3-BG-A12`; `mmc3_test_2_4_scanline_timing_strict`) | | Dummy reads/writes | Preserve all documented dummy bus accesses and RMW double writes | Implemented for major suites; internal-vs-external bus modeling remains a v1.x TODO | | DMA halt eligibility | DMA can halt only on CPU read cycles | Implemented; continue to guard with DMC/OAM DMA tests | @@ -79,7 +79,7 @@ Primary source clusters: | Loopy v/t/x/w | Keep scroll latch behavior and render-time horizontal/vertical reload timing | Implemented; guarded by PPU tests and visual baselines | | Background fetch pipeline | Preserve all visible, prefetch, and extra nametable fetches | Implemented; Mesen2 trace tooling documents exact pipeline | | Sprite evaluation | Per-dot secondary-OAM clear, copy, overflow bug, and OAMADDR walk | Implemented; the sub-cycle flag-timing residuals **closed under R1** (AccuracyCoin sprite-eval 9/9) | -| Sprite 0 hit | Set during rendering with left-edge, dot, and pre-render restrictions | Implemented; residual stale-shifter cases tracked | +| Sprite 0 hit | Set during rendering with left-edge, dot, and pre-render restrictions | Implemented; the stale-shifter case is still open, as ACC-04 (v3.3.0 in the v3.1 → v4.0 line plan) | | NTSC odd-frame skip | Skip dot on odd frames only when rendering is enabled | Implemented and tested by `ppu_vbl_nmi` | | Region variants | PAL and Dendy timing must not be inferred from NTSC constants | **Region-exact under the R1 master clock:** 3:1 NTSC/Dendy, hardware-true **3.2:1 PAL**; `region_timing` 4/4 | | PPU variants | 2C03/2C04/2C05/Vs. palettes and behavior are separate from stock 2C02 | Out of scope — a separate platform initiative (Vs. System / PlayChoice-10); load-time diagnostics only | @@ -89,11 +89,11 @@ Primary source clusters: | Behavior | Required emulator treatment | Project status | |---|---|---| | Frame counter write delay | $4017 effects occur after 3 or 4 CPU clocks depending on APU-cycle phase | Implemented for test ROMs; keep in APU docs | -| 4-step IRQ | Frame IRQ is set only in 4-step mode when IRQ inhibit is clear; $4015 read clears old flag | Implemented with residual AccuracyCoin frame-IRQ cases | +| 4-step IRQ | Frame IRQ is set only in 4-step mode when IRQ inhibit is clear; $4015 read clears old flag | Implemented; no AccuracyCoin residual (146/146 at upstream `f5f41dc2`, v3.1.0) | | 5-step mode | No frame IRQ; mode write can clock quarter/half frame units | Implemented | | Nonlinear mixer | Use nonlinear pulse and TND paths before console filtering | Implemented | -| DMC load vs reload DMA | Model different scheduling phase, dummy cycle, and alignment cycle | Implemented; residual $4015/$4016 bracket cases remain | -| DMA register-read bugs | Repeated halted reads must affect $2007/$4015/$4016/$4017 side-effect registers | Implemented enough for blargg; AccuracyCoin residuals remain | +| DMC load vs reload DMA | Model different scheduling phase, dummy cycle, and alignment cycle | Implemented; no AccuracyCoin residual (146/146, v3.1.0) | +| DMA register-read bugs | Repeated halted reads must affect $2007/$4015/$4016/$4017 side-effect registers | Implemented; blargg's DMA suites pass and AccuracyCoin has no residual (146/146, v3.1.0) | | Controller strobe/read | $4016 low 3 bits latch OUT lines; $4016/$4017 reads clock device and return D0-D4 plus open bus | Standard pads + **Four Score** + **Arkanoid Vaus paddle + Zapper** via the opt-in per-port `InputDevice` overlay; microphone / other expansion devices remain deferred | | DMC controller conflict | Reads can lose or duplicate joypad bits during DMC DMA | **Resolved (v1.4.0).** Modelled in `Bus::dmc_dma_read` (the `$4016`/`$4017` DMC-fetch conflict clocks the controller shift register, ORing open-bus high bits with the controller read; PAL / non-`$4000`-page guarded). Verified by the strict blargg `dmc_dma_during_read4/dma_4016_read`, `sprdma_and_dmc_dma` (+`_512`), and the `read_joy3/count_errors`/`count_errors_fast` conflict-stress smokes (`count_errors` renders "Conflicts: 149/1000", all compensated) — all green | @@ -106,7 +106,7 @@ Primary source clusters: | Nametable layout naming | Prefer explicit CIRAM A10 behavior over ambiguous "horizontal/vertical mirroring" prose | Documented in `docs/mappers.md` and `docs/cartridge-format.md` | | Bus conflicts | For discrete boards, mapper sees CPU value AND PRG ROM byte at the written address | Implemented for supported conflict mappers | | MMC1 | Serial 5-write protocol, reset write, and consecutive-cycle write ignore behavior | Implemented | -| MMC3 | Filtered PPU A12 rising edge after sufficient low time; revision-sensitive IRQ behavior | Implemented with one scanline-timing residual | +| MMC3 | Filtered PPU A12 rising edge after sufficient low time; revision-sensitive IRQ behavior | Implemented; the scanline-timing residual closed in v3.0.0, and both revisions are selectable since v3.1.0 (`T-MMC3-NEC-OVERRIDE`) | | MMC5 | PRG/CHR modes, ExRAM, fill, split, multiplier, scanline IRQ, audio | Feature-complete incl. audio (v1.x); only the >8 KiB multi-chip PRG-RAM configs remain (long-tail policy, no fixture) | | Expansion audio | VRC6, Sunsoft 5B, Namco 163, MMC5, VRC7, FDS require mapper/APU integration | **All landed** via `mix_audio`→`tick_with_external`: VRC6 / 5B / N163 / MMC5 / VRC7 OPLL FM / **FDS 2C33 wavetable** | | FDS | BIOS, disk timing, IRQs, writable media, and FDS audio are a separate platform surface | **Supported:** parser + RAM adaptor (mapper 20) + user-supplied `disksys.rom` BIOS + read/write drive + timer/transfer IRQs + writable `.fds.sav` + 2C33 wavetable audio. Real-BIOS boot unverified in CI (BIOS non-distributable); device + audio unit-tested | @@ -117,11 +117,11 @@ Primary source clusters: |---|---|---| | CPU reset/power | `cpu_reset`, power-on unit tests, randomized RAM developer mode | Add missing external ROM fixture if license permits | | CPU instruction edges | `instr_test_v5`, `instr_misc`, `cpu_timing_test6`, dummy reads/writes | `instr_misc` (5) + `instr_timing` (2) + `cpu_reset` vendored + wired v1.5.0 (T-71-002/003) | -| CPU interrupts | `cpu_interrupts_v2`, IRQ trace fixture, mapper IRQ tests | C1 residual carried forward | +| CPU interrupts | `cpu_interrupts_v2`, IRQ trace fixture, mapper IRQ tests | Passing; the C1 residual (`mmc3_test_2/4` #3) closed in v3.0.0 | | PPU vblank/open bus | `ppu_vbl_nmi`, `ppu_open_bus`, PPU state traces | Passing; keep traces for future changes | -| Sprites/OAM | sprite hit/overflow, `oam_read`, `oam_stress`, AccuracyCoin | Residuals tracked under Cascade A | -| APU/DMA | `apu_test`, `apu_mixer`, `dmc_dma_during_read4`, `sprdma_and_dmc_dma`, `read_joy3`, `pal_apu_tests`, `240pee` (240p suite) | Passing; AccuracyCoin residuals remain. v1.4.0 added the `240pee` UxROM+BNROM render gates and confirmed the DMC-controller-conflict coverage | -| Mapper timing | `mmc3_test_2`, `mmc3_irq_tests`, Holy Mapperel, mapper-specific ROMs | MMC3 sub-test #3 residual; VRC24 test source unresolved | +| Sprites/OAM | sprite hit/overflow, `oam_read`, `oam_stress`, AccuracyCoin | Passing except the sprite-0 stale-shifter case (ACC-04, v3.3.0) | +| APU/DMA | `apu_test`, `apu_mixer`, `dmc_dma_during_read4`, `sprdma_and_dmc_dma`, `read_joy3`, `pal_apu_tests`, `240pee` (240p suite) | Passing; no AccuracyCoin residual (146/146, v3.1.0). v1.4.0 added the `240pee` UxROM+BNROM render gates and confirmed the DMC-controller-conflict coverage | +| Mapper timing | `mmc3_test_2`, `mmc3_irq_tests`, Holy Mapperel, mapper-specific ROMs | MMC3: all of `mmc3_test_2` strict (v3.0.0). VRC2/4: AWJ's `vrc24test` is still not in the corpus; coverage is `m22`'s CHR-bank walk (`tests/m22.rs`) and the local dumps in `tests/roms/external/` | | Input | Standard pads, DMC conflict, Four Score/Zapper/expansion devices | Standard pads only; expanded devices are v1.x | | Commercial canaries | User-supplied external snapshots, never committed ROM bytes | 60-ROM oracle exists | diff --git a/docs/netplay-webrtc.md b/docs/netplay-webrtc.md index 0048b747a..8a7218eee 100644 --- a/docs/netplay-webrtc.md +++ b/docs/netplay-webrtc.md @@ -173,7 +173,8 @@ cartridge board and every `HardwareOptions` field), and a difference fails with options (ADR 0044). From v3.0.0 (`PROTOCOL_VERSION` 6, magic `"RNE6"`, ADR 0045) the identity also carries the emulation epoch. Another epoch, or an older RustyNES's `Sync` (`"RNES"`, `"RNE5"`, decoded at exactly its own length -and magic), fails with `MeshError::EmulatorMismatch` and its counterparts, so +and magic, and since v3.1.0's protocol 7 / `"RNE7"` also v3.0.x's `"RNE6"`, +which names its epoch), fails with `MeshError::EmulatorMismatch` and its counterparts, so a mixed-version session is refused with a reason instead of timing out. **Only a v3.0.0-or-later peer can give that reason.** A v2.9.9 or older peer ignores the `"RNE6"` magic as a foreign datagram and still times out on its diff --git a/docs/performance.md b/docs/performance.md index acaa9ce91..35f980140 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -1212,6 +1212,25 @@ is named as the suspect, not established: one more step, `8f449691` → `33ea0572` alone, would settle it. **Found and localized; not fixed in v2.9.9.** Nothing here is claimed as a speed-up beyond NL-15. +**That step, measured (v3.0.1 cycle, recorded v3.1.0).** `8f449691` → +`33ea0572` alone, `scripts/perf/ab_check.sh`, two independent runs on a quiet +host, A/B/A order-bias drift at most 0.95%: + +| workload | run 1 | run 2 | +| --- | --- | --- | +| `nestest` | +1.5% | +1.8% | +| `palette` | +0.4% | +1.9% | +| `nestest_fast` | +0.5% | +1.1% | +| `palette_fast` | +1.4% | +0.5% | + +Slower on all four workloads in both runs, so the direction is established +and `33ea0572` is confirmed as a contributor. It is **not** the 4-5% the +bisection step showed: its size is 0.4-1.9%, and the runs disagree about which +workload pays most. The commit adds a module and accessors but nothing to the +frame loop, which leaves code layout or inlining as the working explanation, +untested. No change is adopted; the lead is closed as measured. Logs: +`salvaged/perf-v3.0.1-palette-ab/` (gitignored). + ### v2.9.8 — pacing coverage: 60 Hz, Fifo, and run-ahead (the configurations v2.9.3 left unmeasured) **Finding: presents are even in every configuration measured; run-ahead diff --git a/docs/ppu-2c02.md b/docs/ppu-2c02.md index 5f63610cb..94520ee20 100644 --- a/docs/ppu-2c02.md +++ b/docs/ppu-2c02.md @@ -127,9 +127,41 @@ proved by `crates/rustynes-test-harness/tests/extra_scanlines.rs` (`extra_scanlines_zero_is_byte_identical_to_stock`, plus an image-invariance proof on the first frame and a CPU-cycle-growth check). -This is **distinct from the CPU-multiplier overclock**, which needs the -fractional-master-clock timebase rewrite and is a v2.0 item (ADR 0002); only the -dot-resolution scanline *insertion* is in scope here. +This is **distinct from the CPU-multiplier overclock** (`Nes::set_cpu_overclock`, +v3.1.0), which shortens the CPU cycle on the master clock rather than adding +lines, and lives in the bus (`docs/scheduler.md`, "CPU-multiplier overclock"). +Until v3.1.0 this paragraph called it a v2.0 timebase item; the one-clock +timebase that made it possible shipped in v2.0.0. + +### Disable sprite limit (v3.1.0, `T-SPRITE-LIMIT`, optional, default-OFF) + +`Nes::set_sprite_limit_disabled(true)` draws the sprites a scanline drops past +the eighth. It is **render-only**: evaluation, secondary OAM, the overflow +flag, sprite-0 hit and all eight real sprite fetches, with their A12 edges, +are exactly stock, so the game cannot tell. Pinned by +`crates/rustynes-test-harness/tests/sprite_limit.rs`: with the option on, every +CPU cycle, work-RAM byte and audio sample matches stock frame by frame, and +blargg's five sprite-overflow ROMs pass. + +- **Which sprites.** After the eighth real fetch (slot 7, dot 316) of a visible + line, `fetch_extra_sprites` walks primary OAM from entry 0, aligned, skips the + first eight in range for the next line (the ones the hardware draws when + evaluation starts at OAMADDR 0) and fetches up to 56 more + (`MAX_EXTRA_SPRITES`). Only when evaluation found eight; never on the + pre-render line. A line whose evaluation starts misaligned can draw a + slightly different set: it is a display enhancement, not hardware. +- **How they are read.** Through `PpuBus::ppu_read_sprite`, with no + `observe_a12_addr`, and only when `PpuBus::chr_reads_are_pure` (the mapper's + answer, `docs/mappers.md`): on MMC2, MMC4, the J.Y. ASIC, Bandai 96 and + Nanjing 163 a CHR read changes the board, so the option draws eight there. +- **How they draw.** In `emit_pixel`, only where none of the eight hardware + sprites is opaque (a higher OAM index is a lower priority), with their own + palette and priority bit, never as sprite 0. +- **State.** The pending extras for the next line are PPU snapshot v13 + (`spr_extra_*`), because a snapshot can fall between the fetch and the line. + The switch is configuration: carried across a power cycle, and in movies and + the netplay `config_digest` through `HardwareOptions` (the picture differs, + so frame hashes do). ### Power-up and reset state @@ -279,10 +311,25 @@ Per `ref-docs/research-report.md` §Sprite evaluation: - **Cycles 1..=64** — clear secondary OAM to `$FF` (forced reads). - **Cycles 65..=256** — alternate odd (read primary OAM) / even (write secondary OAM). - Read Y from `OAM[n][m]` — `m` is normally 0, but `OAMADDR` seeds `n` and - `m` at dot 0 (`n = (OAMADDR >> 2) & $3F`, `m = OAMADDR & 3`), so a - misaligned `OAMADDR` starts the walk on a tile / attribute / X byte and - the y-test reads *that* byte. If in range for the next scanline, copy - bytes 1..=3. + `m` **at dot 65** (`n = (OAMADDR >> 2) & $3F`, `m = OAMADDR & 3`; nesdev + "PPU registers" -> OAMADDR: "the value of OAMADDR at this tick determines + the starting address"), so a misaligned `OAMADDR` starts the walk on a + tile / attribute / X byte and the y-test reads *that* byte. A `$2003` + write during the dots 1-64 clear therefore still moves the start. (Until + v3.1.0 the FSM seeded at dot 0 only; AccuracyCoin `f5f41dc2` writes + `$2003` at dots 28-29 of scanline 0 and exposed it.) + - If in range for the next scanline, copy **four bytes** from the walk + position, `OAMADDR` stepping by one each, whatever the alignment: a start + at `m = 3` copies the last byte of slot `n` and the first three of slot + `n + 1` (until v3.1.0 the FSM stopped after one byte there). + - The fourth byte copied is the X position, and it is range-tested like Y. + **X in range: `OAMADDR += 1` only**, so a misaligned walk stays misaligned. + **X out of range: `OAMADDR += 1`, then AND with `$FC`**, realigning. + Aligned, both give the next multiple of four, so only misaligned OAM sees + the difference (AccuracyCoin `Misaligned OAM behavior` tests 4-7; until + v3.1.0 the FSM always realigned, and the pre-`f5f41dc2` ROM recorded the + resulting failures as a pass because its fail path did not pop its return + address). - Not in range, secondary OAM **not** full: `OAMADDR += 4`, **then AND with `$FC`** — so `n` advances and `m` is *cleared*, realigning the walk after the first out-of-range sprite. When `n` overflows to 0, evaluation @@ -609,6 +656,15 @@ change core rendering tests. PPUMASK bit 0 (greyscale): output color ANDed with `$30`. Bits 7-5 (BGR emphasis) are applied through the 512-entry `rgba_lut`, per pixel during emission. +**PAL and Dendy swap bits 5 and 6 (v3.1.0, `T-PAL-EMPHASIS`).** NESdev "Colour emphasis": +bit 5 emphasises red on the NTSC 2C02 and **green** on the PAL 2C07 and the Dendy, bit 6 the +reverse, and bit 7 is blue on all three. `emit_pixel` therefore forms the emphasis index as the +PHYSICAL tint (bit 0 red, bit 1 green, bit 2 blue), exchanging bits 5 and 6 off NTSC, and +both framebuffers carry it, so the composite filters see the right tint as well. Until +v3.1.0 every PAL or Dendy game that set emphasis showed the wrong colour. Pinned by +`pal_and_dendy_swap_the_red_and_green_emphasis_bits`. The MiSTer core has no PAL mode, so +there is no RTL counterpart. + **Emphasis model (v2.9.8, `T-EMPHASIS-MODEL`).** The hardware has one attenuator shared by the three bits, armed during the phases of colours `$C`, `$4` and `$8` for bits 5, 6 and 7, so it runs 6, 10 or 12 of the 12 colour phases for one, two or three bits, and it never diff --git a/docs/scheduler.md b/docs/scheduler.md index 8945f39ec..e6c180b40 100644 --- a/docs/scheduler.md +++ b/docs/scheduler.md @@ -82,7 +82,9 @@ A small inner struct that tracks: Scheduling rules per `ref-docs/research-report.md` §DMA: -- DMA can only halt on a CPU read cycle. +- DMA can only halt on a CPU read cycle. A load DMA a write refuses enters + on the next read whichever half it is (four cycles after one refusing + write; `docs/apu-2a03.md`, v3.1.0). - DMC DMA gets precedence over OAM DMA. - OAM DMA: 1 halt + (0 or 1 alignment) + 256 read/write pairs = 513 or 514 cycles. - DMC DMA: 1 halt + 1 dummy + (0 or 1 alignment) + 1 read = 3 or 4 cycles. @@ -133,6 +135,39 @@ fractional or master-clock representation because its PPU:CPU ratio is 3.2. APU frame-counter tables also differ by region; do not scale NTSC cycle counts for PAL. +### CPU-multiplier overclock (v3.1.0, `T-CPU-OVERCLOCK`) + +`Nes::set_cpu_overclock(k)`, `k` in `1..=4` (`MAX_CPU_OVERCLOCK`), divides the +region's master-clock CPU divider exactly: NTSC 12 becomes 6, 4 or 3 per +cycle; PAL 16 and Dendy 15 alternate cycle lengths so that every `k` cycles +take exactly one stock cycle (PAL `x3`: 5, 5, 6; Dendy `x4`: 3, 4, 4, 4; see +below). The PPU's divider is untouched, so the CPU gets exactly `k` times the +cycles per frame on every region. The shortest cycle, 3, still leaves both the +read split `(0, 3)` and the write split `(2, 1)` valid. + +Everything that measures console time stays at the stock rate: the APU (and +with it the DMC), the mappers' `notify_cpu_cycle` IRQ counters, and the PPU's +open-bus decay and post-reset timers. Every `k` CPU cycles make exactly one +stock cycle: the bus counts `overclock_phase` through `0..k`, CPU cycle `i` of +the group lasts `((i+1)*div)/k - (i*div)/k` master clocks (the lengths sum to +the stock divider `div`), and a **stock step** (those devices advance once) +runs on the last cycle of each group. The cycle length changes only at a +cycle's end, because the CPU reads the divider once for each half of a cycle. +Until the v3.1.0 review the length was `div / k` rounded down, exact on NTSC +and not elsewhere (PAL `x3` ran 3.2x, Dendy `x4` 5x). The APU is handed its own counter (`apu_cycle`) +instead of the CPU's, so its put/get phase advances once per stock step. DMA +follows that phase, so a DMA takes about `k` times as many CPU cycles and the +same real time. + +At `k = 1` the branch is never taken: every cycle is a stock step and the APU +gets the CPU counter, so the output is byte-identical (the epoch fingerprint +gate's whole panel runs at `k = 1`). The debt and `apu_cycle` are in the BUS +save-state section (version 3), because run-ahead restores mid-run; the +multiplier is configuration, carried by `HardwareOptions` in movies and the +netplay `config_digest`. `run_frame`'s cycle budget scales by `k`. Not hardware +behaviour: no console runs its CPU faster than its APU. +Tests: `crates/rustynes-test-harness/tests/cpu_overclock.rs`. + ### Frame complete The PPU reports `frame_complete()` when it transitions from scanline 240 to 241 (i.e., the start of vertical blank). The frontend consumes the framebuffer at this point. diff --git a/docs/user-guide/compatibility.md b/docs/user-guide/compatibility.md index 4f4631b5f..6a22ef331 100644 --- a/docs/user-guide/compatibility.md +++ b/docs/user-guide/compatibility.md @@ -93,8 +93,9 @@ for how the region is determined. ## Accuracy RustyNES clears the headline accuracy bar: 100thCoin's **AccuracyCoin** suite -at **100.00% (144/144)** (the "ALE + Read" and "Hybrid Addresses" gaps closed in -v2.0.3, the last failing entry in v2.6.18), **nestest** with zero +at **100.00% (146/146)** on upstream `f5f41dc2` since v3.1.0 (the 144/144 +reported before it hid a masked `Misaligned OAM behavior` failure; the "ALE + +Read" and "Hybrid Addresses" gaps closed in v2.0.3), **nestest** with zero golden-log diff over 8,991 instructions, and the entire blargg `instr_test_v5`, `instr_misc`, `instr_timing`, `cpu_timing_test6`, `cpu_interrupts_v2`, `ppu_open_bus`, `ppu_vbl_nmi`, `apu_test`, diff --git a/ref-docs/2026-10-07-mister-core-contribution-requirements-update.md b/ref-docs/2026-10-07-mister-core-contribution-requirements-update.md new file mode 100644 index 000000000..61cf189c9 --- /dev/null +++ b/ref-docs/2026-10-07-mister-core-contribution-requirements-update.md @@ -0,0 +1,125 @@ +# MiSTer core contribution page: what changed after 2026-08-23 + +**Dated supplemental reference, 2026-10-07.** It supersedes the parts of +[`2026-08-23-mister-core-contribution-requirements.md`](2026-08-23-mister-core-contribution-requirements.md) +named below. That file is immutable and stays as written; this one records +what the page says now. (v3.1.0, SUB-1.) + +**Primary source:** the MiSTer-devel wiki page *Contributing a Core to MiSTer +FPGA* +(), +read on 2026-10-07 from the wiki's own git repository +(`https://github.com/MiSTer-devel/Wiki_MiSTer.wiki.git`, head `1227b217`, +2026-10-06). The page's latest revision is `317b50e` (2026-09-26). Reading the +repository rather than the rendered page gives the exact text and the date of +every edit. Passages below are quoted from it. + +## The revision history since the 2026-08-23 record + +The 2026-08-23 record was taken from revision `a6c9017` (2026-08-20). Six +revisions followed: + +| revision | date | size of the change | what it did | +| --- | --- | --- | --- | +| `6aaf88a` | 2026-09-19 | 61 lines removed | "Update guidelines": the ten-step page removed | +| `01c1da7` | 2026-09-20 | 84 lines added | the page as it now stands: guidelines, reviewer criteria, FAQ | +| `e634b2e` | 2026-09-20 | 1 line | wording | +| `242277d` | 2026-09-21 | 2 lines | wording | +| `1dfe906` | 2026-09-21 | 1 line | wording | +| `317b50e` | 2026-09-26 | 7 added, 4 removed | the `update_all` FAQ answer | + +**Correction to the plan's wording.** `to-dos/mister/IMPLEMENTATION_PLAN.md` +says the page "changed on 2026-09-26". The material rewrite is +`6aaf88a` + `01c1da7`, on 2026-09-19 and 2026-09-20. The 2026-09-26 revision +edits only the FAQ answer about `update_all`: MiSTer-devel goes from "among the +most restrictive paths" to "just one of the different paths", Coin-Op +Collection is added as a contact, and the drop-in database moves into its own +paragraph. + +## What the page now asks + +The page is scoped to one repository: "This document is to help folks get +their cores ready for submission to MiSTer-devel specifically. Other repos may +have other guidelines." + +**Core Submission Guidelines** (numbered on the page): + +1. "Your code needs to be published under a compatible open source license + like **GPLv3** or **MIT**". +2. The template repository structure: `sys/`, `rtl/`, `releases/`, and the + standard root files (`.qpf`, `.qsf`, `.srf`, `.sdc`, `.sv`, `files.qip`, + `clean.bat`, `.gitignore`). Unchanged. +3. "Your code should not rely on custom edits to framework files in the `sys` + folder. Repo admins should be able to maintain your core by dropping in a + fresh `sys` folder and building an updated binary." +4. The MiSTer development principles and coding guidelines. Unchanged. +5. At least one fully functional compiled release in `releases/` as + `_YYYYMMDD.rbf`. Unchanged. +6. Arcade cores only: MRA files. Unchanged, and not applicable. + +**Submission:** "Once your repo is ready, and your code is properly tested, +send a link to your repo to newcores@misterfpga.org. A MiSTer-devel member will +reach out once they have a chance to review the repo." + +**What Reviewers Are Looking For** (new). The page calls these "much more +nebulous than the guidelines above": + +- "Does the code follow the guidelines above?" A missed guideline goes on a + list of fixes; "It's the refusal to follow the guidelines that will + disqualify you." +- "Does the developer understand their code?" The page is explicit that + prompting an LLM to build a core is fine ("_There is nothing wrong with + this_"), and equally explicit that "if you're submitting to MiSTer-devel, + you should understand your code as it is core to the next bullet point." +- "Is the developer willing to maintain their code?" Fixing bugs, responding + to issues, adding features. Reviewers "will be less inclined to believe that + this is something you're interested in doing if you are too busy developing + your next core." +- "Does the developer collaborate well with others?" + +**FAQ** (new): a review "can take upwards of a month in some situations"; +MiSTer-devel is one route into `update_all` among several (Jotego, Coin-Op +Collection, theypsilon, or a drop-in downloader database); and "Is MiSTer-devel +Anti-AI? Not at all ... as long as a developer understands their code, how +they manifested that code is of little consequence." + +## What was removed, against the 2026-08-23 record + +| 2026-08-23 record | now | +| --- | --- | +| §1 "The core must demonstrate **preservation value** through accurate implementation of the original system" | **gone**. No accuracy or preservation criterion appears on the page | +| §2 "Fully AI generated code should meet a minimum reasonable bar for readability and include some evidence of quality and accuracy testing" | **gone**, replaced by "Does the developer understand their code?" | +| step 2 "Publish the project as a public GitHub repository" | the word "public" is gone; the reviewer still needs "a link to your repo" | +| §6 steps 3-5: accept the organisation invitation, **transfer the repository**, add it to the Cores list with a unique Home folder | **gone** from the page. What follows a review is "an email back with the decision and next steps" | +| "Your request will be reviewed within a few days" | "upwards of a month in some situations" | + +## What this means for RustyNES + +These are consequences for the submission case, recorded for the maintainer to +weigh. None of them is a decision. + +- **The case was built on a sentence that is no longer on the page.** The + 2026-08-23 record called the AI-code sentence "the single most important + sentence on the page for this project", because the co-simulation evidence + answers it directly. The evidence is unchanged and still worth presenting: + the guidelines ask for code that is "properly tested". It no longer answers + a stated criterion by name. +- **The new central question is about the maintainer, not the code.** + "Does the developer understand their code?" and "Is the developer willing + to maintain their code?" are asked of a person, and no test suite can + answer them. The sibling's long RTL comments (D14), its per-rung documents + and its oracle-vs-documentation ledger are evidence a reviewer can use to + check the answer, but they cannot give it. +- **The duplicate-core risk is now unstated rather than gone.** The page no + longer mentions preservation value or redundancy. `NES_MiSTer` still + exists. Whether reviewers weigh that is not written anywhere. +- **The repository must be reachable by the reviewer.** `RustyNES_MiSTer` is + private today, and the page asks for a link to the repository. Making it + public is the maintainer's decision and the last step before the email. +- **The repository transfer is no longer described.** Whatever "next steps" + follow a positive review, the page no longer says the repository moves. + The 2026-08-23 record's warning about that one-way action now refers to a + step the page does not describe. +- **Unchanged and already met:** the licence (GPL-3.0-or-later), the template + layout, an unmodified `sys/` (the sibling vendors it verbatim, checked by + `tb/check_sys.py`), and the release naming (`RustyNES_YYYYMMDD.rbf`). diff --git a/tests/roms/AccuracyCoin/README.md b/tests/roms/AccuracyCoin/README.md index 8d37b621d..ea382967d 100644 --- a/tests/roms/AccuracyCoin/README.md +++ b/tests/roms/AccuracyCoin/README.md @@ -8,7 +8,7 @@ diagnostic decoder needs at compile time. | File | Purpose | |------|---------| -| `SOURCE_CATALOG.tsv` | 149-row TSV mapping `(suite, name) -> result-byte address`, extracted from upstream `AccuracyCoin.asm`'s `Suite_*` blocks by `scripts/accuracycoin-build/extract_catalog.py`. `include_str!`'d by `rustynes_test_harness::accuracy_coin_catalog`. | +| `SOURCE_CATALOG.tsv` | 151-row TSV mapping `(suite, name) -> result-byte address`, extracted from upstream `AccuracyCoin.asm`'s `Suite_*` blocks by `scripts/accuracycoin-build/extract_catalog.py`. `include_str!`'d by `rustynes_test_harness::accuracy_coin_catalog`. | | `sub-tests/*.nes` | Custom-built sub-test ROMs that boot directly into one target test (bypass menu + full-battery loop). Built by `scripts/accuracycoin-build/build_sub_test_rom.py`. Used to unblock the Session-22 Mesen2 wall-time oracle blocker. Inherits upstream MIT license. See `docs/audit/session-23-custom-accuracycoin-sub-test-roms-2026-05-22.md`. | The runtime `.nes` ROM lives at [`../accuracycoin/AccuracyCoin.nes`](../accuracycoin/AccuracyCoin.nes) @@ -152,8 +152,9 @@ pass / fail breakdowns. ## Source `https://github.com/100thCoin/AccuracyCoin` (main branch; re-synced to -upstream commit `46199ae4` on 2026-09-19; previously `69c8860`, 2026-09-11, -and `71f57fb` in v2.0.1). The `46199ae4` re-sync left this catalog +upstream commit `f5f41dc2` on 2026-10-07 for v3.1.0; previously `46199ae4`, +2026-09-19, `69c8860`, 2026-09-11, and `71f57fb` in v2.0.1). The `46199ae4` +re-sync left this catalog **byte-identical** — re-running `extract_catalog.py` against the new `AccuracyCoin.asm` reproduces the committed TSV exactly, because the two upstream commits insert `INC 151 rows / 144 -> 146 +scored: two new `CPU Behavior 2` tests, `DMA Landing on Write` (`$0496`) and +`DMC Reload Timing` (`$0497`). It also changed the row grammar. To save ROM +space upstream replaced the unofficial-opcode suites' `table` rows with +`tblf1` / `tblf2`, which store a one-byte token for the words "indirect", +"zeropage", "absolute" and "immediate". `extract_catalog.py` (and +`derive_indices.py`, which shares its patterns) rebuild those names from +the ROM's own `PrintTextSpecialStrings`, so the eight `Unofficial +Immediates` rows now read `immediate` in lower case, as the ROM prints them +(`Immediate` before; the result addresses are unchanged). Before that fix +the extractor returned 85 of the 151 rows and exited 0, because it only +checked that the total was non-zero; it now aborts on any row-shaped line +it cannot read, and on any suite that yields none. + ## License MIT (same as the runtime ROM; full text in diff --git a/tests/roms/AccuracyCoin/SOURCE_CATALOG.tsv b/tests/roms/AccuracyCoin/SOURCE_CATALOG.tsv index 1a49da088..660ff72e5 100644 --- a/tests/roms/AccuracyCoin/SOURCE_CATALOG.tsv +++ b/tests/roms/AccuracyCoin/SOURCE_CATALOG.tsv @@ -71,14 +71,14 @@ Unofficial Instructions: SH* $9B SHS absolute,Y 0x0448 Unofficial Instructions: SH* $9C SHY absolute,X 0x0449 Unofficial Instructions: SH* $9E SHX absolute,Y 0x044A Unofficial Instructions: SH* $BB LAE absolute,Y 0x044B -Unofficial Immediates $0B ANC Immediate 0x0410 -Unofficial Immediates $2B ANC Immediate 0x0411 -Unofficial Immediates $4B ASR Immediate 0x0412 -Unofficial Immediates $6B ARR Immediate 0x0413 -Unofficial Immediates $8B ANE Immediate 0x0414 -Unofficial Immediates $AB LXA Immediate 0x0415 -Unofficial Immediates $CB AXS Immediate 0x0416 -Unofficial Immediates $EB SBC Immediate 0x0417 +Unofficial Immediates $0B ANC immediate 0x0410 +Unofficial Immediates $2B ANC immediate 0x0411 +Unofficial Immediates $4B ASR immediate 0x0412 +Unofficial Immediates $6B ARR immediate 0x0413 +Unofficial Immediates $8B ANE immediate 0x0414 +Unofficial Immediates $AB LXA immediate 0x0415 +Unofficial Immediates $CB AXS immediate 0x0416 +Unofficial Immediates $EB SBC immediate 0x0417 CPU Interrupts Interrupt flag latency 0x0461 CPU Interrupts NMI Overlap BRK 0x0462 CPU Interrupts NMI Overlap IRQ 0x0463 @@ -106,6 +106,8 @@ CPU Behavior 2 Implied Dummy Reads 0x046D CPU Behavior 2 Branch Dummy Reads 0x048B CPU Behavior 2 JSR Edge Cases 0x047C CPU Behavior 2 Internal Data Bus 0x0490 +CPU Behavior 2 DMA Landing on Write 0x0496 +CPU Behavior 2 DMC Reload Timing 0x0497 Power On State PPU Reset Flag 0x03FF Power On State CPU RAM 0x03FF Power On State CPU Registers 0x03FF diff --git a/tests/roms/AccuracyCoin/mirror/AccuracyCoin-mirror.nes b/tests/roms/AccuracyCoin/mirror/AccuracyCoin-mirror.nes index 038fa10aa1a675060bc7495fa5d0518c239aacd2..b1f0a6ed4ba18f8cf8e9e2f237aa71071d05b44d 100644 GIT binary patch delta 19521 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, commit `46199ae4` | +| Upstream | , commit `f5f41dc2` (rebuilt at v3.1.0; `46199ae4` before) | | Upstream licence | MIT (Copyright (c) 2025 Chris Siebert) — the text is at `../../accuracycoin/LICENSE`, and the corpus-wide index is `../../LICENSES.md` | -| Base ROM md5 | `2f9d83104969a5984caf21a77d6746bd` (identical to `tests/roms/accuracycoin/AccuracyCoin.nes`) | -| This ROM md5 | `8162ca0ae099220401e76719e88762fc` | +| Base ROM md5 | `a3635c87ffb1754f58923d070548898b` (identical to `tests/roms/accuracycoin/AccuracyCoin.nes`) | +| This ROM md5 | `bbd842dfa221cdc5a3ea44f8dfcc37b0` | | Built by | `scripts/accuracycoin-build/build_mirror_rom.py` | | Assembler | upstream's own `nesasm.exe` under `wine`, so the output is the author's toolchain rather than an equivalent one | @@ -32,11 +32,11 @@ RustyNES. ```bash git clone https://github.com/100thCoin/AccuracyCoin /tmp/ac-src -git -C /tmp/ac-src checkout 46199ae4 +git -C /tmp/ac-src checkout f5f41dc2 python3 scripts/accuracycoin-build/build_mirror_rom.py /tmp/ac-src \ --out tests/roms/AccuracyCoin/mirror/AccuracyCoin-mirror.nes \ - --expect-upstream-md5 2f9d83104969a5984caf21a77d6746bd \ + --expect-upstream-md5 a3635c87ffb1754f58923d070548898b \ --wine /usr/bin/wine ``` diff --git a/tests/roms/AccuracyCoin/sub-tests/BUILD-PROVENANCE.tsv b/tests/roms/AccuracyCoin/sub-tests/BUILD-PROVENANCE.tsv index e32e93964..3f099ff02 100644 --- a/tests/roms/AccuracyCoin/sub-tests/BUILD-PROVENANCE.tsv +++ b/tests/roms/AccuracyCoin/sub-tests/BUILD-PROVENANCE.tsv @@ -64,6 +64,22 @@ # does not stand alone or the entry needs the battery's preceding state, and # both are open questions rather than DUT defects. # +# v3.1.0 (the f5f41dc2 re-sync): TWO ROMS FROM THE NEW SOURCE. +# * `sprite-eval-misaligned-oam.nes` is REBUILT from f5f41dc2 (suite 18, +# test 6; it was 17/5 in the legacy build). 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 +# was the ROM `RustyNES_MiSTer`'s ladder gated that entry by. Settles at +# frame 175 (the legacy build: 211). +# * `dmc-reload-timing.nes` is NEW (suite 14, test 6, `$0497`), for the test +# upstream added in f5f41dc2. It reaches its verdict in 15 frames against +# the battery's 4500, and it is what located the MiSTer core's DMC reload +# defect to one bus cycle (sibling ledger 3.52). +# Both built with `build_sub_test_rom.py` from a clone at f5f41dc2, under +# upstream's own `nesasm.exe` through wine; rows measured by +# `subtest_identify`. +# # RE-BLESSED IN v2.9.5: `ppu-misc-sprites-on-scanline-0` records # `PassWithCode(1)`, the composite-2C02 outcome, where it recorded # `PassWithCode(2)`, "RGB PPU", until then. The core now draws the odd-frame @@ -104,6 +120,14 @@ # offset, 0x10B5, 0x02 -> 0x03 (the `LDX #test` immediate; `LDY #suite` sits at # 0x10AB). # +# v3.1.0: `sprite-eval-misaligned-oam` is REBUILT from upstream `f5f41dc2`, +# whose fail path exits instead of falling through to a pass. The rebuilt ROM +# settles on frame 175 with 18/6 (see its row). The legacy build it replaces +# settled on frame 211 after the misaligned-evaluation fixes (212 before): +# its tests 3, 6 and 7 then passed on their own path instead of failing into +# the unpopped fail path, which ran a different number of cycles. That legacy +# figure describes a ROM no longer in this directory. +# # REGENERATE WITH THE GENERATOR, AND THE SCHEMA LINE BELOW COMES FROM IT: # # cargo run -p rustynes-test-harness --release --features test-roms \ @@ -124,6 +148,7 @@ controller-strobing 13 7 0x045F Controller Strobing Pass 29 legacy-unrecorded cpu-open-bus 0 6 0x0407 Dummy write cycles Pass 31 legacy-unrecorded dma-open-bus 12 0 0x046C DMA + Open Bus Pass 26 legacy-unrecorded dmc-bus-conflicts 12 6 0x046B DMC DMA Bus Conflicts Pass 29 legacy-unrecorded +dmc-reload-timing 14 6 0x0497 DMC Reload Timing Pass 15 f5f41dc2 fc-4step 13 3 0x0468 Frame Counter 4-step Pass 33 legacy-unrecorded frame-counter-irq 13 2 0x0467 Frame Counter IRQ Pass 66 legacy-unrecorded iflag-latency 11 0 0x0461 Interrupt flag latency Pass 29 legacy-unrecorded @@ -147,6 +172,6 @@ sh-shx-9E 9 4 0x044A $9E SHX absolute,Y Pass 27 legacy-unrecorded sh-shy-9C 9 3 0x0449 $9C SHY absolute,X Pass 27 legacy-unrecorded sprite-eval-2002-flag-timing 17 2 0x048D $2002 flag timing Pass 73 legacy-unrecorded sprite-eval-arbitrary-sprite-zero 17 4 0x0458 Arbitrary Sprite zero Fail(1) 29 legacy-unrecorded -sprite-eval-misaligned-oam 17 5 0x045A Misaligned OAM behavior Pass 212 legacy-unrecorded +sprite-eval-misaligned-oam 18 6 0x045A Misaligned OAM behavior Pass 175 f5f41dc2 sprite-eval-oam-corruption 17 7 0x047B OAM Corruption Pass 111 legacy-unrecorded sprite-zero-hit-behavior 17 1 0x0457 Sprite 0 Hit behavior Fail(1) 29 legacy-unrecorded diff --git a/tests/roms/AccuracyCoin/sub-tests/dmc-reload-timing.nes b/tests/roms/AccuracyCoin/sub-tests/dmc-reload-timing.nes new file mode 100644 index 0000000000000000000000000000000000000000..ef79c9da731a157dc32414eef0e5cd11819fa68c GIT binary patch literal 40976 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 PcI+U2Nx4Z$lJvg-klGtc literal 0 HcmV?d00001 diff --git a/tests/roms/AccuracyCoin/sub-tests/sprite-eval-misaligned-oam.nes b/tests/roms/AccuracyCoin/sub-tests/sprite-eval-misaligned-oam.nes index fd87a1ff88f17114d1ebba70ff8f6c0fd26f887d..54242be3c002a9b3f6decbb3d62b2c67a0189002 100644 GIT binary patch literal 40976 zcmeHw33wD$_HT7}btgoY29gj_NdaMr0~iq%5v2nOM5oox#-{(V14&3mwn@SwI?yPM z0ck*yk-#7!(wGVc#nF*vz#sw%>P*QvBZ_DcjWGyz2*?`h{m#9&IvoUN=KtRNzW2WG zUy!bXUi{y@rpkb&W_%#hFZrfhmK0c){?4*} zX`#h$@)cQTA1eE4wd(M7aj1QKHbs4pUgLdXN||11R`B0`u|9B)Gl)|*Hz_qus?F!N zv`|-uDfkcmR@DTA@?AiQJFQGe4=MSD%G7B^%B+I?qO1v%b9AfhT%VTskn&J| zLBX_w{Hc?4i)<-%jZnrEWKGX1m^Nc-w*HVj?yZcx93?4dV*Zq@$;!ydS(Ef4`OAM0 za5(qQki-6&G-Jw?CzXPntZZfClM^TB6zVqFyPiWS!W;t}Cbxg}k?A>8mEkiA^>BGu zY2xI`%EOPil>DiMMFle^7UfTyTB!eCKKQMkn4MjaQ&4qrj&PVjPggX?4z5@Nm>TTq# z^0LuRBo}4xs2S7hFf6TN9zwV5V~7SW2M*a-|wKn5mpNwo$)L-U2VvFLCTCJwbj)jl0Ld zBD&kX}Uh5$6`_sDm_OYGo1r4NyZRKz6($;avU z%J&0ckR$w1c{l)+pcDY{-zSd*z~BIi`{f6(BhUlsBlNgriUD6F&@kCs5tmFc0MMvA z;YY|T0U&`W00c^sO8_u9fFfC5dmVw&Q*PDc z?x7gcQ;_Ax=_BPs6>;}acmRmuDEXF6ardxHosv#~6gj;z?w%2hV%#aj@POR;jktS~ z8DK0?JSfk8BW^eaaVD~I@N47C?<-;BG9dDGzSEYEo}ZZIcKZqg&=jc>*!lGiylu}P1T8{do@!LVb~ zoAhY;t}SuNLKJTSE=r=VcQ^rAvN_^ITg`A|^B=1iUVq@E?8+4Jy2 z4=9h!%qf_hHC-8=_G^8DT(LJ4_CQ*yK2d&cpPmSoX*rYf3&FKe$(ow2Bt4XzV5W%vt^O< ztSLDLi9(4tNgh?x8ak*y(r~(-Cx7x`Ys8%#kuSgZQBuz2qAca1Y1uQVJ)AaG8IhMY zb!yIJ{kQVQk3&tNB%;Nh$!PS);zEKuO2P1nN~hF2_}cz(-7;Ht`99 z&ynr5EUOs36lEZ?O0XB8WaLjFGPk^{ilt5tY|gqFWY9u=irjE;ctO_GiFvKS&&yA~ z_+WaPlAMi>z=*6ul!K?`Dc`U>I2A2wBHj_wEst!VtdCBWu3OsiZzInFvStGd zz_2|xxUwea6rl^lsqfjqxEV>Ik4Q;VuGVSu%C8^i(ekno(tDHT%=N8M!4mPXJg|&o zpwI9bxw*(cGv!VH3=u&|osnPgM4>)co><2u89jV**3>5q?S*dww3#}Utm2Z3-V(qZ z^b=F%>bI!e3Onm*viVQs7m1fHA7tM28Plh;KHapbdWL-O+eDI8q=YcalovB-c77q{ z>g*P@K3cAOTTh?PdXvdBvnJ2TqO55^rlA(eojh%pGQsHcj*}bT732v@3QAlMGe#cu z4zC+ng;c~dnon-GB7H`7cFxqvPr~?x`Ezm<9$sH0zxcvKkZ5xLq^Xd}D9)|$yS;lwfMD$7<>$5%hY{0Q{YW}IB`-e8QXOF?4$zAby)dsZ z%QN!d>qUQ9A;|}|DD$Yia{B`hk5J-JSq;)X^11KRV38c^(6emWG6+~G|NS@xzMFT{ zXWa%@Q0j~25ifGaD7WZ!D^%&|OXP=F@(`iWrLw&Qz4MtUdq|AoSrea_#6}BBh*c7c ziVE^4po?CpB!XE!4GHGUh4m;nlQD7_rKIQO=eFoss6Q*e{sVy_IO}ig&&hB9nCMJK z&CV+D7v!-=Ql6Ypke{uHgaF4Egb2z~ zs4tT*l#DExHU}dV*k~Y{v55saQ*x#j=__ROSFKGt(2zu5DPQ2@*4}Jg>rCxx!krCJvl%5xr z((}#{%heUc>;b{-O=6aK1+xS&OAKMA2E1;Jg0ncs6>i3jSgj*gL;fRHOsI8%%Z5aG z%5J|BQJ!f<`bx4V(`qAE2grSkZqX&%c=s)4axDP4V)cOJ8Kt}Ic5)>Sa=qb6p5(IY z3;`3P5jlTI-^`h??s~YC*m-c40?8vh&BX^kr(q%UT>GCqQqg#2hyv(%F zUSrZy>+EF`sPD;GTi1=$ge zj~IT#$g0(i0M5as*I?N|Jda$T=N(t_Y~yAblufRo^~Cp5Nm1^;6!;6mwx+nbHHG$T#@)`j zyZTje?vTQ&;6DZK+T|jmFPK9kg65l|>`(i^w zgI_9>)EJijU#?gcU%PU$U#hQ_wc};dJCl#BT%m=N@3eEu3I-@yq20;=h=1*hvha6Y z2;w@Ro%N?{9sL;rxyGTj@n_b!HIbsVwH~R~9vIjf;E8GB0EqvV!Jn{>ZR??NuI>AM7e6CnUUGz$&R~o(2>6JmROnQx`*BE+@rPnxmji=Y6^m>e5 z|3$Cg(Ccw}xr_a-63OaMEs3)F(@Ji&`qN8>S^XI$8CHL0Nxs!Tx@4}^Kc-}b)jzhR z-0B}!veW7xUvkjuf3)O#tN*c*OIH7Xm2|ZEe^b)i=6}58UYp-t;<8m&S49WfD5qEW zr3Po*OZb3_9ZlS|lZ(iGdYGH6OBJrGAO~&s%=|*?laErUPiJ&1y6bLK4Drgjmq0na zXGR{T`)GIs`rn~zhjthN5#}~F{wlznq}x9vFa}kQNwqGuC_Y)i8}Zec0)OVYsZ?LJ zRx%f_s@Kjbu`el~ubl9QKyRv}b`Jjo5cRc+cBWJ!n(83QyDzhXwoqBn9kqjMLj#oI zl}e>8QuPZ`@jl5f)#%!(eI~!DM%EfC^)R1gt()dnhMguFue%zrtZF6y^wv^on`8xX z8|BTFs>3VANn6}r7htX%wzwbA?~$z9Z2|ccw-!(EYVEtatZkCbK-#oU4B3_;t1`7? zC^4$!_DGUG&!n83Yjzy@y~X{})3RsBQ`XXoIku{jC&Oxs=LP3l|JxrIFP=Ty)b!I& z$B%#i{jpT@9o&}?z?~b(;IJW+Vt9MufDo&-I_Ho zzr13_vSrUd|J-xWK09yT+__m8++4DUAlDc+@Zq_H-v>*EV|{Q z<*cR2@{{Gb<$KF9%h#5}md`AIxBSiWp=G~ikL5kf4$HfiKUvP$MPSh!@R3K{~?cGkfV0NqwDkLnNavoo}4?^Y<4(~9Qpn4Ef%-?qmQ0` zT9!SY9Xp*k;_2n-;knI| z;JFj-FptBN>>2KP#N+a0!hPK1_T+dbc&2%#duG6$=ka*vd!E62fv4EB#IxM?`ufALg#KJZj~KJsXuPd$e`UxI#~XRhb>o~KECrW55ndMNkN zLphKh%7ye$PNau&BR!NO=>e->J}6%(!RJv;qjp%&D}8jo_8kJBuc;Oj^on z%QudqX1S+8MN@!2MT%)(657=8MTrR|(uN zCtruwR$rURs1HqcK3!62vDQ=t?3HFXTEEH+8)`6=VTdNJiZ=y1El#kZ6gxjDL2*6O z@h7cKeRXYx_APH{QJaeVW|7Sh6>+Ygf0D|mxv(C4aEDGa@J7S&yRiu_4(&x zK=6#$>U?xAN~xP*cQy~p%gggi-uZr$_Zh$0>#vnGm%*}P zHROzRR7w;x^-7t*w=*G;hqyqUn>U&u=9?y_8&k~31TSHNvjhsO{zV!8AHe^l!2hJc z|D?e6Qb0AkF@a*TqW$t%D^2Y%XpXD&+)`uKZb21g@tJQ?W#5r@)bv=ih{_{YbTX;F z@!KOJ+L+yyk}Gh!GGk=mr#gG3DYGV1tE-H0U_A46rT%!$< z5$%Yt1^Xt<@UV{tH2})8E}($FR{Nr!DrlN28rmfF3-C4^%`fmNsIv1IQ94A4-l>zE zDI`^?q<-ujjj5ho{|hPp7gJD~zktg8MYS|Vbwk3uJnxHn-WL|vngTJ9Q=u4p?F&7O9RMqTtsl&YKn5Ic9vtx9-^OTZMLDJDTHdwrZ?+NrB7*>gChGhb^mTkXC z@Gz|D(prwKMY@|5)1@sg(Av@Fl};dE8io8o;9g+3*97hbhAU@WO_$cY>@^~S`HXA_ z^n|=LKXH_1e@xi1VK#>4b#-=s05b%OD^1GYfcBZc5luG@+4nYT2ata(oKV(w!mcv+ z=-QhQ2JM?wSw(|;Nh>A4g3byX>?_)GmPia4M{^d0jshAkCI_Ol!DuA0*8}FJ%mOH} ziPgGeq#p?@5wL7F5)3nez+lwF_J*|RvHj=%7u5csaw`|g)B*H?SwNREuuRMzeTE21 z&!a!of4sZ@qmT8EZtVYux&8Zf>_4E#fX93T{>wk0OJKmucMa&jYQSxe=tO#nWawNCJyG?97 zVtut6=3&6!r`5wV_M)>xxlrl0AX9h4m^rNn=KOQ`QV#aJ6k*Ru zPIh-4T*dG^t;fUDXkFkMG)xp$mNzt}^+;6o%IOxG0o~A8&|@rozZ1R}dyIpxfcapA z4UHRnU^05*6N+nEKCSElStv+9=;11uHW9P&(+b$qX(4b$49Al~_f=UjbF|y)+hbL$ zeeYW_@muY!p(;YDvQ|l&(~nU;qpAiKNPkHZ4-$;%7y8#$S?g=tYiB^e?-z_LL=~QQ zK)Zx0c>7X4tW0XCuHLMNt#)IEX+tl}{ch~DdSkP{cZ$DfUw~5BCap_(UVUNn_JtQ0 z(2VfLW_Jprdda`mgQ)szXYzb2tZt(4n&V@=miR6;U9|Rob9_~eN3;1Y@!h-@za^q# zXV^xsv@?7o`mXxM=E;&YYUAM2Q5#b?z6f`4DZM#FXMX-3Tsm#zJMbT7Z=$&GwHsd> zTuSfC9+K}Rt1`&%Uf8TA4F0YeK7V)b?S93Z;O|i?z0h~VWx}cnKrFb-QG>s`>`Rc| zH1E4Tl*F&ZcWC1RnYXVO^@BIw+uh$|!DaAJ3@!_sIgz(7kLe0J5f`FitcdC|@e6Wm zk;ktvL47N&t>PF%dic8&Sm9B;Y5D6fX-E-4LlFp?prqikF>XUIe^0ma_-lS$`P2`W zjH7t0ZcNVf-FvIn9aX2k_gC7Db@q+T-}ciw*4Z~){-D3s2JlOJ2WY%?LA5LB8wLFj zg8rzW|6I_20{7zH+q4?w0yYIyhDo56N_Bm0nD#UK?#sV1A*AHK=I`jWD^I?LwTfTR z+qIuLjWT{2BU67t)&s1pg6*&HEgM15nAw$Q8NF)JqSY|1liERRuXffV)GnG$y-72x zv9xCJ#cPt)VaiSpldPWk*~tq4m}k|c?yn@xvtGX3{gu4{=vEr*Te)LHJ*D;p%RXRv z8(7`}mUn~YyZ~%cE9h`BSID zj#`&1Pn}98DW3(K=fGwe*t~488Q(N4d6ZTQOrT?6`8`mOD{Et7XxUzVblQ5-w0^Q;WuSrE1C1CrA2 z9atu*_(0J)`W_cI4D$0))lA zfUe7#l0?qcSLmoXvlTjtFVJDZ?D~l?@IOj~rq{2Z2v_HJy+p9fED`KfPLK$e&Jr@N znH-)75fMm)2t1xq|E9)R&O@$2>s=5kCXXZ<$B>e5g_jtcP-YV(iSN1gLs@wTk)1W5r6phSgT_9d#hqU!1suxs#sa7ilubJBsS(P z?0kmUqQh$}8)Ma?tlv_bf+Y%@LTeUR{BfiSNKA;hm5;VyB3uE7FwC^nNP(m}Y?%n$ zkGCa(#?}~0?G7M9<-!&*AnpMu4`tQa>5qm68jM;nwhj`r5A;_tED4$Gk#Gvb?KLSn z_Vm!;uS{B;qI`TAliIGL&1MCQ)Qclj=~l0_ikt{5=<_Q4(n=TgTR+7ry6ed9x3qTk zwHD1ppr44%?f~L3DUexKs+|X{EzJ5v!2yg6G#|CWN2!wsy0yoQ=#Ep*4V$ZZ_+Z zNw-)mT<%bIA5q$`5^Tb>?~z@Mm=1@l9wbRa1E!87Wz|YI8TBYL@z-x_Z@=R{0;iII zC%C0yXepF8S3gQetcZaKzh$AjzjI~&V#$fnI+SItFt)ZRmuUWi&MD1kk}gT%$P#Yn zfIlmJ-%&mel$n=CI+QOjA{DUeR1bqS20#!3)cy`2a?x&V;R%GocW`)PE8s>Bd?!GW z#Yy3z0caTxjD|MF3XzyRms>{{c$iZ$oZcFz4Kz4Ng2OujwJWW;F>1=}q4CLecCCYQ z`2t1S9N=XS-|Ec>5lZ)ENQ|ZsM1U6Fn853M@-&>$bdo0^QFXk;6K$;#{HIWy(#X?P z%QKjmX;Be|n9*dek~)XPqa_<0V4lm!{VHWqB0w@?7g89DM~l4@+(4VRU^N?bM8<(l zonO7n%}P-jMr|lFk@2BOF}HsUj_MbOb`;Tb0@QMfpehR&A1qe3R;+rsK)u6!7RAsn z@UW2(8fxPfKJ%4Vo1|k>cv*Mx5yEHw)%XZ4uCNzU#G`aO!Ez=vII3@jUemwWN}4Ny zgKW}NVwz?6oY68ewtie{!(X*53nK^5oD>D$=mn0W1s^5|AfsbgZ- zzP3qG=4^!wz^~%?UtJ6T-oFU{J;&dAt?>5kEdD;)8B7FrBN@!Ukl_wa)3=2Nizh8| zqTn=BW2(c9($)Sv*fAM`YUK$<7~h~ri0hGgj^=u;148{1Aicq-1q|tC`gykty2mfZ1iOZ4-4q1fs$J=Z3^FZJpede zy~phwx1DDg${JK^y|jeG>RWUv=ul|sC9EAR!S*d#!4?Zyq3mQ7ORQ;7b{vnrfvVqU z5_uQ~sARhX%dmIw75W^ntqzte>>gyy2!2ekWe#fv=e67T7>QoHde6x6kkL2JfCFqnQT6@O;vpsn@_f3_TFm4@>G-qi-Q(WU^7`5+?Ia*IN!rMIxbYWj61Ac9gv8se}GQ<$R)}#Fi z%%ZuTJt?qu3ww8Afz4V6gOv1iKK!+zsNsfKyU4a{d!J%EQU&sJJL+V1^^LP1w}}UnA(qtSR{ur%hpj+pw)z#)c#t z_UAS`OpBFnmsAJVe6W*gad~lqbjAP05#=}?fRSH`WJQ1}vazP|p0(5P-i;wjbVQMiAvbJ+JYLu0lrp_kcP^}ApAx$EM?X_slJfIsc@;|}D@n|1m$TqtK2#-ZCoy<* zc~iY<-t-`8Ra=S^>RR?FbE~S_Yc=(?H)!Zg0Mwp=I%vr>*x;ZT2I{B{q*UUdZVY7C zVq2*C5(pwxb;2e(5T0a}I2+9l5Y!kfbr1~hW1vpjS1mk8Fi=PB!+H$osBZ#5VO3Q} ztv>@W>5>JfuJyHcMgst&L6iWrM48D5J8Os%GJ_caAndHwiU?J!H_Ph=;!z>Ve%e~W zwVNm;NuwbS1qJ|tJ86jwfc13Ld+bIBdcgydC-#s)YnF9N3fv- zts3nD_$-|4MQLTBWC9?i1#qO!o&rc;K!`#{3%~$*K>&3tkACKS=6wON(Dr>ml$QvS z#~9nRmB?uCr>L8VnOjiWv_J5b95h~5d0xgFcb(uC;;Tmk_E);a~o zYD}B7iGLu!v`nq4sr|v)D>=uz(7$o-cedUmbNAIn-I+OKLbHBQ5 z>YkW2fBo*G9dA$l-Ih-pUxO+qJ_?ZdazWd@_@PxP0}*j?`YTER|9v|-KrBwg8hgl zo4N;$pWEr)t9-LZm6qDM`IWc#sN=EiLDI+KWP=Os4U7k110eNd>+1nBYX?F=vk7F< z-mSAEqwJJS@p51jQhtLGg9wYDv;CPORnqe$|%|=?BWiF| z3TRGp_Gw+{a6%KjiuK6CeOf&8qBiFQxvdGF+egh0=y~(AA^6%;kElD zhdR$^qF%4r!8##7?nfce&Y+0I_$<^QpzLBQw5$W>Bxny6kf{AxDPR=Hh+Ra}p}p4% zK?Q}kDPXZdFhoGaYr=~-<+A_)P+UPMHs*!!PaqjZBurog1VhvT*CR4QP%L6+v=UJ* zrWQ_7j$VxQycEVF50PW@AH`8k^k-HbiLIfXij;W|!LSjRBh7tfm#s8>Xhi$AUE8;{ z$8<`FbVpakq;$34taM{33ge>g1rMAs1eXByj!1v*)lk8(7=#xsp<2~~-6^!;SQ3IN zQ^Admd)Wa4))KZrY23)*fs__%77Gb5C6&(QdZ4V%IZRd_G1 z71gsu6%fbgj_Qa!ln29VRR;enunSn&vTI;jSYkfu8U!pj5sbVBftUCbL9c5NEEEJ+ z(^ANa0?!1A$G+7oxB$gOz|0`cF|ZzPODGmOP+7l3v1q}NtuMAMhbdMkOh zZw|^sO}in_H3+#pR^)D`>b11>ONOQ-e*l$MkZ~|YT4PA=WpHwq)tH1tPG^Wo7?$PBX?8+`m5J>YbTJ`x zuQ579=c^ER2_fFdh(F`}SY?1-o*knh2$x)h?*yzB-xz2I@5mzOKQzo)4sojGeG59CWb3?+@Hi;{eDVu=w3>%I1y-92k?lgDKH4Ra%KT)6;IH|EDx#@sERaF@(fU z3*N_%l5E!oRkAtd{zgV%6UWrD)i|`6VBVm1iFdAz)xKCn8sz?rSW&`{q zH2nSqLqcfSymQ#6vz2EK27XPugzIZ>(SD?S`e}-KbBa1FMb%RfT+s$PQ@iNXueGnUm!@uGpRBpVgrna( zwk%x5t`DHQ)!DrgSV<;qCLsEDpf~+luk{-fIO?Z3@{X6uS|&}#9DJ=rYuo78yK$z; zH{XUG9v&O|9lBMu5+z2_Hs#`{Ri=PkJz2NbngT|=5ZH=ZVl3v>#+8Myr(r$1B?vm! zgizy5Wr6j1fMLb~R63|6VrD9n){{=HxJ<`!9;-LC)V_td+=UQ+pWTK{s2r>S?5Cv+ zXqIVJGefvxr*{aqD|XR%O*m_+dDDv3bMOz* zn23nUkFFW5b*i)D2tjxt;t)BE=#bIr56|GQyq-o>^eJ5VCDJK&_t~T%Q-Y%0ajtug~bF)n?~oXf6m5u8WC^IgW%|A`1tuVHw1}tAf4IN`zcJk zFK}Vv_6IUdkO_h}URH}hxm*UZO?Mva7f0*xw6Giu+fsiOeL?nd_5pSZg z7wbMj-ky3;T66)J08yW76(kM`AhX9Jvt44CUAZzgBS~0qraezw(vwn^#et+Hp1Bdwmi&otJXTWJeA>C zg$(5L8Lzr8w8w~j6Ddp+e?Nbr{kjE!Nif@CZ3qLf%)|0)aPe$;&)^k|>{iB>`>C+) z7r7iuU%fu(5k()?np*fGqTYwKZ(0C|=&r-sC;xdw*d)ys=vrpT43ioV+6Nf=aMK$Q zIhX}F1bzd>Tuv>^SDv-uXJUT=_Nv8WG^L>)#K1se$vfd}z?vujYJ0%%+LhO_*zRO{% zz7oA36V`;+N|v>fjI4$;x9AVKaXAe9m6!3h(7`c?fsF(4&b?pqUHUhp8y1GWuUFnD zb_X49bY^^+r{3@8JMgbdX)xBSofDcNd2QMfad6;(nx7s@n;kYEfadSzo zv3EQu$vWdUGDt!VT-<4DtZ<7LCL3u>$Qrs*V_3WH>`INw6WFhIn%{$g3n^&pEk-No zy>~e&nK`(;SBA`Nla?OjPWptf`zVy%lrfxL=jOqt*4%nSYj%A|L6GE* z)+9#1huR5K_Z~gXiNi-&vL;NF=RfnTb$!{Zw%zaV2|N1DG3;RpNy*qNKc zWBV}Db8utXPph3vJdUfc^j{r{P7>cA;&$G66sf{AP^4Nc;X{FVQ{XsvzOA!^(70i7 zDN_#@lSMn~L36IQ4ZBwb!z&9LUi}#fD%D~uNwg!Dk;a=e3$9hGwI-|y{DC7Wjj*f$ ze7srn0)|mJ;=M@+9RH0FHnu4m2=rsvq(tcW92c3<#VCGEM?`BsBNU6$2pXoDF;YZN z;lH(~M4?9XgCxZg`>`bd!_-L&Zs95Nt6b%v)kd_NVK*4aF z4WkgF6?S15wFy4Sf=g=LmH0h~tV+r3olq%Ryb~)W*_&M{S-m-xlFgf2DTR3_;p|Et z=%~d34Qp|{5Yz-&!NdmyQh*I2+}jtI<*lwzT(;FzdHILdNZMHp2&#Q96GmsLCZEez z7P0g3Z`Qg^C}8Nnx=mFk?Nbah*eQn(kyaFojqRSJa%oM$P&uy*huzcmrXo|!Ip#m$rP+6jv-Fr>#~gyi z`N6STa=trOMNa>3x6=&#w-@j|8o9sB(Gs?@0hcxW`+-rx+bvEnn5d2l)DdW7v1=T; zGFB-#hRR`jPx`qA{31hsZa%Jmr5kh$2T(b8?)(;1w}ev7Dwu{Fj*DX-bPYyn5RNqngP2)ah4^)dX;UXDbZM`FhA_mZDlzcS zs^NiyL284~xDdjgBoQilLp_5=oXlsG8!sFVIsny5PD1I5Y*-;ziuJ8yj3 zwqZ!yhOTW798f;|me7$5T{>KKbS-X0cyq&$n;W{`93&hRBplM3aA?Djp$%P!1__fm zA$Vs5gN%oe2(`aOj>j5?Jl4?lv0$j|V5p*Cs5z}8db(lA(+ypp4hHcAgFF-bS`_?x zKlt@2|ALBNv}X8a!;miZ5d9NokB$ z3A^*(p%XgFrhl^4*LwoTY7%M6nrg1?KM(7GVK5&%|AcA@TXivd9>&hqAUyCA!vo6y zx6lM$xf*cM)qq711QCWZB-S+RYebkzvVwrqL)W!YIr^L0rKYH%)`xS!?AxDNV~QG zbxbtz<4JrAL8G;{BpNdZ87QNUs$$x`i>jCihc@A02kTd$X|buZQJpGpoK$4}Bu-5r zS+LODo5P&%G zu4EN%QFcIDbOWH$ZPjWo5*@eP_R<{$s8SaH5a`bV!|_dLpjh=ev}tg^UaU7-1#C<& zol%MwP4FMuw4i?#-@>b0IMJmG2otMxY%l&H@CCjd{F|m`DFCAom+X$N(mN3utqDRs zcExHJemO2oWFrYv1IMe6adMd}#g%Gf@(!1m1{;rW27YJf(3=0w2u* zmc$H*i;S?_lL`_~IJ($qKV3{KDR3%JSgRxnsfGK2FPxAtA?a--;rAla$`iJI7HqE6 zm0KFLw>hBN00dq&03J>ec#&e}u~9xjgsj7>o3VAsxIVUN^tdn(Nkv`>Q+`%ln1C#F&yOOTup*5zjYO+LqG1w*Hnp|;QN!^pT#Z1V0a_gm z$7kOk1N#t?!o%Jeyt&Axw7(6bocsCk)Q!$$@Lo%c?(9qp3W&Xf5*MdaR>8`~xkOa& zr>PsyPo-k!P?_^1Z%lMV#XIwHk~01s zkGI=9^S!Z{(eH+d7Iar%=%%4vd7<0JXeIHCqMq>&!Zy7AKjR*sE%#NUazDQ+Cg(oy zeap>t_q$ic5E++lAN9yt?kc@=>=5iaZ|RUEy8Mz3&LGRfKo{tEZK;zx1SB z`IxIljcH3;>@8{;W!C%Dtad9`mZM!Eox80uX?Dl)y?hg2~ ze~_Eo5r3UNh>7X^LEE;GdnZqh+Us^l@0~fb%ie;5n0>jqH|~4->8|^R4!sHP&HINA zy=DL0x!v~X=EmYrsv0`fR236rmT-9LdVf{9xu&YYgK>J69u)uUKEKqsHAJl7AkTNa zOj(_eEAhV8cjx9cg~&50D3-b{&mK)fQ?@kPas}=?0XHkQ<@HxXp;XdVWmRTcwJQfHEi%`9f3Q=-`FO`OP9>$vG4GZZ+pmh`!c7Gi$Ls>e0K_3Tt!0v1?!CHv=nO>=qSnd zpLsHCD=3Dcsf z0x%)%Xn>(?6}b;5UpHnbpExmVrO3S#ybM$HXcY=t$kAs z7FJfgH#Vby$6G3JeW4VS-{ydJaeIm#7`z;HY-vHwF1` z|FPd>nNGh9m}UHMV7~D)S^rAhQfYw$b&dTZBn?yBcpc7Jr~Gn!Z5z8+M)l`-@Buw0 z-xQN0<0m(Qno%9qqFcok^8G?LQL&j&rP&m(8EHi>ZURDbQMw_1ueB5=*dnLI2Oh+O zB+85LfOjP>PQW3GE;tTviOEMG)WK%I8R3~UbVlhwshR5Df6pRR@w4s z=pZF@?|_}b9s2k00B=GE;}0OMp?Xy@~bJuFU8~xO%X(B zhvH9?(yRqkj-940444awKe>hOwh#9=Kj`hU;j%lFgoW7HtbBO79`~soJMAoiT~~Ul z>)HDN-lgojv>NSbj@4_05H`AK)1ppCvB9ssfCD;P3{viu%Oo)I%Q5*5FEzRrArYdt zEq>6~MRMa>K}2?7ams@gyy?!XsIPZMoS~wr{Ql>~5f!B}wfbn^Wv%^s4?@bSRg=AT zl(X_}_nuYOo!OSi-lN$2A$qgpa`<6Jq^RE>e~D$o4VO{o8Axj^->hkccl(`R5{X|2({yP|y>cODW%#%#o-Mat0v$ ztiT-U0anKVf_@rm#)=?aAgmfEwazdgC<9@F5@*xgt5PsGYI*QM6f;PPvxDW^Zaa&w zJW^K_VPp$HP+sZHDlf3cf203Gnc@lp)NQRhHz8!RCNE0i&)Q)Ec>q!vh*lqI^o-V&<`$?nJBMF8p)WhW zBH330zBtx_AEUr;;54?0U;<&#PVCUE^QI2+n;ib=jnN4YLnlwbhM!b&es&JubXh{` z3H~hyqUxgQN02o0zG&*v(u`y+8{rZnGc-D#Tdwb4~8WT?T7 z^ixzPPvf_TY1puves-9+-oZw58yYe5O>N}t+4zx=C!ZvfDsT8B(Yn<{*duUa3WbY& z%E*g^dRvXg7lPj5MjMa+_KUO;1f1Re81Ie2r5d&h^I-27`URPo+>1i3K?xzUYfGD)`Dr0_No;q>kOX~D^bCosoL7qV=O zrpl(&+x4CG-kaVL;~0M|Mm0C1#9=g}MPf~u5wir^aB|}b|4%fj-9fb&;~SU>BTpO` z_jbhgq&~`%?BW#KAW%`Ryn6u`Wp=~9C))gga5qt!zI+}TCv;?lUx6kIup37xQAIr1 z=+#wHd!SIGaAMP%oz<5}&wu~_0qtWpq@>b_e%wHWx`jSebp?ZSoJqmJ&A>S~0ca#;)O(|KD6xuJs>7;A{PV z8yW0vwk78zJJ$9A{H9Ad*X%~TpW5AC)FAl1nYh8U@{V?Igr?D`Iz)(Dr0l#SlL|>Su4Z>)40t?VINjtPDfqsh$i^Z0m^DeFR zuq9XaGg6p2vM98NUx$n3GmKJL?17z_M-Wv#YX?j=YADP$s@d5_Ha3!NJ>qHYt7-M@ zZE%ij=Ka_0F*va2mGaFvqqoy+@|yCcwPurQLLa9bokwbsa4{|QPb5vMxvWWa+3W)y zkV4H~6H>^pP_MGWIjK1uxAz{fpWoQsIkUO+_F}EsFCA#W`c$U_^trK_$r#r=F7D1a zdIl!M-`?vETEj-+3=415H!f~aT%Wjz8``#O-@)FoQ|HL2=q@ogcD?E5Te`(6SmtJ- zu7hu_u&#$3mNYjw_!&wo4%{F8fOhbqTH}>Z0&OC{u?Vk+X+yRBXMB~`Uore|;k~f^ zf)1>~!JLCls#2NL#To{>L;f57ZC zCnTz)eC`70SK9FnRvfjl{e}+i?e#c=I`{{(cPPefLlR=CBJOxOkI5aAufIuh|FIsg z%%Xm{;ybUk2CdA0bv--p}~wayKR}cP?f} zB*T3=7yQ8AaEIo)-CTa|&d$!w&7Lhuh6_2|B!4T{E&Om1eroRhEp9GFhxiP4D5RzI zFx+l!+qRuNSdt7kck=AnlZjrqY(5I{8SaFcbALNwf+Pjqf;m%j$uC@JFZAawY3GJq zh70`MY$hLf2~P1D?ojto8&6;E&Lw#%eT7Tx2_NFd4Fw*4=KdDX+(=0>+zCa4Zyqob z@(CA#89&3F+y3Uk(qdUVop7{{8;%zyBV7QSS9U+s^MFb;Mtkd;PgJB_(T~WAae>CwbP9{Hz{u z3;#M6p2|Okf1zZzZj^hygrY-yqTK6yAT6bbDEIo&rlzJ9`$5mky_rK#8aZ4A7%Dqwkxw{Uw zqWS=?DEC2ElzXH68|6OeigIt1f1}(7T~Y3J!xiP;DE~&e54zA(=+E6}dAT>rKcxee z`=CpBlzXH68|6OeigIt1f1})ox>WAT&s(o2+kqx>TlR_=qYDECJBH_Cm`73JP2|3@i&K`~Tz(`~D>I7IPuCk*drUxS+$;@(Aq=S9t&bbqqxe z_y1W<3`1NgpaK7vZZUoww-{Mzlbh zJ(c{6E6O)71^tZ7F{!^J`c35=pD~8v;phA_#xVZHn=31Q6klpuS^?2l2z;i%gCCbi zga>{xlW$Ds4A3vCP%9R}KQnW5=1dmfCN5t_29s|Q^x^nSfma27G?#yqy6Ji1moYk{ z2;mQ{{^ARYuTM^|UZl^Fk|RgR-y^q25Aq*+<C&{SU1c_#A-;KQ5054?OAL2YAx| z^VOe!LHhO1>D`O;KeXmBmoGbx$@e+*;rLzx|GB{T=JK!k{4nX?C%bnX>0jKicss>6 zD{4kE>Ervsx0Cz@(FMcFUwpE72g7%qmBjr^c5#1UBKa3L6z^j8kD8Up?0>wN<0EG= zeDU#O&OdS%<6nHzx0BhwW5IA{{|14N5_s_A@`&)jGy4P2?BB3xJF{=p%w%T&e|*snX8*{UNnHN}4LrU(qqzPbpCErh^uSJB-|FLB-@7BZ z|D%)Ke@92IfAvYO|J@zA{?!~Gc{jrsAFt;8BkyMXi%))hg6lu96W3qhqXfP|gpU&8 z8>$<){&z=l{c9Syes@K2{XgXLMGj)}EowL*@OKJ)jlkc<~au#g|&psT0vx2z;i%gCCbiga^KX$u}!13iK%b4L`t-(jOJg;(MOU zmr=yzQ=t#X&k*=U0zZ?>ziH9&7%Ok-*R7^25*UpEQ%%ANp7D z_-15Me7TX!;FAR&{J13v;#cY^zK%V5^dR~MfsYb+ z@Z<7`@W6k;Nh*FM7$}*gr$qAAXkq z;b-+f>I)v<3|{_G|B=4{^&g8L^&i8d{^Nd8{!>NyM}5WWf7E}h{zv`C=u!VMdency z&&xmRKUV*v{$uq&>Nkc*{U`M0;lWS%dHDxk=+DbH>OZcZDF3Mch<*|D5&8?fDF3MM zxICi#!!Pu&D5v~@^csWkKG>i9&_9j*u*N2ahyA&K$!6{^OeH^Z&*sgbNA5{YC3@JO z;bDJ95Bm#0<6jK>S5kbie;U!lz6=lhGJ5b6ejXn93MLz}OfEm{zlr$4{u!WORk6Ba75tMjb22BvzgAsaN&X3=C!~>o)#g>3 z7(R7U2KWDQGxtwSB|k!Meg*Uhotj1Tn-GHdWlUmt_&NWKNsRxh&1);y5g!Z|J%Nm_y3Yd z@&4bAtsI{;iueC^Y~}ouM)CgNuARL9mpGjF|F#Q!vcQ91G31*CezTZ-TY3Kvc;5bP znne@7Tuse@P=*|8Et>3p~9uh5ajq{nLc~ z%Z2^N2>ZV-?4K^|UoPxFM%cex*nf<$f4Q*#7-9cPVgEE?e}TvNhud3($9PKkh5g3} z`>VpfqlJAp3HxRU`&S5jrogKLKU&yd74{!3?7v#rKS$Vqt+4+DVgFUa{*#3L|0wJ~ zQP_W#u>T}s|5d{NlZ5?O3HwhH_FpUPKS9`E;FAS@y9l2w!f#(C>_17^zeLzKSJ-!r zuy3}o|7wBH5%>~;&lUDB5%$mJ{r~N&w{K8)(7w=lNgW(e= zC2{{BcX9v3MDj0&yt_a@1M(&k{f<=}pEQZ#;phC5CNciSyVmZ+y;tZ@P8dF%=%F8v zU*N${_<4BXw=?-B;b&PfxO_?3Ouj|XhvRbuzC_@2x%@jy PcI+U2Nx4Z$lJvg-)-W4R literal 40976 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 diff --git a/tests/roms/LICENSES.md b/tests/roms/LICENSES.md index b216ee82f..43215246a 100644 --- a/tests/roms/LICENSES.md +++ b/tests/roms/LICENSES.md @@ -188,7 +188,7 @@ AccuracyCoin is a single-NROM-cartridge battery of NES accuracy tests. The ROM i plus hex error codes) with no `$6000` status protocol. The integration test in `crates/rustynes-test-harness/tests/accuracycoin.rs` decodes the per-test result state from RAM and asserts the measured pass rate, which RustyNES holds at -**144/144 (100.00%)** (see `docs/STATUS.md`). +**146/146 (100.00%)** at upstream `f5f41dc2` (see `docs/STATUS.md`). ## "full palette" ROMs diff --git a/tests/roms/accuracycoin/AccuracyCoin.nes b/tests/roms/accuracycoin/AccuracyCoin.nes index 8ba95ee6edb2f968687513ad2e1e5039162801f7..26b6ac06191d13a0a3f5c7e24fa50af2a22e92aa 100644 GIT binary patch delta 19526 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 Zeh?P*pgT!oc55H*9cfsvb-KgZ{{s+3JH`M2 delta 19723 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 xo>H `46199ae4` window is two commits and **165 differing ROM @@ -35,9 +36,20 @@ the battery passes; re-extracting `SOURCE_CATALOG.tsv` from the new `AccuracyCoin.asm` reproduces the committed TSV **byte-identically**, since the insertion moves code and not the result-address map. +The `46199ae4` -> `f5f41dc2` window is nine commits: two new tests (`DMA +Landing on Write`, `DMC Reload Timing`), fixes to `DMC Sync`, `Misaligned OAM +Behavior` and `BG Serial In`, a renamed PPU "Data Bus" -> "IO Bus", and +546 bytes saved by new row macros (see `../AccuracyCoin/README.md`). One of +the fixes changed a verdict here. `adacbc23` ("stack fix") makes +`FAIL_MisalignedOAM_Behavior` pop the return address its `JSR` pushed. Before +it, a failure in tests 2-7 of that routine returned INTO the test body, +which ran on to its final `LDA #1` and recorded a pass, so this emulator's +three real failures there (tests 3, 6, 7) read as 144/144. The new ROM +exposed them; v3.1.0 fixes them (`docs/ppu-2c02.md`). + ## What it is -AccuracyCoin is a single-NROM-cartridge battery of **144 NES accuracy +AccuracyCoin is a single-NROM-cartridge battery of **146 NES accuracy tests** (plus 5 print-only `DRAW` tests) spanning CPU, PPU, APU, bus, IRQ, NMI, dummy-read / dummy-write, DMA, and mapper behaviour. It is interactive on real hardware (the user navigates with D-Pad / A / Start), but our harness uses a fixed @@ -45,7 +57,7 @@ button-press script that triggers "run all" and then reads the result addresses out of CPU RAM directly. Current pass rate (measured via the RAM-direct decoder), against a catalog -of 149 rows / 144 assigned tests: **100.00%** (144 of 144). The last two +of 151 rows / 146 assigned tests: **100.00%** (146 of 146, v3.1.0). The last two gaps were both on the `Advanced Sprite Evaluation` page — "Misaligned OAM2 Address" closed in v2.6.17, and "Frozen OAM2 Increment" in v2.6.18 on the rule that a `$2001` mask change during dot N must not act on dot N @@ -57,7 +69,7 @@ See `docs/STATUS.md` for the authoritative breakdown. | Harness file | Purpose | |--------------|---------| | `crates/rustynes-test-harness/src/accuracy_coin.rs` | Drives ROM from power-on; reads pass-rate from RAM via the catalog decoder. | -| `crates/rustynes-test-harness/src/accuracy_coin_catalog.rs` | `OnceLock`-lazy 149-entry catalog parsed from the TSV in `../AccuracyCoin/SOURCE_CATALOG.tsv`. | +| `crates/rustynes-test-harness/src/accuracy_coin_catalog.rs` | `OnceLock`-lazy 151-entry catalog parsed from the TSV in `../AccuracyCoin/SOURCE_CATALOG.tsv`. | | `crates/rustynes-test-harness/tests/accuracycoin.rs` | The CI gate — prints per-suite breakdown + per-failing-test list. | ## Why two directories? diff --git a/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md b/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md index 55a94df67..d39646ee7 100644 --- a/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md +++ b/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md @@ -431,6 +431,13 @@ variant is what would put the first two in front of a user at all.)* > sprite-0 stale-shifter item are still open in §6b; R2, R4, R5, the `$2002` > race, the `$2007` read and PAL alignment are closed below with evidence. In > §6c the CPU-multiplier overclock is the one open build item.)* +> +> *(2026-10-07, v3.1.0 records item DOC-01: **R1 shipped** in v3.0.0 as +> `T-MMC3-BG-A12` (§6b's `[x]` below), and **the CPU-multiplier overclock +> shipped** in v3.1.0 as `T-CPU-OVERCLOCK` (`Nes::set_cpu_overclock`). The +> sprite-0 stale shifter is the one item left in §6b; the line plan schedules +> it as ACC-04 in **v3.3.0**, with ACC-03. AccuracyCoin is 146/146 at upstream +> `f5f41dc2` (v3.1.0; `docs/STATUS.md`).)* All remaining hard-tier accuracy residuals share **one root cause** and converge on the v2.0.0 one-clock + every-cycle-bus-access refactor. They are **outside the @@ -1065,6 +1072,14 @@ plan); the A/V, HD-audio, shader/NTSC, GPU-timing and egui-render verifies → the original re-bless is done (§7, commit `c286e632`) and each future broken-boot fix re-blesses its own snapshots, which next applies in **v2.9.6**.)* +*(2026-10-07, DOC-01: F1 and the other device runs belong to the hardware +release, which D29 placed at the end of v3.9.x; F3 and the browser-RA deploy +are **v3.4.0** (hosting, D19); the A/V, HD-audio, shader/NTSC, GPU-timing and +egui-render verifies stay **unscheduled**. The "next applies in v2.9.6" above +is history: v2.9.6 shipped, and the re-bless rule is now enforced by the epoch +fingerprint gate (`T-EPOCH-FINGERPRINT`, v3.1.0), which fails a moved output +until the epoch rises.)* + --- ## 11. CI / tooling follow-ups (proposed, not yet implemented) diff --git a/to-dos/README.md b/to-dos/README.md index 4a1e7cbf2..58ebe8dc2 100644 --- a/to-dos/README.md +++ b/to-dos/README.md @@ -11,8 +11,8 @@ lines named v1.8.8 until v2.7.6). This directory holds the phase-and-sprint development history that produced RustyNES v1.0.0 (the cycle-accurate production core) and the long line of feature/platform releases built on top of it. The phases below are -**delivered** — RustyNES ships at v3.0.0 with a cycle-accurate core -(AccuracyCoin 144/144), **191 mapper families**, FDS, Vs./PC10, rollback netplay, +**delivered** — RustyNES ships at v3.1.0 with a cycle-accurate core +(AccuracyCoin 146/146), **191 mapper families**, FDS, Vs./PC10, rollback netplay, RetroAchievements, TAS movie tooling, the performance + desktop-UX shell, the full v1.1.0 → v1.7.x feature set (Lua scripting, visual filters, the studio / TAS-tooling / debugger-depth suite, the writable/programmable "Forge" tools, diff --git a/to-dos/ROADMAP.md b/to-dos/ROADMAP.md index 560260fa0..0b9369205 100644 --- a/to-dos/ROADMAP.md +++ b/to-dos/ROADMAP.md @@ -57,7 +57,7 @@ v2.8.0 → v0.9.7; the synthesis itself = **v1.0.0**. ## Status -- **Current release:** **RustyNES v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). **No hardware has run any bitstream.** +- **Current release:** **RustyNES v3.1.0 "Bellwether"** — the AccuracyCoin re-sync and an honest 146/146, the CPU overclock and the sprite-limit option in movies and netplay, PAL emphasis, the alternate MMC3, rewind and run-ahead on the Vs. cabinet, and the MiSTer core matched to all of it. Built on **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). **No hardware has run any bitstream.** - **The line after v3.0.0 (drafted 2026-10-07):** [`plans/v3.1-to-v4.0-line-plan.md`](plans/v3.1-to-v4.0-line-plan.md) indexes v3.1.0 → v4.0.0, with one plan file per release and the maintainer's 29 decisions (D1-D29) tabled there. In order (D29 moved the hardware release to the end of v3.9.x the same day): - v3.0.1 "Mortar" (in progress); - v3.1.0: options, emphasis, the AccuracyCoin re-sync; @@ -73,7 +73,7 @@ v2.8.0 → v0.9.7; the synthesis itself = **v1.0.0**. - **Programme after v2.4.0 — the v2.4.1 → v2.5.0 "Fabric" line, and the v2.6–v2.9 programme behind it** *(since 2026-09-22 the programme ends at v3.0.0; ADR 0041)*. An **independently-written NES core in SystemVerilog for MiSTer FPGA and the Retro Remake SuperStation One, verified against RustyNES as an oracle.** Not a port, and it cannot be one: a MiSTer core is SystemVerilog compiled by Quartus 17.0.2 into a Cyclone V bitstream. The reference firewall therefore extends to HDL — `NES_MiSTer` and `fpganes` `rtl/` are **strict black boxes**, instantiable as opaque modules to compare *outputs*, never readable as source. **v2.5.0 is scoped to "the 6502 rung closes"** — the co-simulation harness plus a cycle-exact 6502, gated, **as planned**, on nestest 0-diff and per-cycle bus equality — of which **per-cycle bus equality was achieved and nestest 0-diff was not**: it stops at a `$2002` read where *both sides address it* and only the data differs, because the DUT has no PPU. That and the 5 M-cycle window are **reclassified as rung-3 acceptance criteria** rather than carried as v2.5.0 debt — because the arithmetic does not support more: a from-scratch cycle-accurate NES core is **7–13 months FTE** against a two-to-four-week window at demonstrated cadence. PPU, APU and MiSTer integration are **v2.6–v2.9**; stating that now is better than discovering it at v2.4.6. The design is **replay, not lockstep** (the determinism contract makes a pre-recorded trace exactly the trace a lockstep run would produce, and `Nes` has no per-cycle step to lockstep *with*), **no DPI-C** (it would put `` `ifdef SIMULATION `` guards into RTL that must also pass Quartus — the exact construct that lets a simulated netlist drift from the synthesised one), and **hash first, capture on divergence** (a 4200-frame AccuracyCoin run is ~7.5 GB of per-cycle CSV; 4096-cycle hash checkpoints are ~480 KB). **Two risks are accepted in writing:** the core may be **declined as a duplicate** — `NES_MiSTer` already scores 121/125 on AccuracyCoin, and *real Famicom AV hardware also scores ~121/125*, so there is no published accuracy headroom; and **the oracle can be wrong**, since 141/141 is not "matches silicon", so every rung is labelled by whether it has an **independent** oracle. Retro Remake is a planned fallback home, not a contingency. See ADR 0037, `docs/mister.md`, and [`plans/v2.5.0-fabric-plan.md`](plans/v2.5.0-fabric-plan.md). - **Programme after v2.5.0 — the v2.5.1 → v2.7.0 line: the rest of the console, and a contributable package.** The Fabric line is delivered and the 6502 rung is closed; this line builds the PPU, APU, mappers and MiSTer integration, and takes the core to a state worth submitting to MiSTer-devel. **Maintainer decisions, 2026-08-23:** hardware is **both boards eventually** — a DE10-Nano **plus the SDRAM add-on** (mandatory: the NES reads cartridge ROM directly and the onboard DDR3 is too slow) and a SuperStation One (128 MB integrated), with **one `.rbf` booting both** turning "SS1 runs MiSTer cores unmodified" from an inherited claim into a measured one; mappers are **the top six** — NROM, MMC1, UxROM, CNROM, MMC3, AxROM, ~90% of the licensed library by title count, explicitly **not** FDS, expansion audio, or the remaining ~168 families; and v2.7.0 is **scoped to what genuinely fits**, with the arithmetic stated up front (**rung 3 8–16 wk · rung 4 4–8 wk · rung 5 2–4 wk + a 4–12 wk tail · rung 6 2–4 wk · rung 7 4–8 wk = 20–40 weeks FTE** before the AccuracyCoin tail, across twenty release slots — **milestones, not dates**). **Rung 6 comes before rung 7 deliberately**: NROM at 327 Kb fits on-chip, so hardware bring-up needs no memory controller, and getting a board in the loop before writing the SDRAM controller de-risks the second largest technical item. Two v2.5.0 gates — **nestest 0-diff and the 5 M-cycle window** — are not carried as debt but reclassified as **rung-3 acceptance criteria**: both stop at a `$2002` read where *both sides address it* and only the data differs, because the DUT has no PPU. The contribution requirements were **fetched from the MiSTer-devel wiki rather than recalled**, and one line of it is the whole case for this programme: on AI-generated code the project asks for *"a minimum reasonable bar for readability and… evidence of quality and accuracy testing"* — the co-simulation apparatus **is** that evidence, and no incumbent core can show its equivalent. See [`plans/v2.7.0-mister-core-plan.md`](plans/v2.7.0-mister-core-plan.md), [`mister/`](mister/), and the four dated research files in `ref-docs/`. *Superseded 2026-09-22 by ADR 0041: the hardware-verified core and the contribution package are v3.0.0.* - **Historical detail — v2.2.4** (2026-07-24) — a **libretro / RetroArch distribution** cut whose purpose is that the RustyNES core **builds and installs cleanly through the Libretro buildbot** () for in-RetroArch use. **Zero emulation-core changes** — the deterministic `#![no_std]` chip stack, save-state / TAS / netplay formats, and every golden vector are byte-identical to v2.2.3, so **AccuracyCoin holds 141/141 (100.00%)**, nestest 0-diff, by construction. The work is a libretro-completeness audit + metadata correction: the core is confirmed to inherit every v2.2.3 change automatically (the fast-dot-path default, the `PPU_SNAPSHOT_VERSION` 8 / APU v4 save-state schema handled transparently by the dynamic `snapshot_core_into` sizing, the `Mapper::mix_audio` i32 widening, the Zapper model, and the `mNNN_` mapper rename), and both buildbot cross-ABIs the GitHub gate models — `x86_64-pc-windows-gnu` and `aarch64-linux-android` — build clean. `rustynes_libretro.info` (the metadata RetroArch's core downloader reads) is corrected: **`disk_control` `false` → `true`** (the FDS multi-side Disk Control interface has been wired since the buildbot recipe landed, but was advertised as absent — the real fix), `display_version` `v1.0.0` → `v2.2.4`, and the mapper count `168` → `172`. Also: the reviewer-tooling standardization onto the shared Antigravity template rides along (`scripts/agy-review.sh` + workflow). Documented libretro follow-up: **core options** (region / overscan / palette / accuracy toggles) remain unexposed (`core_options = "false"` is accurate, not stale) — a deliberate future enhancement, not a v2.2.4 gap. See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[2.2.4]` + `docs/libretro/`. -- **Release line since v2.1.0:** the v2.1.x **"Fathom"** accuracy line (v2.1.0 → v2.1.10) → **v2.2.0 "Capstone"** (the milestone cut closing the "deepen the existing project" run) → **v2.2.1** (housekeeping) → **v2.2.2 "Conduit"** (build / distribution / CI-integrity) → **v2.2.3 "Datum"** (performance appraisal + the last two Holy Mapperel residuals closed) → **v2.2.4 "Cartridge"** (the libretro/RetroArch distribution cut) → **v2.2.5 "Colophon"** → **v2.2.6 "Almanac"** → **v2.2.7 "Timbre II"** → **v2.2.8 "Aperture II"** → **v2.2.9 "Studio II"** → **v2.3.0 "Datum II"** → **v2.3.1 "Plumb Line"** → **v2.3.2 "Lucid"** → **v2.3.3 "Cadence"** → **v2.3.4 "Ledger"** → **v2.3.5 "Manifest"** → **v2.3.6 "Sounding"** → **v2.3.7 "Overtone"** → **v2.3.8 "Parallax"** → **v2.3.9 "Crucible"** → the **v2.4.x "Fabric"** co-simulation line (**v2.4.1 "Fabric"** → **v2.4.2 "Cairn"** → **v2.4.3 "Touchstone"** → **v2.4.4 "Ignition"** → **v2.4.5 "Compass"** → **v2.4.6 "Abacus"** → **v2.4.7 "Keystone"** → **v2.4.8 "Palimpsest"** → **v2.4.9 "Plumbline II"** → **v2.5.0 "Rungwork"** → **v2.5.1 "Retrace"** → **v2.5.2 "Dormant"** → **v2.5.3 "Hysteresis"** → **v2.5.4 "Escapement"** → **v2.5.5 "Raster"** → **v2.5.6 "Vestige"** → **v2.5.7 "Collimation"** → **v2.5.8 "Blanking"** → **v2.5.9 "Overture"** → **v2.6.0 "Assay"** → **v2.6.1 "Interleave"** → **v2.6.2 "Witness"** → **v2.6.3 "Mainspring"** → **v2.6.4 "Rubric"** → **v2.6.5 "Muster"** → **v2.6.6 "Chassis"** → **v2.6.7 "Detent"** (the bitstream becomes a published, reproducible release artifact and a one-cycle disagreement is attributed rather than fitted away) → **v2.6.8 "Arrears"** (the gates the previous release fixed and never widened: four of six denied checkpoint streams had been passing unnoticed, three of them not run by the suite at all, and nestest widens 19x and gains the nine-field comparison that closes the gate's stated `nmi_line` blind spot) → **v2.6.9 "Abeyance"** (an exclusion hides improvement as well as regression -- both denied co-simulation streams close, and the larger one was a defect in the HARNESS rather than the console, carried for seven releases behind the phrase "by design") → **v2.6.10 "Inference"** (the cartridge meets the synthesiser, and simulation could not have asked the question -- rung 7's five boards had never been through Quartus, and `chr` written from two separate `always_ff` blocks could not infer as an M10K, so 128 KB stayed in flip-flops: 1,048,576 registers against roughly 166,000. The fix is behaviourally invisible -- 141 gates unchanged -- and the release carries the bitstream the previous one could not produce) → **v2.6.11 "Exposure"** (a picture is a gate the ladder did not have -- all 141 co-simulation gates green and two of six commercial games rendering wrong, because only THREE of those gates compare a framebuffer and all three ship CHR-ROM, so a CHR-RAM write taking the shared-pin composite address built for fetches was reached 9,600 times and compared never)) -> **v2.6.12 "Groundwork"** (the bitstream was an NROM-only console -- `emu.sv` left `cart_mapper`, `cart_prg_16k_banks` and `cart_chr_8k_banks` unconnected, so Quartus tied all three to GND and the fitted cartridge was mapper 0 with 8 KiB of PRG and 8 KiB of CHR against a declared 256 and 128; no gate could see it because `emu.sv` is not in the testbench file list, so all 142 gates exercised a correctly-configured cartridge, and Quartus said so three times in messages that cite an instance path rather than a file) -> **v2.6.13 "Slack"** (the cartridge outgrows the die -- an SDR SDRAM controller, a behavioural part model, a four-way arbiter and a console bridge written from the AS4C32M16SB-7 datasheet, and three consumers measured to three different budgets rather than the one figure the previous step assumed; the PPUDATA port leaves the shared bus through a handshake and that fix shipped a defect only a banked cartridge could see, the request carrying the raw PPU address where the mapper's translation was needed, so every banked-CHR board read bank zero; off the die the console passes 142 of 142 and it still ships on the die, because an off-die core cannot run without the add-on) -> **v2.6.14 "Docket"** (the submission checklist becomes auditable -- 30 boxes, 16 unticked and fourteen of those saying nothing about why, so an unticked box could not be told apart from work outstanding, work blocked elsewhere, and work already done and never ticked; the third case occurred five times. One box asked `docs/provenance.md` to state that no NES core was ever opened, which that document's own Do-not-self-certify section forbids, so it could only have been ticked by writing the sentence the provenance rules exist to prevent. Re-measuring the ticked half found two claims that had expired, the task board four delivered items never ticked and an SDRAM precondition the previous release did not follow, and its claim that MMC1 was unimplemented is retracted) → **v2.6.15 "Warrant"** (the claims the submission will make become checkable, and the instrument pays the oracle back — the `.rbf` name this core shipped would have distributed NOTHING, because `Distribution_MiSTer`'s builder skips any file whose stem does not end in `_` plus eight digits, so an accepted core would appear in the Cores table and ship nothing with no error anywhere; two of the four R1/R2 residuals ADR 0002 closed turn out never to have been IRQ-timing residuals, resting on an assertion blargg WITHDREW in the successor ROM; `sys/` verbatim, the `.qsf`'s single seed table and the bitstream name all become checks instead of claims; the nine rung-1 gates run in CI against a PINNED oracle commit, so the accuracy evidence stops being a document describing a check a reader cannot run; `cpu_interrupts_v2` lands on the DUT as the first INDEPENDENT interrupt oracle, five of five; and `T-ORACLE-001`'s opening claim is retracted — RustyNES does clock the MMC3 counter on the pre-render line, which `2-details` sub-test 8 has asserted all along) → **v2.6.16 "Interlock"** (the arbiter's numbers describe a stimulus, not the console: the gate's worst case issues a CHR and a PRG request on the same cycle and the running console never does — zero coincidences and a minimum separation of two over fifteen million SDRAM cycles — so both published figures are wrong about it in opposite directions, the CPU MEETING its deadline at 24 against 24 on all 240,303 requests where the gate said it missed by four, and the fetch reaching 23 against a derived 22 that a `CHR_LAT` sweep shows understates the real tolerance by at least nine cycles; the slot-scheduled arbiter this version was scoped around is therefore NOT built, and is still worth building because zero margin means one added cycle anywhere breaks the CPU) → **v2.6.17 "Terminus"** (a write lands where the cycle ENDS, and this core does not move to meet it. The AccuracyCoin and TriCNES oracles re-sync to upstream, and the battery grows 141 → 144 assigned tests across two new pages that RE-HOME eleven existing PPU tests, so a re-sync is not an append and a suite-keyed baseline has to be regenerated rather than extended. `OAM2Address` becomes a live counter through sprite fetch and the frozen fetch reads index 0 for every byte, closing two of the three new tests and carrying an approved SAVE-STATE EPOCH — `PPU_SNAPSHOT_VERSION` 8 → 9, so pre-v9 `.rns` states no longer load. The third names the dots its two `$2001` writes land on, which is how the core came to be measured TWO DOTS EARLY on both: a 6502 commits a write at phi2, the last of a CPU cycle's three PPU dots, and this core applies PPU register writes at M2-low, the first. The move was then MADE, measured against a first-difference control captured beforehand, and NOT ADOPTED — the write alone reads 141/144 and breaks the independent `ppu_vbl_nmi/10-even_odd_timing`, and the best combination found reads 142/144 against the 143/144 that ships, for a save-state epoch and six re-baselined goldens. `mask_for_skip_check` is PROVEN to be a compensation for the placement — under phi2 it needs one delay stage rather than two — and two of its three dependants are still un-re-derived, so adopting the mechanism now would replace a documented compensation with an undocumented one. `Misaligned OAM2 Address` was also found to be passing via TWO CANCELLING ERRORS, both since fixed independently. Ships one core fix nothing in the corpus could see — the misaligned-OAM `+4 & $FC` realignment, reached 114 times per battery run out of 56,953,944 out-of-range branches and pinned by a targeted test because no ROM's verdict moves — plus `terminus_control.rs` as a standing first-difference gate. The follow-up version the write-placement work had been scoped into is FOLDED here: that split existed because Terminus was to be a scheduler change with its own ADR and a full re-baseline, and the refutation means it is not — so v2.6.17 carries both). v2.6.18 "Errata" closes the last AccuracyCoin entry on top of it, and v2.6.19 "Accession" carried both into the FPGA core, v2.6.20 "Telltale" made the OAM2 counter observable, v2.6.21 "Steward" gave the core battery saves and gated CHR writes during rendering, v2.6.22 "Rigging" built the instruments a board would need before the board existed, v2.6.23 "Pulse" closed the CHR-during-rendering divergence by making the `$2007` access pulse the rendering pipeline's load instead of computing its own, and v2.7.0 "Palisade", the first of the ADR 0041 audit line, made a corrupt save state fail at restore rather than one tick later, stopped pulse 1 muting on the `$4001 = $08` sweep idiom, and gave the save-state fuzz target the reach to find five defects the audit had not, v2.7.1 "Keepsake" stopped six cartridge boards writing an empty battery save and moved every user file the frontend writes onto the atomic writer, v2.7.2 "Bankroll" gave MMC1, MMC5 and Namco 163 the banks their boards have and made `$6000-$7FFF` read open bus on the 205 board variants with nothing there, v2.7.3 "Hearth" made the desktop keep battery saves, closed three ways around the Lua sandbox, gated script HTTP destinations, and brought audio back after a device change, v2.7.4 "Pocket" kept the Android and iOS apps alive through an internal error, gave both battery saves, paused them and released audio when they leave the screen, and made their saves atomic, v2.7.5 "Tally" closed the core audit's twelve performance proposals (eleven by measurement, one adopted: the audio buffer keeps its capacity), deprecated 18 dead bus methods and `ApuBus`, and closed the core and frontend ledgers, v2.7.6 "Recount" measured alone the six deletions v2.7.5 had bounded only together (five zero; the sixth, never reached by the benchmarks before, bounds at 0.2%), turned three redundant fast-path stores into an assertion, and shipped the contributed libretro buildbot fixes and targets, v2.8.0 "Bulkhead" opened the v2.8.x libretro + RTL audit line: the libretro core unwinds and contains a panic at its own boundary, keeps save states the right size with a Zapper, withdraws its memory maps on unload, loads through the standard `retro_game_info` via a vendored binding, and the save state carries the internal data bus, v2.8.1 "Gasket" closed the libretro audit: Four Score, four described ports (the list now NULL-terminated, where RetroArch had read past it), the Zapper unplugged, unclipped expansion audio, UNIF, and a Makefile that honours its callers, v2.8.2 "Solder" corrected the MiSTer core's on-die RTL against the oracle and the wiki (MMC3 acknowledge, SNROM PRG-RAM, the triangle and noise reload drop, the `$2002` latch) and found the audit's MMC1 finding inverted: the emulator was the one ignoring a reset, and v2.8.3 "Rivet" released every reset on the clock that uses it, cut the CPU by about 4% with two exact rewrites the fit report confirmed, and made the ladder run from a fresh checkout (164 of its 165 gates, a claim v2.8.4 found overstated), and v2.8.4 "Tether" closed the v2.8.x line: it found every off-die read would have been sampled a clock late on hardware (the SDRAM model and controller shared the error, and only the new SDRAM timing constraints could see it) and fixed both, pinned both builds at fitter seed 5 after a sweep of each, and made the ladder run all 165 gates from a clean checkout, and v2.9.0 "Survey" opened the v2.9.x line by re-running all four audits (139 rows re-verified, none regressed, 39 new findings fixed or dispositioned), including a Power Cycle that erased the battery save and an off-die MiSTer build that hung under `bootcore=`, v2.9.1 "Hone" found that the A/B tool had been timing the old code on both sides and re-measured what it had rejected, made a Vs. cabinet save-state about 9x faster, moved the off-die build's CHR into its own SDRAM bank, and pinned both builds at fitter seed 2 with two byte-identical clean compiles each, v2.9.2 "Candidate" triaged a fifth, 32-finding audit of both repositories (16 fixed, each finding with its evidence in its ledger), made save states keep the cartridge RAM of twelve board families that dropped it, fixed the MiSTer CPU losing an NMI raised inside a DMA, and cut the release-candidate bitstream pair for the SuperStation One session, v2.9.3 "Handset" moved every dependency to its newest release, answered all 244 review threads left open on PRs #7-#97 and fixed the ten findings that still held (mapper 28 rebuilt, a header editor that writes only what you change), and prepared the mobile device run, which the maintainer then moved after v3.0.0 with the board session, v2.9.4 "Plumb" recorded v3.0.0 as the API major with a release-candidate core (ADR 0043), made CI run the feature-gated tests, fuzz targets and a coverage floor it had only linted or never had, and corrected the records against the code, v2.9.5 "Caliper" gave every open accuracy item an outcome: blargg's `apu_test` frame-counter coincidence, the composite 2C02's odd-frame scanline-0 sprite pixel, OAM DMA filling the PPU I/O latch and KS7032's `$6000` window fixed red first, 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11), v2.9.6 "Roster" added seventeen mapper families from their NESdev pages (174 → 191), promoted GTROM to Curated with a modelled SST39SF040 whose saves persist, corrected mapper 4's NES 2.0 submappers (MMC6, NEC, MC-ACC, T9552), and re-baselined the local commercial suites, which had drifted unread since about v2.0.0, v2.9.7 "Tandem" brought the desktop's features to the web and phones (battery saves in the browser, Vs. DualSystem on web and mobile, FDS and NSF on mobile), made the release binaries the full native build, and fixed a PPU A12 defect real games exposed: the PPU reported one pulse per scanline where the console makes eight, so Acclaim's MC-ACC, the J.Y. ASIC and mapper 91 had counted eight times slow, v2.9.8 "Vanguard" carried v3.0.0's planned breaking changes ahead of it (a save identity that ignores the header, older save states and movies refused, movies and netplay that record the machine, the ADR 0042 API removals), booted every one of 748 staged dumps and fixed the defects that turned up, and made the game database's corrections reach every platform, v2.9.9 "Ballast", the release candidate for v3.0.0, re-ran all four audits and fixed what they found, raised the MMC3 IRQ by the NESdev rule (T-ORACLE-001) and gave the MMC5 the chip's CHR set selection (which fixed *Uchuu Keibitai SDF*), made audio exact across a save state, moved the MiSTer core's oracle pin onto it with the APU and PPU parity that opened, and cut the release-candidate bitstream pair at seed 6 (a seed-8 pair was withdrawn when the RTL changed after it), v3.0.0 "Cornerstone", the API major, gathered every break since v2.x in one place, made movies and netplay carry a core timing epoch so a version that emulates differently is refused with a reason (ADR 0045), took the last struct-extensibility breaks, closed blargg's `4-scanline_timing` in the emulator and the MiSTer core (T-MMC3-BG-A12), and shipped that core as a release candidate, not hardware-verified, and v3.0.1 "Mortar", the current release, unbanked *Famicom Yarou Vol.1*'s CHR-RAM (raising the emulation epoch to 2), reached the MiSTer core's odd-frame A12 exception with a new test ROM that found a one-cycle defect, moved the toolchain and the libretro buildbot to Rust 1.99, answered every bot review left since PR #1, recorded the shared Bisqwit NTSC pass as derived and moved the TriCNES source out of the repository, and wrote the plan to v4.0.0 (`to-dos/plans/v3.1-to-v4.0-line-plan.md`). AccuracyCoin held **141/141** through v2.6.16, read **143/144 (99.31%)** at v2.6.17, where the upstream re-sync grew the battery to 144 assigned tests and `Frozen OAM2 Increment` was the single named failure, and has read **144/144 (100.00%)** since v2.6.18 closed it — and the number was never always *by construction*: v2.3.4, v2.3.7, v2.3.9, the rung-3 releases v2.5.4-v2.5.6 and v2.6.17 change the core, so for those it is **verified** rather than inherited, and saying which is which is the point. **Full per-release detail is in `CHANGELOG.md` and `docs/STATUS.md` (the single source of truth)** — the entries below (v2.1.0 "Fathom" was the prior anchor here; v2.0.8 → v2.0.1) are the older historical trail, retained rather than duplicated. +- **Release line since v2.1.0:** the v2.1.x **"Fathom"** accuracy line (v2.1.0 → v2.1.10) → **v2.2.0 "Capstone"** (the milestone cut closing the "deepen the existing project" run) → **v2.2.1** (housekeeping) → **v2.2.2 "Conduit"** (build / distribution / CI-integrity) → **v2.2.3 "Datum"** (performance appraisal + the last two Holy Mapperel residuals closed) → **v2.2.4 "Cartridge"** (the libretro/RetroArch distribution cut) → **v2.2.5 "Colophon"** → **v2.2.6 "Almanac"** → **v2.2.7 "Timbre II"** → **v2.2.8 "Aperture II"** → **v2.2.9 "Studio II"** → **v2.3.0 "Datum II"** → **v2.3.1 "Plumb Line"** → **v2.3.2 "Lucid"** → **v2.3.3 "Cadence"** → **v2.3.4 "Ledger"** → **v2.3.5 "Manifest"** → **v2.3.6 "Sounding"** → **v2.3.7 "Overtone"** → **v2.3.8 "Parallax"** → **v2.3.9 "Crucible"** → the **v2.4.x "Fabric"** co-simulation line (**v2.4.1 "Fabric"** → **v2.4.2 "Cairn"** → **v2.4.3 "Touchstone"** → **v2.4.4 "Ignition"** → **v2.4.5 "Compass"** → **v2.4.6 "Abacus"** → **v2.4.7 "Keystone"** → **v2.4.8 "Palimpsest"** → **v2.4.9 "Plumbline II"** → **v2.5.0 "Rungwork"** → **v2.5.1 "Retrace"** → **v2.5.2 "Dormant"** → **v2.5.3 "Hysteresis"** → **v2.5.4 "Escapement"** → **v2.5.5 "Raster"** → **v2.5.6 "Vestige"** → **v2.5.7 "Collimation"** → **v2.5.8 "Blanking"** → **v2.5.9 "Overture"** → **v2.6.0 "Assay"** → **v2.6.1 "Interleave"** → **v2.6.2 "Witness"** → **v2.6.3 "Mainspring"** → **v2.6.4 "Rubric"** → **v2.6.5 "Muster"** → **v2.6.6 "Chassis"** → **v2.6.7 "Detent"** (the bitstream becomes a published, reproducible release artifact and a one-cycle disagreement is attributed rather than fitted away) → **v2.6.8 "Arrears"** (the gates the previous release fixed and never widened: four of six denied checkpoint streams had been passing unnoticed, three of them not run by the suite at all, and nestest widens 19x and gains the nine-field comparison that closes the gate's stated `nmi_line` blind spot) → **v2.6.9 "Abeyance"** (an exclusion hides improvement as well as regression -- both denied co-simulation streams close, and the larger one was a defect in the HARNESS rather than the console, carried for seven releases behind the phrase "by design") → **v2.6.10 "Inference"** (the cartridge meets the synthesiser, and simulation could not have asked the question -- rung 7's five boards had never been through Quartus, and `chr` written from two separate `always_ff` blocks could not infer as an M10K, so 128 KB stayed in flip-flops: 1,048,576 registers against roughly 166,000. The fix is behaviourally invisible -- 141 gates unchanged -- and the release carries the bitstream the previous one could not produce) → **v2.6.11 "Exposure"** (a picture is a gate the ladder did not have -- all 141 co-simulation gates green and two of six commercial games rendering wrong, because only THREE of those gates compare a framebuffer and all three ship CHR-ROM, so a CHR-RAM write taking the shared-pin composite address built for fetches was reached 9,600 times and compared never)) -> **v2.6.12 "Groundwork"** (the bitstream was an NROM-only console -- `emu.sv` left `cart_mapper`, `cart_prg_16k_banks` and `cart_chr_8k_banks` unconnected, so Quartus tied all three to GND and the fitted cartridge was mapper 0 with 8 KiB of PRG and 8 KiB of CHR against a declared 256 and 128; no gate could see it because `emu.sv` is not in the testbench file list, so all 142 gates exercised a correctly-configured cartridge, and Quartus said so three times in messages that cite an instance path rather than a file) -> **v2.6.13 "Slack"** (the cartridge outgrows the die -- an SDR SDRAM controller, a behavioural part model, a four-way arbiter and a console bridge written from the AS4C32M16SB-7 datasheet, and three consumers measured to three different budgets rather than the one figure the previous step assumed; the PPUDATA port leaves the shared bus through a handshake and that fix shipped a defect only a banked cartridge could see, the request carrying the raw PPU address where the mapper's translation was needed, so every banked-CHR board read bank zero; off the die the console passes 142 of 142 and it still ships on the die, because an off-die core cannot run without the add-on) -> **v2.6.14 "Docket"** (the submission checklist becomes auditable -- 30 boxes, 16 unticked and fourteen of those saying nothing about why, so an unticked box could not be told apart from work outstanding, work blocked elsewhere, and work already done and never ticked; the third case occurred five times. One box asked `docs/provenance.md` to state that no NES core was ever opened, which that document's own Do-not-self-certify section forbids, so it could only have been ticked by writing the sentence the provenance rules exist to prevent. Re-measuring the ticked half found two claims that had expired, the task board four delivered items never ticked and an SDRAM precondition the previous release did not follow, and its claim that MMC1 was unimplemented is retracted) → **v2.6.15 "Warrant"** (the claims the submission will make become checkable, and the instrument pays the oracle back — the `.rbf` name this core shipped would have distributed NOTHING, because `Distribution_MiSTer`'s builder skips any file whose stem does not end in `_` plus eight digits, so an accepted core would appear in the Cores table and ship nothing with no error anywhere; two of the four R1/R2 residuals ADR 0002 closed turn out never to have been IRQ-timing residuals, resting on an assertion blargg WITHDREW in the successor ROM; `sys/` verbatim, the `.qsf`'s single seed table and the bitstream name all become checks instead of claims; the nine rung-1 gates run in CI against a PINNED oracle commit, so the accuracy evidence stops being a document describing a check a reader cannot run; `cpu_interrupts_v2` lands on the DUT as the first INDEPENDENT interrupt oracle, five of five; and `T-ORACLE-001`'s opening claim is retracted — RustyNES does clock the MMC3 counter on the pre-render line, which `2-details` sub-test 8 has asserted all along) → **v2.6.16 "Interlock"** (the arbiter's numbers describe a stimulus, not the console: the gate's worst case issues a CHR and a PRG request on the same cycle and the running console never does — zero coincidences and a minimum separation of two over fifteen million SDRAM cycles — so both published figures are wrong about it in opposite directions, the CPU MEETING its deadline at 24 against 24 on all 240,303 requests where the gate said it missed by four, and the fetch reaching 23 against a derived 22 that a `CHR_LAT` sweep shows understates the real tolerance by at least nine cycles; the slot-scheduled arbiter this version was scoped around is therefore NOT built, and is still worth building because zero margin means one added cycle anywhere breaks the CPU) → **v2.6.17 "Terminus"** (a write lands where the cycle ENDS, and this core does not move to meet it. The AccuracyCoin and TriCNES oracles re-sync to upstream, and the battery grows 141 → 144 assigned tests across two new pages that RE-HOME eleven existing PPU tests, so a re-sync is not an append and a suite-keyed baseline has to be regenerated rather than extended. `OAM2Address` becomes a live counter through sprite fetch and the frozen fetch reads index 0 for every byte, closing two of the three new tests and carrying an approved SAVE-STATE EPOCH — `PPU_SNAPSHOT_VERSION` 8 → 9, so pre-v9 `.rns` states no longer load. The third names the dots its two `$2001` writes land on, which is how the core came to be measured TWO DOTS EARLY on both: a 6502 commits a write at phi2, the last of a CPU cycle's three PPU dots, and this core applies PPU register writes at M2-low, the first. The move was then MADE, measured against a first-difference control captured beforehand, and NOT ADOPTED — the write alone reads 141/144 and breaks the independent `ppu_vbl_nmi/10-even_odd_timing`, and the best combination found reads 142/144 against the 143/144 that ships, for a save-state epoch and six re-baselined goldens. `mask_for_skip_check` is PROVEN to be a compensation for the placement — under phi2 it needs one delay stage rather than two — and two of its three dependants are still un-re-derived, so adopting the mechanism now would replace a documented compensation with an undocumented one. `Misaligned OAM2 Address` was also found to be passing via TWO CANCELLING ERRORS, both since fixed independently. Ships one core fix nothing in the corpus could see — the misaligned-OAM `+4 & $FC` realignment, reached 114 times per battery run out of 56,953,944 out-of-range branches and pinned by a targeted test because no ROM's verdict moves — plus `terminus_control.rs` as a standing first-difference gate. The follow-up version the write-placement work had been scoped into is FOLDED here: that split existed because Terminus was to be a scheduler change with its own ADR and a full re-baseline, and the refutation means it is not — so v2.6.17 carries both). v2.6.18 "Errata" closes the last AccuracyCoin entry on top of it, and v2.6.19 "Accession" carried both into the FPGA core, v2.6.20 "Telltale" made the OAM2 counter observable, v2.6.21 "Steward" gave the core battery saves and gated CHR writes during rendering, v2.6.22 "Rigging" built the instruments a board would need before the board existed, v2.6.23 "Pulse" closed the CHR-during-rendering divergence by making the `$2007` access pulse the rendering pipeline's load instead of computing its own, and v2.7.0 "Palisade", the first of the ADR 0041 audit line, made a corrupt save state fail at restore rather than one tick later, stopped pulse 1 muting on the `$4001 = $08` sweep idiom, and gave the save-state fuzz target the reach to find five defects the audit had not, v2.7.1 "Keepsake" stopped six cartridge boards writing an empty battery save and moved every user file the frontend writes onto the atomic writer, v2.7.2 "Bankroll" gave MMC1, MMC5 and Namco 163 the banks their boards have and made `$6000-$7FFF` read open bus on the 205 board variants with nothing there, v2.7.3 "Hearth" made the desktop keep battery saves, closed three ways around the Lua sandbox, gated script HTTP destinations, and brought audio back after a device change, v2.7.4 "Pocket" kept the Android and iOS apps alive through an internal error, gave both battery saves, paused them and released audio when they leave the screen, and made their saves atomic, v2.7.5 "Tally" closed the core audit's twelve performance proposals (eleven by measurement, one adopted: the audio buffer keeps its capacity), deprecated 18 dead bus methods and `ApuBus`, and closed the core and frontend ledgers, v2.7.6 "Recount" measured alone the six deletions v2.7.5 had bounded only together (five zero; the sixth, never reached by the benchmarks before, bounds at 0.2%), turned three redundant fast-path stores into an assertion, and shipped the contributed libretro buildbot fixes and targets, v2.8.0 "Bulkhead" opened the v2.8.x libretro + RTL audit line: the libretro core unwinds and contains a panic at its own boundary, keeps save states the right size with a Zapper, withdraws its memory maps on unload, loads through the standard `retro_game_info` via a vendored binding, and the save state carries the internal data bus, v2.8.1 "Gasket" closed the libretro audit: Four Score, four described ports (the list now NULL-terminated, where RetroArch had read past it), the Zapper unplugged, unclipped expansion audio, UNIF, and a Makefile that honours its callers, v2.8.2 "Solder" corrected the MiSTer core's on-die RTL against the oracle and the wiki (MMC3 acknowledge, SNROM PRG-RAM, the triangle and noise reload drop, the `$2002` latch) and found the audit's MMC1 finding inverted: the emulator was the one ignoring a reset, and v2.8.3 "Rivet" released every reset on the clock that uses it, cut the CPU by about 4% with two exact rewrites the fit report confirmed, and made the ladder run from a fresh checkout (164 of its 165 gates, a claim v2.8.4 found overstated), and v2.8.4 "Tether" closed the v2.8.x line: it found every off-die read would have been sampled a clock late on hardware (the SDRAM model and controller shared the error, and only the new SDRAM timing constraints could see it) and fixed both, pinned both builds at fitter seed 5 after a sweep of each, and made the ladder run all 165 gates from a clean checkout, and v2.9.0 "Survey" opened the v2.9.x line by re-running all four audits (139 rows re-verified, none regressed, 39 new findings fixed or dispositioned), including a Power Cycle that erased the battery save and an off-die MiSTer build that hung under `bootcore=`, v2.9.1 "Hone" found that the A/B tool had been timing the old code on both sides and re-measured what it had rejected, made a Vs. cabinet save-state about 9x faster, moved the off-die build's CHR into its own SDRAM bank, and pinned both builds at fitter seed 2 with two byte-identical clean compiles each, v2.9.2 "Candidate" triaged a fifth, 32-finding audit of both repositories (16 fixed, each finding with its evidence in its ledger), made save states keep the cartridge RAM of twelve board families that dropped it, fixed the MiSTer CPU losing an NMI raised inside a DMA, and cut the release-candidate bitstream pair for the SuperStation One session, v2.9.3 "Handset" moved every dependency to its newest release, answered all 244 review threads left open on PRs #7-#97 and fixed the ten findings that still held (mapper 28 rebuilt, a header editor that writes only what you change), and prepared the mobile device run, which the maintainer then moved after v3.0.0 with the board session, v2.9.4 "Plumb" recorded v3.0.0 as the API major with a release-candidate core (ADR 0043), made CI run the feature-gated tests, fuzz targets and a coverage floor it had only linted or never had, and corrected the records against the code, v2.9.5 "Caliper" gave every open accuracy item an outcome: blargg's `apu_test` frame-counter coincidence, the composite 2C02's odd-frame scanline-0 sprite pixel, OAM DMA filling the PPU I/O latch and KS7032's `$6000` window fixed red first, 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11), v2.9.6 "Roster" added seventeen mapper families from their NESdev pages (174 → 191), promoted GTROM to Curated with a modelled SST39SF040 whose saves persist, corrected mapper 4's NES 2.0 submappers (MMC6, NEC, MC-ACC, T9552), and re-baselined the local commercial suites, which had drifted unread since about v2.0.0, v2.9.7 "Tandem" brought the desktop's features to the web and phones (battery saves in the browser, Vs. DualSystem on web and mobile, FDS and NSF on mobile), made the release binaries the full native build, and fixed a PPU A12 defect real games exposed: the PPU reported one pulse per scanline where the console makes eight, so Acclaim's MC-ACC, the J.Y. ASIC and mapper 91 had counted eight times slow, v2.9.8 "Vanguard" carried v3.0.0's planned breaking changes ahead of it (a save identity that ignores the header, older save states and movies refused, movies and netplay that record the machine, the ADR 0042 API removals), booted every one of 748 staged dumps and fixed the defects that turned up, and made the game database's corrections reach every platform, v2.9.9 "Ballast", the release candidate for v3.0.0, re-ran all four audits and fixed what they found, raised the MMC3 IRQ by the NESdev rule (T-ORACLE-001) and gave the MMC5 the chip's CHR set selection (which fixed *Uchuu Keibitai SDF*), made audio exact across a save state, moved the MiSTer core's oracle pin onto it with the APU and PPU parity that opened, and cut the release-candidate bitstream pair at seed 6 (a seed-8 pair was withdrawn when the RTL changed after it), v3.0.0 "Cornerstone", the API major, gathered every break since v2.x in one place, made movies and netplay carry a core timing epoch so a version that emulates differently is refused with a reason (ADR 0045), took the last struct-extensibility breaks, closed blargg's `4-scanline_timing` in the emulator and the MiSTer core (T-MMC3-BG-A12), and shipped that core as a release candidate, not hardware-verified, and v3.0.1 "Mortar" unbanked *Famicom Yarou Vol.1*'s CHR-RAM (raising the emulation epoch to 2), reached the MiSTer core's odd-frame A12 exception with a new test ROM that found a one-cycle defect, moved the toolchain and the libretro buildbot to Rust 1.99, answered every bot review left since PR #1, recorded the shared Bisqwit NTSC pass as derived and moved the TriCNES source out of the repository, and wrote the plan to v4.0.0 (`to-dos/plans/v3.1-to-v4.0-line-plan.md`), and v3.1.0 "Bellwether", the current release, re-synced AccuracyCoin to upstream `f5f41dc2` and fixed the two defects it found (raising the epoch to 3), made the CPU overclock and the sprite-limit option work and travel with movies and netplay, corrected PAL emphasis and the alternate MMC3, gave the Vs. cabinet rewind and run-ahead, and matched the MiSTer core to all of it, including a `$2006` timing rule it had never been tested against. AccuracyCoin held **141/141** through v2.6.16, read **143/144 (99.31%)** at v2.6.17, where the upstream re-sync grew the battery to 144 assigned tests and `Frozen OAM2 Increment` was the single named failure, and read **144/144 (100.00%)** from v2.6.18 — **a score v3.1.0 showed was overstated**: the old ROM's `Misaligned OAM behavior` fail path fell through to a pass, hiding a real failure in every one of those releases; on upstream `f5f41dc2` the core reads **146/146** with that failure fixed — and the number was never always *by construction*: v2.3.4, v2.3.7, v2.3.9, the rung-3 releases v2.5.4-v2.5.6 and v2.6.17 change the core, so for those it is **verified** rather than inherited, and saying which is which is the point. **Full per-release detail is in `CHANGELOG.md` and `docs/STATUS.md` (the single source of truth)** — the entries below (v2.1.0 "Fathom" was the prior anchor here; v2.0.8 → v2.0.1) are the older historical trail, retained rather than duplicated. - **Historical detail — v2.0.8 "Harbor"** (2026-07-09) (the preceding release is v2.9.9 "Ballast"; see the release line above) — the eighth release of the **v2.0.x mobile-finalization train** and the **iOS release candidate** ("Harborlight"), the final release of the iOS finalization window (**v2.0.5 → v2.0.8**). A **host / iOS-only** cut: the cycle-accurate core is **unchanged and byte-identical to v2.0.7** (AccuracyCoin still **141/141, 100.00%**; nestest 0-diff; `#![no_std]` chip stack untouched). It stages the App Store scaffolding for v2.1.0: version-controlled **App Store Connect listing metadata** (`fastlane/metadata/ios/{en-US,es-ES}/`, mirroring the Android tree, files-only), a **dormant App Store `release` lane** in `fastlane/Fastfile` that stages the build + listing but **does not submit** (`submit_for_review: false`) and is **not** CI-wired (the interim channel stays **TestFlight**), and an **App-Review §4.7 self-audit** (no bundled/downloadable ROMs, ownership notice, searchable library, 4+ rating) in `docs/ios-v2.0.8-readiness.md`. Version bump (workspace `2.0.7 → 2.0.8`; iOS `MARKETING_VERSION → 2.0.8`). **No store submission** (that is v2.1.0); screenshots, real signing, the listing upload, and the App-Review submission are the **maintainer / v2.0.9 / v2.1.0** closeout. See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[2.0.8]` + `docs/ios-v2.0.8-readiness.md` + `to-dos/plans/v2.0.5-v2.0.8-ios-finalization-plan.md`. - **Earlier in the train:** **RustyNES v2.0.7 "Harbor"** (2026-07-09) — the seventh release of the **v2.0.x mobile-finalization train** and the **third iOS finalization release** ("Trim"), continuing the iOS window (**v2.0.5 → v2.0.8**). A **host / iOS-only** cut: the cycle-accurate core is **unchanged and byte-identical to v2.0.6** (AccuracyCoin still **141/141, 100.00%**; nestest 0-diff; `#![no_std]` chip stack untouched). It wires the **App Store submission floor** (Apple mandates the **iOS 26 SDK / Xcode 26** for every App Store Connect upload from **2026-04-28**, so the tag-gated iOS CI now selects the newest Xcode 26.x on the runner — a build-SDK pin, non-breaking fallback on older images), **reconciles the deployment target `iOS 15.0 → 17.0`** to match the code's real API floor (`NavigationStack` iOS 16 + `.topBarTrailing` iOS 17, unguarded at 12+ sites — the prior 15.0 was never buildable), and **re-audits `PrivacyInfo.xcprivacy`** against the v2.0.6 crash reporter (no new data type / required-reason API — local-only, backup-excluded, off by default). Version bump (workspace `2.0.6 → 2.0.7`; iOS `MARKETING_VERSION → 2.0.7`). **TestFlight-only** (App Store + AltStore PAL deferred to v2.1.0); on-device profiling + the Xcode-26 archive are a **maintainer / v2.0.9** step. See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[2.0.7]` + `docs/ios-v2.0.7-readiness.md` + `to-dos/plans/v2.0.5-v2.0.8-ios-finalization-plan.md`. - **Earlier in the train:** **RustyNES v2.0.6 "Harbor"** (2026-07-09) — the sixth release of the **v2.0.x mobile-finalization train** and the **second iOS finalization release** ("Parity"), continuing the iOS window (**v2.0.5 → v2.0.8**). A **host / iOS-only** cut: the cycle-accurate core is **unchanged and byte-identical to v2.0.5** (AccuracyCoin still **141/141, 100.00%**; nestest 0-diff; `#![no_std]` chip stack untouched), so no accuracy / save-state / determinism number moves. It adds a **new opt-in, privacy-first crash-reporting surface** (off by default — the iOS analogue of the Android v1.8.8 `CrashReporter`, closing the v1.9.9 iOS-applicable deferral): **Settings → Diagnostics** installs an uncaught-`NSException` handler that writes **local** crash logs the user can view + copy in-app — **nothing is uploaded**, so the "Data Not Collected" privacy label is unchanged (EN + ES); the handler re-checks the live opt-in at crash time so opting out stops new logs immediately. It also records the **feature-parity re-verification** of the v1.9.x host features (Game Center, CloudKit save sync, MFi controllers, capture / PiP, accessibility) against the unchanged v2.0.0 bridge surface. Version bump (workspace `2.0.5 → 2.0.6`; iOS `MARKETING_VERSION → 2.0.6`). **TestFlight-only** (App Store + AltStore PAL deferred to v2.1.0); on-device crash-capture verification is a **maintainer / v2.0.9** step. See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[2.0.6]` + `docs/ios-v2.0.6-readiness.md` + `to-dos/plans/v2.0.5-v2.0.8-ios-finalization-plan.md`. @@ -504,7 +504,11 @@ deferred to a v1.4.x follow-up (UI compiles + no-ops on wasm). See ### Release engineering (v1.x) -- [→] **CI: `macos-15-intel` runner sunset — August 2027.** GitHub will +- [x] **CI: `macos-15-intel` runner sunset — August 2027.** *(OBSOLETE, closed + at v3.1.0, records item DOC-05 / CI-05: nothing uses the label. The + `x86_64-apple-darwin` release target was dropped in v1.6.0 (ADR 0009; the + note at `.github/workflows/release.yml:72`), and v3.0.1 moved the remaining + macOS jobs to `macos-15`. The entry is kept as written below.)* GitHub will decommission the `macos-15-intel` label after that date (per `actions/runner-images#13045`). Plan: migrate to `cargo-zigbuild` cross-compile from Linux, or drop `x86_64-apple-darwin` from the @@ -1435,7 +1439,7 @@ Minted from the line plan, [`plans/v3.1-to-v4.0-line-plan.md`](plans/v3.1-to-v4.0-line-plan.md). Each row names the release slot that owns it and the backlog ID from the 2026-10-06 surveys. The release plans hold the gates. An existing ticket is reused, not -re-minted: `T-PS-dual-runahead` (ADR 0032), `T-MISTER-SAVESTATE`, +re-minted: `T-PS-dual-runahead` (ADR 0032; **DONE v3.1.0**), `T-MISTER-SAVESTATE`, `T-MISTER-CHEATS`, `T-MISTER-ZAPPER`, `T-MISTER-4PLAYER`, `T-MISTER-PADDLE`, `T-MISTER-KEYBOARD`, `T-MISTER-VMODE`, `T-MISTER-OSD` and `T-MISTER-DIRECTVIDEO` keep their sections above. @@ -1443,14 +1447,14 @@ re-minted: `T-PS-dual-runahead` (ADR 0032), `T-MISTER-SAVESTATE`, | Ticket | What | Backlog | Slot | | --- | --- | --- | --- | | `T-ECOSYSTEM-WATCH` | Every minor: the wgpu/egui pair, the AccuracyCoin upstream diff, rcheevos, the libretro build image, the Android/iOS policy calendar, the Rust pin | ecosystem survey | every minor | -| `T-LIBRETRO-TOOLCHAIN` | Drop the libretro build's Rust 1.96 pin if a branch pipeline on 1.99 passes all 15 jobs (D5). **Dropped in v3.0.1** (pipeline 119614, 15/15); closes when the first `main` pipeline after merge is green | LR-02 | v3.0.1 | -| `T-ACCURACYCOIN-RESYNC-2610` | Re-sync AccuracyCoin to upstream HEAD (two new tests and a "Misaligned OAM Behavior" fix since 2026-09-19) and triage red first (D25) | ecosystem 7 | v3.1.0 | -| `T-EPOCH-FINGERPRINT` | A committed panel of output hashes that fails CI when it moves without an `EMULATION_EPOCH` rise | CI-02 | v3.1.0 | -| `T-CPU-OVERCLOCK` | The CPU-multiplier overclock, in `HardwareOptions`, movies and netplay (D22) | FE-01 | v3.1.0 | -| `T-SPRITE-LIMIT` | "Disable sprite limit", render-only, carried like the overclock (D22) | FE-02 | v3.1.0 | -| `T-PAL-EMPHASIS` | The PAL/Dendy emphasis red/green swap | ACC-01 | v3.1.0 | -| `T-COMPOSITE-ARTIFACTS` | Differential phase distortion and inter-pixel artifacts, an opt-in video option (D20) | ACC-02 | v3.1.0 | -| `T-MMC3-NEC-OVERRIDE` | The NEC rev B MMC3 as a per-game or config override (D20) | ACC-13 | v3.1.0 | +| `T-LIBRETRO-TOOLCHAIN` | Drop the libretro build's Rust 1.96 pin if a branch pipeline on 1.99 passes all 15 jobs (D5). **Dropped in v3.0.1** (pipeline 119614, 15/15); **CLOSED v3.1.0**: the first `main` pipeline after the merge, 119931, ran 15/15 green | LR-02 | v3.0.1 | +| `T-ACCURACYCOIN-RESYNC-2610` | Re-sync AccuracyCoin to upstream HEAD (two new tests and a "Misaligned OAM Behavior" fix since 2026-09-19) and triage red first (D25). **DONE v3.1.0**: `f5f41dc2`, 146/146; two defects fixed red first (write-refused DMC load DMA, misaligned sprite evaluation, the latter a false pass on the old ROM) | ecosystem 7 | v3.1.0 | +| `T-EPOCH-FINGERPRINT` | A committed panel of output hashes that fails CI when it moves without an `EMULATION_EPOCH` rise. **DONE v3.1.0** (`tests/epoch_fingerprint.rs`, ADR 0045 amendment) | CI-02 | v3.1.0 | +| `T-CPU-OVERCLOCK` | The CPU-multiplier overclock, in `HardwareOptions`, movies and netplay (D22) **DONE v3.1.0** | FE-01 | v3.1.0 | +| `T-SPRITE-LIMIT` | "Disable sprite limit", render-only, carried like the overclock (D22) **DONE v3.1.0** | FE-02 | v3.1.0 | +| `T-PAL-EMPHASIS` | The PAL/Dendy emphasis red/green swap **DONE v3.1.0** | ACC-01 | v3.1.0 | +| `T-COMPOSITE-ARTIFACTS` | Differential phase distortion and inter-pixel artifacts, an opt-in video option (D20) **DONE v3.1.0** (differential phase; the inter-pixel artifacts already existed in the signal-decode pass) | ACC-02 | v3.1.0 | +| `T-MMC3-NEC-OVERRIDE` | The NEC rev B MMC3 as a per-game or config override (D20) **DONE v3.1.0** | ACC-13 | v3.1.0 | | `T-MAPPER-BREADTH-V3` | The missing families with real titles, both cores (D26) | MAP-03, MAP-05, FB-1 | v3.2.0, v3.8.0 | | `T-KNOWN-BLANK` | Triage the 52 KNOWN_BLANK dumps | ACC-08, ACC-09 | v3.2.0 | | `T-CURATED-EVIDENCE` | BestEffort → Curated on local-dump evidence under D23's record | MAP-01 | v3.2.0 | diff --git a/to-dos/mister/IMPLEMENTATION_PLAN.md b/to-dos/mister/IMPLEMENTATION_PLAN.md index 67204181a..da2dd41fd 100644 --- a/to-dos/mister/IMPLEMENTATION_PLAN.md +++ b/to-dos/mister/IMPLEMENTATION_PLAN.md @@ -31,7 +31,7 @@ complete core; the phase table below is in the new order. The narrative for the | Phase | Release slot | Content | Decisions | |---|---|---|---| -| **S** (submission prep, docs only) | v3.1.0, then kept current through to H | SUB-1 (a dated `ref-docs/` record of the live contribution page; it changed on 2026-09-26), SUB-2 (re-scope the checklist), SUB-5 (refresh `submission-case.md`); TL-5 (this file, done) | D10, D14 (the RTL keeps its long comments) | +| **S** (submission prep, docs only) | v3.1.0, then kept current through to H | SUB-1 (a dated `ref-docs/` record of the live contribution page; rewritten 2026-09-19/20, last edited 2026-09-26; **done v3.1.0**, `ref-docs/2026-10-07-mister-core-contribution-requirements-update.md`), SUB-2 (re-scope the checklist), SUB-5 (refresh `submission-case.md`); TL-5 (this file, done) | D10, D14 (the RTL keeps its long comments) | | **F1** (cheap breadth) | v3.2.0 | FB-16 options first (custom palette, +8 sprites), FB-2 SUROM/SXROM, the 206 family, 66, 11, 79, 9/10, 118/119, 71/232, 34, the trivial discretes, FB-10 paddle, FB-8 Four Score, FB-6 cheats | D12, D26 | | **F2** (the memory platform) | v3.3.0 | FB-20 arbiter (RTL-9 closes), DDR3, FB-4 save states, FB-5 rewind, the real `hps_io` under Verilator; **the off-die build becomes the headline** and on-die a "lite" build | D4, D12, D16 | | **F3** (big boards, audio) | v3.4.0-v3.5.0 | MMC2/4, FME-7/5B, VRC2/4, the Zapper (v3.4.0); MMC5, N163, VRC6, VRC7, Bandai FCG with expansion audio, Famicom peripherals (v3.5.0) | D15 | diff --git a/to-dos/mister/TASKS.md b/to-dos/mister/TASKS.md index 77d7faffa..2bef78faf 100644 --- a/to-dos/mister/TASKS.md +++ b/to-dos/mister/TASKS.md @@ -181,10 +181,15 @@ Legend: `[ ]` open · `[~]` in progress · `[x]` done incompatible by ~17x**, with no independent oracle to adjudicate: risk 6 arriving as a measurement. The constant stays the oracle's, labelled fitted, in a `localparam` - - [ ] A purpose-built decay stimulus, to close the three mutations that are + - [x] A purpose-built decay stimulus, to close the three mutations that are inert **on this ROM** (which timer gates which bits, and where the deadline sits inside a 1.27 s gap). Named as a step, not a silence — and - note it would confirm this core matches 600 ms, not what hardware does + note it would confirm this core matches 600 ms, not what hardware does. + **Done at v2.9.5, ticked at v3.1.0 (RTL-5):** `ppudecay075` (mkrom + program 75) catches all three mutations, 22,829 / 22,829 / 71,428 + cycles (sibling `docs/rung3-ppu.md`, "v2.9.5: the purpose-built ROM + exists"). The box stayed open for four releases after its work landed, + which is why v3.1.0 planned it again - [x] **`rtl/nes_top.sv` assembles the console** and carries **not one observation port** — everything the gates read is reached hierarchically from the co-simulation wrapper, which is legal in @@ -287,6 +292,15 @@ Legend: `[ ]` open · `[~]` in progress · `[x]` done **The version number is struck**: this said "v2.6.7", and v2.6.7 shipped "Detent" instead. Seven releases have now passed the slot, so naming one is a prediction rather than a plan. **Unblocks on hardware.** +- [ ] **The `$2006` copy delay, measured on a board** (v3.1.0, sibling ledger + 3.50). Since v3.1.0 the core delays the `v <- t` copy 3 dots when the + second write lands in a rendering line's background-fetch window and 1 + dot elsewhere, because the oracle does; NESdev documents a constant "1 to + 1.5 dots". On the board: run the sibling's `ppu2006pipe080` stimulus (mkrom + program 80) and compare the picture, or a logic-analyser capture of the + PPU address pins, against the core's fetch trace. If they differ, fix the + oracle first and move the pin. **BLOCKED — no board**, with the bring-up + above. ## Rung 7 — memory and mappers diff --git a/to-dos/mister/contribution-checklist.md b/to-dos/mister/contribution-checklist.md index 2abe8f25f..6eb60b707 100644 --- a/to-dos/mister/contribution-checklist.md +++ b/to-dos/mister/contribution-checklist.md @@ -2,7 +2,18 @@ Every line traces to `ref-docs/2026-08-23-mister-core-contribution-requirements.md`, which quotes the -MiSTer-devel wiki fetched 2026-08-23. **Nothing here is from memory.** +MiSTer-devel wiki fetched 2026-08-23, **and, from v3.1.0, to +`ref-docs/2026-10-07-mister-core-contribution-requirements-update.md`**, which +records the page as rewritten on 2026-09-19 and 2026-09-20 (revision `317b50e`, +2026-09-26, is the live one). **Nothing here is from memory.** + +**Re-scoped at v3.1.0 (SUB-2).** The rewrite dropped "preservation value", the +AI-code sentence ("evidence of quality and accuracy testing"), the repository +transfer, and the Cores-list step, and added four reviewer criteria: the +guidelines followed, the developer understands the code, will maintain it, and +collaborates. Boxes that traced to a removed requirement are kept with their +history and re-scoped in place rather than deleted, so the record of why each +one was there survives. The whole list must be complete **by the hardware-verification release (v3.x)**, which is the submission ([ADR 0041](../../docs/adr/0041-hardware-release-is-v3.0.0.md) moved it from v2.7.0 to v3.0.0 on 2026-09-22; [ADR 0043](../../docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) moved it after v3.0.0 on 2026-09-29, when v3.0.0 became the API major with an unverified release-candidate core); it is NOT complete now, and an item left unchecked below is carried deliberately rather than overlooked. Individual items are marked with the release that settled them -- **(now)** for ones true @@ -201,7 +212,10 @@ Settled at **v2.6.6**, except the one item that needs a board. ## Quality bar - [x] Core is accurate enough to demonstrate **preservation value** **(v2.6.14)** - — measured rather than asserted, and by two independent surfaces. The + — **no longer a stated requirement (v3.1.0):** the 2026-09-20 rewrite + removed the criterion. The evidence below is kept because the page still + asks for code that is "properly tested", and this is the testing. Measured + rather than asserted, and by two independent surfaces. The AccuracyCoin status vector is identical to the oracle's **entry for entry across all 146 entries**, with 146 of 146 executed on both sides and none `NotRun` (v2.6.5). Six commercial titles render **byte-identically over @@ -220,9 +234,16 @@ Settled at **v2.6.6**, except the one item that needs a board. attached to this machine, confirmed by checking the USB bus, serial devices, removable block devices and mounts rather than assumed. Nothing in this repository can close it. **Unblocks on hardware.** -- [ ] **AI-generated-code bar:** readability, plus *"evidence of quality and - accuracy testing"* — the co-simulation record is that evidence, and the - submission should link it explicitly rather than assume a reviewer finds it +- [ ] **The reviewer's criteria: properly tested, and a developer who + understands the code, will maintain it and collaborates.** Re-scoped at + v3.1.0 from the **AI-generated-code bar** (*"evidence of quality and + accuracy testing"*), which the 2026-09-20 rewrite replaced with "Does the + developer understand their code?" and its two companions. The testing + half is what the co-simulation record answers, as before. The other three + are asked of the maintainer, and no document in this repository can + answer them; the long RTL comments (D14), the per-rung documents and the + oracle-vs-documentation ledger are what a reviewer can use to check the + answer. **BLOCKED — on the submission itself.** **The evidence is now also ARGUED rather than merely linkable (v2.6.15):** `RustyNES_MiSTer/docs/submission-case.md` is the document the email will @@ -279,6 +300,13 @@ Settled at **v2.6.6**, except the one item that needs a board. ## Submission +- [ ] The repository is reachable by the reviewer: the page asks for "a link + to your repo", and `RustyNES_MiSTer` is private + **BLOCKED — on a maintainer decision** (v3.1.0). The 2026-09-20 rewrite + dropped the word "public" from the guidelines, but a reviewer still has to + open the link. Making the repository public, or granting the reviewer + access, is the maintainer's call and the last step before the email. + Every item here is **BLOCKED — the submission IS the hardware-verification release (v3.x)** (ADR 0043; it was v3.0.0 under ADR 0041), by the programme's own definition, and three of the four are somebody else's action rather than this @@ -288,15 +316,21 @@ outstanding work. - [ ] Email `newcores@misterfpga.org` with the repository link **BLOCKED — the hardware-verification release (v3.x).** Sending it before the quality bar closes is the whole thing the checklist exists to prevent. -- [ ] Await review (the page says days) +- [ ] Await review (the page said days; since 2026-09-20 it says "upwards of a + month in some situations") **BLOCKED — not ours to do**, and it follows the email. -- [ ] **Decide deliberately** on the MiSTer-devel invitation and repository - transfer — acceptance moves the repo, it is one-way, and this project owns it +- [ ] **Decide deliberately** on what a positive review asks for — until + 2026-09-19 the page said an invitation to MiSTer-devel and a repository + transfer, a one-way move of a repository this project owns **BLOCKED — on being accepted**, and then it is a maintainer decision - rather than a task. Named here so acceptance does not arrive as a - surprise with a one-way consequence attached. + rather than a task. The rewrite no longer describes the transfer; a + review ends in "an email back with the decision and next steps". Kept so + whatever those steps are does not arrive as a surprise with a one-way + consequence attached. - [ ] Add to the Cores list with the Home folder - **BLOCKED — on acceptance.** The Home folder itself is already settled: + **CONTINGENT — on the next steps a positive review names.** The step is + gone from the page since 2026-09-19 (v3.1.0), so it may no longer be the + submitter's to do. The Home folder itself is already settled: `CONF_STR`'s first field gives `/media/fat/games/RustyNES`, and it is unique — the incumbent core's internal name is `NES` (v2.6.7). @@ -314,6 +348,12 @@ Not a failure path — a planned one. See **CONTINGENT — on being declined.** The design decision that keeps it cheap is already taken and holds today: `nes_top.sv` carries no MiSTer framework dependency, and `emu.sv` is the only file that does. +- [ ] A drop-in downloader database (`downloader_.ini`, from the + `DB-Template_MiSTer` template), the route the page itself offers outside + MiSTer-devel, alongside asking Jotego, Coin-Op Collection or theypsilon + **CONTINGENT — on being declined, or on choosing it instead** (v3.1.0). + It reaches `update` and `update_all` users with no review at all, so it + is also open to the maintainer before or without a submission. - [ ] The co-simulation evidence is publishable on its own terms regardless **DECIDED — this is a statement, not a task**, and it is unconditional: the ladder, its goldens and its mutation records stand whatever any diff --git a/to-dos/plans/v2.0.x-mobile-finalization-plan.md b/to-dos/plans/v2.0.x-mobile-finalization-plan.md index f1864d6c1..d6fd771de 100644 --- a/to-dos/plans/v2.0.x-mobile-finalization-plan.md +++ b/to-dos/plans/v2.0.x-mobile-finalization-plan.md @@ -60,7 +60,7 @@ Nothing freezes. Between now and v2.0.0: - **Android (v1.8.x):** keep cutting **GitHub-sideload** releases. Finish the **v1.8.9** batch (the side-pane / foldable UX fix + the held docs) and run the **batched on-device verification pass** (all v1.8.6–v1.8.8 items, see - [`../v1.8.x-on-device-verification.md`](../v1.8.x-on-device-verification.md)). + [`../../docs/mobile-v2.9.3-run-sheet.md`](../../docs/mobile-v2.9.3-run-sheet.md) (the v1.8.x checklist, folded in at v3.1.0)). The PGS / Play-Integrity / Chromecast features stay **default-off behind their build flags** (`PGS_ENABLED`, `PLAY_INTEGRITY_ENABLED`, `CHROMECAST_ENABLED`) — they are flipped on only at **v2.1.0**, not now. @@ -212,7 +212,7 @@ rationale + mechanics in **ADR 0025**. Work items (sequence within v2.0.1–v2.0 the only distribution channels until then. - The **batched on-device verification pass is a hard gate** on the v2.1.0 production launch (it already gated the deferred Play launch; it now gates the - joint launch). See [`../v1.8.x-on-device-verification.md`](../v1.8.x-on-device-verification.md). + joint launch). See [`../../docs/mobile-v2.9.3-run-sheet.md`](../../docs/mobile-v2.9.3-run-sheet.md) (the v1.8.x checklist, folded in at v3.1.0). ## Cross-references updated for this plan @@ -221,6 +221,6 @@ rationale + mechanics in **ADR 0025**. Work items (sequence within v2.0.1–v2.0 - [`v1.8.0-android-plan.md`](v1.8.0-android-plan.md) / [`v1.9.0-ios-plan.md`](v1.9.0-ios-plan.md) — launch-half superseded banners - [`v1.9.x-ios-train-plan.md`](v1.9.x-ios-train-plan.md) — the full v1.9.0–v1.9.9 iOS TestFlight train (finalized here in v2.0.5–v2.0.8) - [`v2.0.0-master-clock-plan.md`](v2.0.0-master-clock-plan.md) — "what follows v2.0.0" -- [`../v1.8.x-on-device-verification.md`](../v1.8.x-on-device-verification.md) — gating sequence +- [`../../docs/mobile-v2.9.3-run-sheet.md`](../../docs/mobile-v2.9.3-run-sheet.md) (the v1.8.x checklist, folded in at v3.1.0) — gating sequence - [`../DEFERRED-AND-CARRYOVER-FEATURES.md`](../DEFERRED-AND-CARRYOVER-FEATURES.md) — the launch deferral - `CLAUDE.md`, `docs/STATUS.md`, `docs/android.md` — milestone sequence diff --git a/to-dos/plans/v3.1.0-plan.md b/to-dos/plans/v3.1.0-plan.md index a6ed63983..674bd78a2 100644 --- a/to-dos/plans/v3.1.0-plan.md +++ b/to-dos/plans/v3.1.0-plan.md @@ -69,7 +69,7 @@ on the v3.0.1 tree, and a mutation that reverts the fix and is caught. | 3.1-S3 | Items 6, 7 and 8: emphasis, composite artifacts, the NEC override | items 6-8 | — | | 3.1-S4 | Item 9: dual-mode rewind and run-ahead | item 9 | — | | 3.1-S5 | Items 12-16: the sibling, in parallel with S2-S4 (it touches no oracle file) | items 12-16 | — | -| 3.1-S6 | Items 10 and 11, the ecosystem watch, the release cut | full gates; ladders; release notes listing every format break (D2) | — | +| 3.1-S6 | Items 10 and 11, the ecosystem watch, the release cut | full gates; ladders; release notes listing every format break (D2), and stating plainly that every earlier AccuracyCoin 100% included the `Misaligned OAM behavior` failure the old ROM masked | — | ## Risks @@ -88,4 +88,30 @@ on the v3.0.1 tree, and a mutation that reverts the fix and is caught. | # | Outcome | Evidence | | --- | --- | --- | -| — | not started | — | +| 1 | **Done.** Upstream `f5f41dc2`, 151 rows / 146 scored, **146 of 146**. Two defects fixed red first: the DMC load DMA a write refuses (4 cycles, not 3), and misaligned sprite evaluation (seed at dot 65, a four-byte copy from `m = 3`, the X range test). `Misaligned OAM behavior` had been a false pass on the old ROM (its fail path did not pop its return address, upstream `adacbc23`). `EMULATION_EPOCH` 3, BUS section 3. Extractor and `derive_indices.py` read `tblf1`/`tblf2` and fail closed; the mirror ROM is rebuilt. **Sibling consequence (read in `rtl/ppu2c02.sv`, 2026-10-07):** the RTL already seeds evaluation from OAMADDR at the end of the clear and already copies four bytes by a byte counter, but it never applies the X out-of-range `&$FC` (the inverse of the oracle's old defect); the DMC write-refusal path is unread yet. The pin move will move the AccuracyCoin goldens; the RTL needs the X rule (and the DMC rule, if absent), each with a gate that catches its mutant, written from the ROM's comments and nesdev, not from the oracle's Mesen2-derived evaluation region | battery red at errors 9, 3, 6, 7 in turn, each fix green; `misaligned_oam_eval_starts_at_dot_65_copies_four_bytes_and_tests_x` catches 3 of 3 mutants; TriCNES per-cycle diff at the test-9 `STA $5000` | +| 2 | **Done.** `tests/epoch_fingerprint.rs` + `golden/epoch_fingerprint.tsv` (epoch 3, `last_release_epoch` 2): seven panel ROMs, every frame's framebuffer, audio, end RAM, CPU cycles. Fails on moved output while the epoch equals the last release's and refuses that bless; after a rise, a re-bless suffices. ADR 0045 amended; the release cut must set `last_release_epoch` (`docs/agents/ci-and-release.md`) | with the cut simulated: control passes; both of this release's fixes reverted as mutants fail and their bless is refused (table unchanged); epoch 4 without bless fails, with bless passes; all files restored byte for byte | +| 3 | **Done.** `Nes::set_cpu_overclock(1..=4)`: the CPU divider is divided; the APU, DMC, mapper cycle hooks and PPU timers run on "stock steps" from an exact cycle schedule (`overclock_phase`, `apu_cycle`, BUS section 3; integer division made PAL x3 run x3.2 and Dendy x4 x5 until the PR #594 review); `run_frame`'s budget scales. `x1` byte-identical (epoch gate). Desktop: Settings combo, recorded by movies and matched by netplay (not held at stock like the scanline overclock). Not done: libretro and mobile options (neither exposes the scanline overclock either) | `cpu_overclock.rs` 6/6; mutants: every cycle a stock step (2 tests fail), `apu_cycle` parity flip and dropped debt on restore (snapshot test fails, after moving it to an audible ROM: ny2011 is silent for 300 frames and made it blind) | +| 4 | **Done.** Render-only extras: up to 56 sprites fetched after the eighth real fetch, drawn behind the hardware eight, PPU snapshot v13. `Mapper::chr_reads_are_pure` (5 impure boards found by scanning every `ppu_read` body; pinned for every constructible id against `save_state`). Desktop checkbox now live | `sprite_limit.rs` 5/5 (RAM, audio, cycles identical frame by frame; blargg sprite_overflow 1-5 pass with it on); stimulus built in the test (spritecans never exceeds 7 per line, NEStress's 62 are transparent); mutants: extras not drawn, extras dropped on restore, MMC2 / J.Y. ASIC claiming purity, all caught | +| 5 | **Done.** `.rnm` format 6 (minimum 6), netplay protocol 7 (`"RNE7"`, same `Sync` layout; `"RNE6"` refused naming its epoch); ADR 0044 amended | `a_format_5_movie_is_refused_as_too_old`, `a_protocol_6_peer_is_refused_as_another_version_naming_its_epoch` | +| 6 | **Done.** `emit_pixel` forms the emphasis index as the physical tint, exchanging PPUMASK bits 5/6 off NTSC (NESdev "Colour emphasis"), for both framebuffers. NTSC unchanged; the sibling has no PAL | `pal_and_dendy_swap_the_red_and_green_emphasis_bits` red first (PAL gave index 97, red, for bit 5) | +| 7 | **Done (scoped).** The inter-pixel colour artifacts were already reproduced by the raw signal-decode pass (v2.1.9); added the missing differential phase distortion there, as an opt-in knob (`diff_phase`, deg/row, default 0) in `signal_decode.wgsl`. Desktop only; mobile/web write 0; the Bisqwit CPU filter does not model it | `crt_stack_shaders_parse_and_validate` (naga, the wgpu front end) passes on the changed shader; the param test pins the knob and its 0 default. NOT shown on screen by an automated test: no GPU-output test exists for this pass, so the visual check is the maintainer's | +| 8 | **Done.** `Mapper::set_mmc3_revision_override`, `Nes` / `HardwareOptions` (`mmc3_revision`), re-applied after a power-cycle rebuild; desktop `[emulation] mmc3_irq_revision`. Running `mmc3_test_2/6` under it FOUND a defect: the alternate revision did not assert on a `$C001` reload to 0 ("IRQ should be set when reloading due to clear"), which MMC3.md documents; fixed, Sharp unchanged | `mmc3_test_2_6_passes_under_the_alternate_revision_override` (and 5 then fails, proving the override acts); `the_mmc3_override_survives_a_power_cycle_and_clears`; all 20 MMC3 tests pass. Two older tests pinned the opposite (NEC silent on the reload) and were corrected with the wiki's text: `nec_asserts_once_on_a_c001_reload_to_zero_and_not_after`, and `mmc3_submapper_4_is_nec_and_0_is_sharp` now expects the single IRQ | +| 9 | **Done.** `VsDualSystem` owns a rewind ring of whole RVSD containers (framebuffers included), `restore_quiet` keeps it, `restore` and `power_cycle` empty it; `RunAhead::run_cabinet_ahead` / `finish_cabinet`; `produce_dual_frame` routes `rewind_held` and the run-ahead depth; the desktop sizes the ring from `[rewind]` (`rewind_budget`, one definition replacing three copies). ADR 0032's amendment gained an outcome note | `vs_dualsystem_rewind.rs` (6 tests, on a cart whose screens change every frame; mutants: a slim sub block in the capture, a loud restore in the step back, both caught); `cabinet_runahead_matches_a_plain_run_on_both_screens` at n = 1, 2 (mutants: no rollback, capture left on, both caught); `a_cabinet_rewinds_and_runs_ahead_through_the_produce_path` (mutants: rewind ignored, run-ahead ignored, both caught) | +| 10 | **Done.** DOC-01..DOC-09 below, each checked against the code; the v1.8.x Android checklist folded into `docs/mobile-v2.9.3-run-sheet.md` as rows G1-G17 (only the rows nothing there covered; one row dropped as now wrong) and deleted, its three live links repointed. Also: the CHANGELOG now says plainly that every earlier AccuracyCoin 100% included the masked `Misaligned OAM behavior` failure | the table below | + +## Records item 10: DOC-01..DOC-09 + +Copied from the 2026-10-06 backlog survey, which lived only in a session +transcript until now, with the v3.1.0 outcome of each. + +| ID | Where | Outcome | +| --- | --- | --- | +| DOC-01 | `to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md` | R1's box and §7 were already reconciled on 2026-10-07; §6 annotated (R1 shipped v3.0.0, the CPU overclock v3.1.0, the stale shifter is ACC-04 in v3.3.0) and §10 retargeted | +| DOC-02 | `docs/compatibility.md` | DualSystem presentation (libretro v2.1.10, web and the mobile bridge v2.9.7), 139/141 dated, VRC6 / 5B / N163 audio Landed, the microphone shipped v2.2.0, MMC5 large PRG-RAM done v2.7.2; the open questions re-checked: CRC32 half-answered (`rustynes-gamedb` is CRC32-keyed), region override still open (FE-11, unscheduled) | +| DOC-03 | `docs/mappers.md` open questions | MMC3 default answered (and its "Sharp (MMC3A)" corrected), MMC5 audio answered, pirate boards answered by policy | +| DOC-04 | `docs/nesdev-hardware-emulation-checklist.md` | AccuracyCoin residuals and the MMC3 #3 residual closed; the stale shifter kept as ACC-04; `vrc24test` confirmed still absent from the corpus | +| DOC-05 | `to-dos/ROADMAP.md` | CI-05 closed as obsolete (no Intel macOS target since v1.6.0, ADR 0009); the open-questions placeholder and the forward ticket table were already done on 2026-10-07 | +| DOC-06 | `docs/libretro/UPSTREAM_SYNC.md` | libretro-super#2131 merged 2026-10-06 (`9c08e5e6f3`); docs#1215 open | +| DOC-07 | `VERSION-PLAN.md` "Planned next" | Already correct: the v3.0.1 cut added its row and the v3.1 line; verified, not edited | +| DOC-08 | stale code comments | `rustynes-android/src/gfx.rs` (Bisqwit is implemented), `wasm.rs` (winit build, audio and IndexedDB all exist), `ntsc.rs` (the composite ladder exists), `netplay_ui.rs` (`mesh_net` exists; the desktop does not use it yet, v3.4.0) | +| DOC-09 | `docs/ios-v1.9.9-readiness.md` | A pointer added under the historical text: FDS, NSF and DualSystem shipped on the bridge in v2.9.7 | diff --git a/to-dos/v1.8.x-on-device-verification.md b/to-dos/v1.8.x-on-device-verification.md deleted file mode 100644 index d26ac6273..000000000 --- a/to-dos/v1.8.x-on-device-verification.md +++ /dev/null @@ -1,101 +0,0 @@ -# Android on-device verification checklist - -Status: living checklist · Applies to: the Android app (`android/app`) on every mobile -release train (v1.8.x "Android" → the v2.0.1–v2.0.4 Timebase re-port → the v2.1.0 store -launch). Referenced by -[`plans/v2.0.x-mobile-finalization-plan.md`](plans/v2.0.x-mobile-finalization-plan.md). - -Host CI (`ci.yml`) remains the authoritative accuracy/determinism gate — the emulation -core is byte-identical on ARM, so **AccuracyCoin is proven on the host, never re-measured -on a phone**. `android.yml` proves the mobile host *links* against the NDK toolchain and -that the UniFFI-binding + Gradle-packaging pipeline is intact. This document is the -**manual, human-in-the-loop** pass that the automated gates cannot cover: it confirms the -app behaves correctly on real hardware before a release is cut. Run it on at least one -arm64 device (a phone) and, when a release touches large-screen / TV / foldable code, on a -tablet/foldable/TV as well. - -## How to run - -```bash -# Build + install the default (foss) debug variant on a connected device. -cd android && ./gradlew :app:installDebug # alias -> installFossDebug (ADR 0025) -# The play variant (freemium / Play-Services surface) when verifying that channel: -cd android && ./gradlew :app:installPlayDebug -``` - -Both flavors must be verified before the v2.1.0 launch (ADR 0025): the **`foss`** build -(no ads, no Google SDKs, fully unlocked — the F-Droid / sideload artifact) **and** the -**`play`** build (freemium + the Play-Services surface). - -## Checklist - -### 1. ROM import + library - -- [ ] Import a `.nes` via the Storage Access Framework picker; it appears in the library - and boots. -- [ ] Import a NES 2.0 ROM; header fields (mapper, region) read correctly in ROM info. -- [ ] Box-art auto-match populates a grid entry (network path; skip on `foss` if offline). -- [ ] Drag/again re-open a previously imported ROM from the library — it resumes cleanly. -- [ ] Confirm the picker offers iNES / NES 2.0 only (FDS / NSF are a post-v2.0.0 - carryover; the bridge must not advertise them). - -### 2. Save-state, rewind, battery SRAM - -- [ ] Write a save-state slot; kill + relaunch the app; load the slot — state restores. -- [ ] Hold rewind; the picture runs backward smoothly and resumes forward on release. -- [ ] A cross-platform `.rns` written on desktop loads on device (and vice versa) — - **within the same format epoch**. A pre-v2.0.0 (pre-Timebase) `.rns` must fail to - load with a clean error toast, never a crash (ADR 0028 / ADR 0002). -- [ ] Battery-backed SRAM (e.g. Zelda) persists across an app restart. -- [ ] Load a pre-v2.0.0 `.rnm` movie: it replays and a **"recorded on a pre-v2.0.0 build"** - warning is surfaced (drained from `NesController.drain_warnings`, ADR 0028); a - current-epoch `.rnm` replays with no warning. - -### 3. Controllers - -- [ ] On-screen multi-touch D-pad + buttons register (including diagonals + simultaneous - A+B); haptic tick fires. -- [ ] A hardware controller (MFi / Xbox / DualSense over Bluetooth or USB-OTG) binds to - P1 and maps South=A / West=B / Start / Select / D-pad. -- [ ] P1–P4 hardware controllers each bind to their own port (Four Score path). -- [ ] Controller hot-plug / disconnect mid-game is handled without a crash or stuck input. - -### 4. Audio + interruptions - -- [ ] Audio plays cleanly with no persistent crackle/underrun at steady 60 fps. -- [ ] Incoming call / notification / another media app ducks or pauses correctly; audio - resumes on return (AudioFocus). -- [ ] Headphone plug/unplug does not wedge the audio thread. -- [ ] Background → foreground (home button, recents) pauses and resumes without desync. - -### 5. Determinism smoke (SMB / Zelda) - -- [ ] *Super Mario Bros.* — World 1-1 renders pixel-correct (no SMB3-style flicker); the - status bar, sprites, and scroll are correct through the first screen. -- [ ] *The Legend of Zelda* — the title screen + overworld render correctly; SRAM save + - reload round-trips. -- [ ] Same input sequence from a save-state produces identical results across two runs - (the determinism contract holds on-device; it is the same core the host CI gates). - -### 6. Accuracy figure to match - -The on-device build runs the **same byte-identical core** as the host, so it inherits the -host accuracy figure — currently **AccuracyCoin 139/141 (98.58%)** on the v2.0.1 catalog -(see `docs/STATUS.md`, the authoritative scoreboard). On-device verification does **not** -re-run AccuracyCoin; it confirms the app surface (I/O, input, audio, lifecycle) around that -core is correct. If a device build ever diverges visibly from the host reference frames, -that is a host-shell bug — file it against `rustynes-android` / `rustynes-mobile`, not the -core. - -### 7. Platform polish (as applicable to the release) - -- [ ] Adaptive layout: phone (compact) vs. tablet/unfolded (expanded two-pane) both lay - out correctly; the NES image letterboxes at any aspect. -- [ ] Foldable fold/unfold mid-game survives with no restart (the emulation thread + GL - surface persist). -- [ ] Android TV / leanback: d-pad navigation reaches every control; the app boots to the - TV home banner. -- [ ] Picture-in-Picture keeps gameplay running; the HUD hides in PiP. -- [ ] `foss` flavor only: confirm the merged manifest has **no** `AD_ID` permission and no - Play-Services meta-data, and that Settings shows no Billing / Play-Games / Cast- - framework surface (ADR 0025). From eaea8776ae6921d3f2f31896eb42c45db4293d2f Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Thu, 8 Oct 2026 06:10:29 -0400 Subject: [PATCH 2/4] review(v3.1.0): sync slice B to release head 8af50dc0 (do not merge) Brings this review-only slice up to #594 at 8af50dc0; every path in the slice equals the release head (git diff --quiet checked). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- CHANGELOG.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6d68f4d1..a000128cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,7 +96,9 @@ run-ahead. **Save states, movies and netplay from v3.0.1 are refused.** multiplier is exact on every region: a CPU cycle is 12 master clocks on NTSC but 16 on PAL and 15 on Dendy, which do not divide by 3 or 4, so the overclocked cycle lengths alternate to keep the average exact (a review of - the release PR found PAL x3 running at x3.2 and Dendy x4 at x5). + the release PR found PAL x3 running at x3.2 and Dendy x4 at x5). It and the + sprite-limit option below apply to both consoles of a Vs. DualSystem cabinet + (the same review found the cabinet ignored both). - **"Disable 8-sprite-per-scanline limit" now works** (`T-SPRITE-LIMIT`; it was shown and saved but inert). It draws the dropped sprites behind the eight the console shows, and changes nothing the game can see: the overflow flag, the @@ -110,7 +112,8 @@ run-ahead. **Save states, movies and netplay from v3.0.1 are refused.** - **MMC3 IRQ revision setting** (`T-MMC3-NEC-OVERRIDE`, Settings > Emulation): run any mapper-4 game on the Sharp or the alternate (MMC3A / NEC) chip, for dumps whose header cannot say which. blargg's `mmc3_test_2/6-MMC3_alt` passes - under the alternate setting. Carried in movies and netplay. + under the alternate setting. Carried in movies and netplay. A save state + never changes which revision runs: loading one keeps the current setting. - **Rewind and run-ahead on the Vs. DualSystem cabinet** (`T-PS-dual-runahead`, ADR 0032 amended): both work in two-screen mode, on the whole cabinet, so the two consoles never fall out of step. A step back restores both screens @@ -166,7 +169,7 @@ run-ahead. **Save states, movies and netplay from v3.0.1 are refused.** ### Verification -- `cargo test --workspace --features test-roms --release`: **3,262 passed, 0 +- `cargo test --workspace --features test-roms --release`: **3,263 passed, 0 failed, 11 ignored**. The epoch fingerprint gate passes at epoch 3, now recorded as the released epoch. - AccuracyCoin **146/146** at upstream `f5f41dc2`; `nestest` 0-diff. From 0f79e0bf6ec45898b8e8ae4f71b4f6ae8a867b97 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Thu, 8 Oct 2026 06:45:03 -0400 Subject: [PATCH 3/4] review(v3.1.0): sync slice B to release head c95a8d10 (do not merge) Brings this review-only slice up to #594 at c95a8d10; every path in the slice equals the release head (git diff --quiet checked). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- CHANGELOG.md | 5 ++++- README.md | 2 +- SECURITY.md | 3 ++- docs/STATUS.md | 2 +- docs/agents/ci-and-release.md | 2 +- docs/compatibility.md | 2 +- docs/libretro/UPSTREAM_SYNC.md | 12 ++++++------ docs/mappers.md | 4 ++-- docs/mobile-v2.9.3-run-sheet.md | 7 +++++-- docs/ppu-2c02.md | 4 +++- to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md | 15 +++++++++++---- to-dos/README.md | 3 ++- to-dos/ROADMAP.md | 4 ++-- to-dos/plans/v3.1.0-plan.md | 6 +++--- 14 files changed, 44 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a000128cb..90b652870 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -178,7 +178,10 @@ run-ahead. **Save states, movies and netplay from v3.0.1 are refused.** *Millionaire* (Sachen, mapper 146), which the game database marks PAL, at one checkpoint, from the PAL emphasis fix. It was attributed by running that ROM alone on each v3.1.0 commit, and re-blessed. -- The MiSTer core: LADDER-FILL. +- The MiSTer core: one frozen-tree run of each co-simulation ladder at an + oracle pin on this branch, nothing skipped: on-die 212 passed, 0 failed, + 1 expected failure; off-die 213 / 0 / 1. The core matches the emulator on + all 146 AccuracyCoin entries. ## [3.0.1] - 2026-10-07 - "Mortar" (the open items closed, one game's graphics fixed, the last MMC3 rule exception tested in the MiSTer core, Rust 1.99 everywhere, every unanswered bot review answered, and a roadmap to v4.0.0) diff --git a/README.md b/README.md index 97a755a11..27e1b8884 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

Build Status License: GPL-3.0-or-later Version Rust: 1.99
- AccuracyCoin nestest Mapper families Try in browser
+ AccuracyCoin nestest Mapper families Try in browser
Platform

diff --git a/SECURITY.md b/SECURITY.md index 970347976..66528b90b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -10,7 +10,8 @@ Report against the latest release or `main`. | Version | Supported | Notes | | ------------- | --------- | ----- | | main | Yes | Where fixes land first | -| 3.0.x | Yes | The current line | +| 3.1.x | Yes | The current line | +| 3.0.x | Partial | Fixes are shipped forward into the current line, not backported | | 2.9.x | Partial | Fixes are shipped forward into the current line, not backported | | 2.8.x | Partial | Fixes are shipped forward into the current line, not backported | | 2.7.x | Partial | Fixes are shipped forward into the current line, not backported | diff --git a/docs/STATUS.md b/docs/STATUS.md index 7678a5a55..09cbcb79d 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1883,7 +1883,7 @@ without panicking (no `$6000` status protocol). | `vrc24test` | — | — | — | — | **Skipped (Track B1)**: link rot. AWJ's original forum attachment (id=10017 on forums.nesdev.org/viewtopic.php?p=203716) is auth-walled; the deletion is documented at archive.nes.science. No GitHub mirror found. | | `AccuracyCoin` | 1 | 1 | — | — | 100thCoin / Chris Siebert single-NROM accuracy battery (MIT license, 151 catalog rows across 22 suites: 146 scored tests + 5 visual-only `Power On State` tests sharing `$03FF`; the v2.0.1 upstream re-sync grew the catalog from 144 to 146 rows / 139 to 141 assigned tests, adding the PPU "ALE + Read" and "Hybrid Addresses" tests, and the **2026-09 re-sync** to upstream `69c8860` grew it again to 149 rows / 144 assigned across 22 suites — two new pages, `Advanced Background Evaluation` and `Advanced Sprite Evaluation`, which re-home eleven existing PPU tests that had outgrown `PPU Misc.`, plus three genuinely new tests, and the **v3.1.0 re-sync** to upstream `f5f41dc2` grew it to 151 rows / 146 scored with two new `CPU Behavior 2` tests, `DMA Landing on Write` and `DMC Reload Timing`). Interactive (D-Pad menu); the harness presses `START` to "run all tests on the ROM" then takes two parallel measurements. **(1) Framebuffer decoder** reads the 10×16 on-screen result grid by exact-pixel colour (5-colour palette: `#64A0FF` = pass, `#4F1000` = fail, `#DC834C` = partial-pass, `#4C4C4C` = no-test / not-run, `#FFFFFF` = border); this is the legacy path and has a known grid-stride bug that under-samples by ~31 cells. **(2) RAM-direct decoder** reads each test's result byte from its fixed CPU-RAM address (catalogued from upstream `AccuracyCoin.asm` in `crates/rustynes-test-harness/src/accuracy_coin_catalog.rs` and `tests/roms/AccuracyCoin/SOURCE_CATALOG.tsv` — 151 `(suite, name, addr)` triples, regenerated by `scripts/accuracycoin-build/extract_catalog.py`) and decodes per-test pass/fail/error-code names + per-suite breakdowns. This is the authoritative path. **Current measured pass rate (RAM-direct): 100.00% (146/146, upstream `f5f41dc2`, v3.1.0)** on the default build. The re-sync's ROM found two defects, fixed in the same change: a DMC load DMA refused by a write took three cycles instead of four (`DMA Landing on Write`), and sprite evaluation took its start from OAMADDR at dot 0 instead of dot 65 and dropped a misaligned OAMADDR after an in-range X (`Misaligned OAM behavior`, which the pre-`f5f41dc2` ROM had recorded as a pass because its fail path returned into the test body without popping the return address). Before that, the two upstream PPU tests "ALE + Read" and "Hybrid Addresses" (briefly the only gaps at 139/141 after the v2.0.1 catalog re-sync) were **closed in v2.0.3** by promoting the 2-cycle-ALE PPU fetch model to the unconditional default. The `90.65%`, `84.17%` and the trajectory figures below are historical engine-lineage milestones (the pre-promotion v1.0.0-rc2 / Session-26 era), retained as history. Historical trajectory: `64.03%` (post-D2 baseline) → `67.63%` (post-D3, 7 6502 bus-pattern fixes) → `69.06%` (post-Phase-3 OAM DMA parity fix, +1 strict test flipped) → `69.78%` (post-FSM-fix recovery, +1 sprite-related sub-test flipped as a side-benefit of the `crates/rustynes-ppu/src/ppu.rs` dot-64 reset removal) → `76.98%` (post-Cascade-B DMC DMA scheduler, commit `9b0c81c` — closes all 8 tests in the `APU Registers and DMA tests` suite + 3 net elsewhere as side-benefits; +11 tests flipped) → `78.42%` (post-Cascade-A OAMADDR-during-rendering reset, commit `f29f7ca` — hardware-accurate per nesdev: OAMADDR is reset to 0 during dots 257-320 of every rendered scanline; +2 tests flipped — Sprite overflow behavior PASSES, Sprite 0 Hit advances from error 1 → error 13) → `79.14%` (post-session-7 OAMADDR-walks-during-eval + $4-aligned `$2004` write, commit `c230489` — closes `Address $2004 behavior` with code 16; +1 net flip) → `79.86%` (post-session-7 RMW ABS,X/Y unfixed-address dummy read, commit `32d5b18` — 18 RMW opcodes get the canonical cycle-4 unfixed-address dummy; flips `APU Tests :: Controller Clocking` and advances `Implied Dummy Reads` 2→3 + `Frame Counter IRQ` 6→7 via the SLO $4015,X bracket; +1 net flip) → `82.73%` (post-session-8 BG-pipeline cycle-9 reload + post-emit shift, commit `086ce4d` — fixes the long-standing 1-column BG pixel off-by-one identified in `docs/audit/cascade-a-investigation-2026-05-19.md`; flips `Sprite 0 Hit behavior` + `Sprite overflow behavior` + `Suddenly Resize Sprite` + `$2007 read w/ rendering`; +4 net flips, +2.87pp) → `83.45%` (post-session-24 Controller Strobing M2-low-defer write, Session-24 Phase 3 — deferred `$4016` commit buffer on `LockstepBus` mirrors Mesen2's `NesControlManager::ProcessWrites`; flips `APU Tests :: Controller Strobing` from `[error 4]` to PASS; +1 net flip) → **`84.17%` (post-session-26 Sprint 2 iter 5 Frame-Counter-IRQ split, 2026-05-23 — separates `FrameCounter::irq_flag` ($4015 bit 6 visibility) from `FrameCounter::irq_line_active` (CPU IRQ source driver) so Tests I/J/K/L/M/N/O all PASS without spuriously asserting the CPU IRQ line on inhibited frame-counter cycles; flips `APU Tests :: Frame Counter IRQ` from `[error 19]` to PASS; +1 net flip)**. Session-26 Sprint 2 iter 4 (APU Register Activation OAM-DMA chip-select gate) advanced the same suite's APU Register Activation entry internally from `[error 4]` to `[error 6]` but did not flip the catalog-headline metric. The previous `75.93%` headline reflected the framebuffer decoder's stride bug, not real accuracy. Strict floor in CI is **60%** — see `crates/rustynes-test-harness/tests/accuracycoin.rs::MIN_PASS_RATE`. the v0.9.x 80% target and the v1.0.0 90% gate were both cleared, and the default build measured **100.00% (144/144)** until the v3.1.0 re-sync (146/146 since) — the master-clock core is the default, the former C1 + sub-cycle residuals are closed, and the two v2.0.1 PPU tests were closed in v2.0.3 (see "Accuracy residuals" below). **No open AccuracyCoin gap, as of v2.6.18.** The 2026-09 re-sync opened two, both in the new `Advanced Sprite Evaluation` page. `Misaligned OAM2 Address` ($0495) closed in v2.6.17 by modelling `OAM2Address` as a live counter through sprite fetch. `Frozen OAM2 Increment` ($0493) closed in **v2.6.18**, and the long diagnosis this row used to carry is **RETRACTED in all three of its claims** — it said a `$2001` enable on dot 256 took effect a dot early (all four writes the ROM names are in force from the START of the dot it names), it blamed test 2 or 3 (the failure was test **4**, the false-positive guard, and the ROM omits an `INC ` re-runs them with their original `ready_for_review` payload. That is how #548's ready run was recovered: every job passed on attempt 2. - **A pull request tests Linux only unless its branch is `release/*` (2026-10-03, v2.9.8).** `select matrix` in `ci.yml` gives an ordinary PR `ubuntu-26.04` and `ubuntu-26.04-arm`; macOS and Windows test legs run only on `main`, `release/*` PRs, dispatch and the weekly cron. v2.9.8's release PR was `feat/v2.9.8-cadence`: 36 checks green, merged, then `main` failed on `test (windows-latest)` alone, so Auto Release skipped and the release had no tag. The cause was the checkout, not the code: CRLF on the Windows runner stopped the source-shape tests' split on `"\n#[cfg(test)]\nmod tests {"`, so `every_load_path_installs_a_dual_system_cabinet` counted its own strings (3 against 1). Fixed by `*.rs text eol=lf` in `.gitattributes` (#582, itself on a `release/*` branch so its PR ran Windows). **Name every release PR's branch `release/*`.** -- **A release whose goldens or commercial snapshots moved must raise `rustynes_core::EMULATION_EPOCH` (v3.0.0, ADR 0045).** The epoch is what tells a movie or a netplay peer which emulator *behaviour* it expects. `.rnm` format 5 records it, protocol 6 sends it, and a mismatch is refused, naming both epochs. Forget the bump and a movie recorded on the old behaviour replays its inputs on the new timing, and a mixed-version session desyncs, both silently: the exact failure the epoch was added to prevent. **The trigger is mechanical:** any re-blessed golden, moved `.snap`, or changed AccuracyCoin/blargg/commercial result between the last release and this one. It does not move for byte-identical refactors or performance work, frontend features, or new mapper families (no earlier output exists to differ from). Check it at the release cut, beside the CHANGELOG. **Since v3.1.0 a test enforces it** (`tests/epoch_fingerprint.rs`, ADR 0045 amendment): a moved panel fingerprint fails unless the epoch is above the table's `last_release_epoch`. **At every release cut, set `# last_release_epoch` in `crates/rustynes-test-harness/golden/epoch_fingerprint.tsv` to the epoch being shipped**, in the release commit; skip it and the next release can move output under an unchanged epoch without the gate noticing. +- **A release whose goldens or commercial snapshots moved must raise `rustynes_core::EMULATION_EPOCH` (v3.0.0, ADR 0045).** The epoch is what tells a movie or a netplay peer which emulator *behaviour* it expects. `.rnm` records it (format 5 at v3.0.0, format 6 since v3.1.0), netplay sends it (protocol 6, `"RNE6"`, at v3.0.0; protocol 7, `"RNE7"`, since v3.1.0), and a mismatch is refused, naming both epochs. Forget the bump and a movie recorded on the old behaviour replays its inputs on the new timing, and a mixed-version session desyncs, both silently: the exact failure the epoch was added to prevent. **The trigger is mechanical:** any re-blessed golden, moved `.snap`, or changed AccuracyCoin/blargg/commercial result between the last release and this one. It does not move for byte-identical refactors or performance work, frontend features, or new mapper families (no earlier output exists to differ from). Check it at the release cut, beside the CHANGELOG. **Since v3.1.0 a test enforces it** (`tests/epoch_fingerprint.rs`, ADR 0045 amendment): a moved panel fingerprint fails unless the epoch is above the table's `last_release_epoch`; a probe newly added to the panel is not a moved output and blesses at any epoch (since the PR #594 review). **At every release cut, set `# last_release_epoch` in `crates/rustynes-test-harness/golden/epoch_fingerprint.tsv` to the epoch being shipped**, in the release commit; skip it and the next release can move output under an unchanged epoch without the gate noticing. - **A runner outage made `CI success` green with nothing tested (2026-10-05, the #585 merge, run 37373901726).** No GitHub-hosted runner ever acquired `detect code changes` ("The job was not acquired by Runner of type hosted even after multiple attempts": no runner name, zero steps, cancelled after 15 minutes), so every gate downstream was skipped. The API reported the job `cancelled`, yet the aggregate's `contains(needs.*.result, 'cancelled')` step was itself skipped, so `needs` did not carry that value. `CI success`, `main`'s only required check, then reported success. **Fixed in v3.0.0** with a step that requires `changes` to have succeeded, and `setup` to have succeeded whenever `changes` found code. A docs-only change still passes, because `setup` skips exactly when `code` is false. The CodeQL "Push on main" run lost `Analyze (rust)` to the same outage. **A default-setup CodeQL run cannot be re-run**: the run and job retry endpoints answer 403, and re-applying the default-setup configuration returns the last run's id rather than starting one. The next push to `main` (or the weekly schedule) is what re-analyses. - **HOLD THE AUTO RELEASE BY CANCELLING MAIN'S CI RUN (v3.0.0).** `release-auto.yml` fires on `workflow_run: CI completed` on `main`, requires `conclusion == 'success'`, and tags that run's `head_sha`. When the maintainer asked for a full docs sweep after #586 had merged, `gh run cancel` on the main CI run of the merge commit left it `cancelled`, so no tag or release was made. The sweep then goes in as its own PR, and that PR's green main run is what tags v3.0.0, on the swept commit. Confirm with `gh release view vX` and `git ls-remote --tags origin vX`. The cancelled run's own `CI success` shows red, which is the outage gate working, not a failure. - **THE FIRST MAJOR BUMP FOUND TWO `bump_release.py` BLIND SPOTS (v3.0.0).** (1) Internal path dependencies carried `version = "2.0.0"`, a caret range no 3.x satisfies, so after the bump `cargo update -w` failed to resolve the workspace. No minor or patch bump can see it. `bump_internal_requirements` now moves them on every MAJOR, and run `cargo update -w` in BOTH workspaces (`crates/rustynes-cosim` has its own lock) after any bump. (2) A CHAIN anchor with its own description (`vX "C" released — . Built on ...`, root `ROADMAP.md`) kept the OLD release's description under the new name; CodeRabbit caught "v3.0.0 ... the tenth release of the v2.9.x line". The selftest had that exact shape and asserted only where the insertion landed. A described head now takes `--lead`, and the old text moves into the chain beside its own release. diff --git a/docs/compatibility.md b/docs/compatibility.md index 67c9794f6..73137556a 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -156,7 +156,7 @@ Bros.) boot and render with their correct 2C04 palette. **PlayChoice-10's second-screen instruction menu and its Z80 coprocessor are out of scope** — only the NES-game half runs (with the 2C03 palette). All of the above is gated on `ConsoleType::VsSystem`/`Playchoice10`; a stock `Nes` cart is byte-for- -byte unchanged (AccuracyCoin 100.00% -- 141/141 at the time, 144/144 from v2.6.18 -- plus both ROM oracles byte-identical). +byte unchanged (AccuracyCoin 100.00% -- 141/141 at the time, 144/144 from v2.6.18, both overstated by the older ROM's masked `Misaligned OAM behavior` failure, and 146/146 since v3.1.0 -- plus both ROM oracles byte-identical). Region timing (PAL/Dendy) is validated by automated gates (`ppu_region_constants_match_hardware` in `rustynes-ppu`; `region_timing.rs` in `rustynes-test-harness`). The R1 diff --git a/docs/libretro/UPSTREAM_SYNC.md b/docs/libretro/UPSTREAM_SYNC.md index 27c492326..81119718d 100644 --- a/docs/libretro/UPSTREAM_SYNC.md +++ b/docs/libretro/UPSTREAM_SYNC.md @@ -160,13 +160,13 @@ At this point, you can safely navigate to your repository settings on GitHub and Both forks were synced to upstream `master` (`libretro-super` `a7054054af`, `docs` `36e9222824`) and carry a branch `rustynes-v3.0.0-sync`. -**Status (checked 2026-10-07, v3.1.0 records item DOC-06).** Both were -submitted after the v3.0.0 tag. The `.info` PR, +**Status (checked 2026-10-08, v3.1.0 records item DOC-06).** Both were +submitted after the v3.0.0 tag, and both are **merged**. The `.info` PR, [libretro/libretro-super#2131](https://github.com/libretro/libretro-super/pull/2131), -**merged** on 2026-10-06 (`9c08e5e6f3`). The docs page PR, -[libretro/docs#1215](https://github.com/libretro/docs/pull/1215), is **open**, -with its one review finding fixed and answered. Until v3.1.0 this section said -no pull request was open. +merged on 2026-10-06 (`9c08e5e6f3`). The docs page PR, +[libretro/docs#1215](https://github.com/libretro/docs/pull/1215), merged on +2026-10-08 (`4a0c09f236`), after its one review finding was fixed and answered. +Until v3.1.0 this section said no pull request was open. | repo | branch commit | change | | --- | --- | --- | diff --git a/docs/mappers.md b/docs/mappers.md index 15eea5301..a93c5a690 100644 --- a/docs/mappers.md +++ b/docs/mappers.md @@ -973,7 +973,7 @@ chunked `NSFE` containers; the FDS-style `$5FF6/$5FF7` RAM banking remains defer ## Edge cases and gotchas 1. **MMC1 consecutive-write bug.** Writes on adjacent CPU cycles after the first are ignored — but only the **data** (bit 0): the bit-7 reset is never ignored (`nesdev_wiki/MMC1.xhtml`, "Consecutive-cycle writes"). *Bill & Ted's Excellent Adventure* needs the data half (an `INC` on `$FF` writes a reset, then a `$00` that must be dropped); *Shinsenden* needs the reset half (it sets bit 7 on an `RRA abs,X`'s second write and crashes if that reset is dropped). Until v2.8.2 the oracle filtered the reset too; the MiSTer RTL did not, and was right (RTL audit R-3.5a). Pinned by `a_reset_on_the_cycle_after_a_write_is_never_ignored` and `a_data_write_on_the_cycle_after_a_reset_is_ignored`. -2. **MMC3 IRQ pattern-table revision differences.** MMC3A (Sharp) generates IRQ even with latch = $00; MMC3B (NEC) does not. *Star Trek: 25th Anniversary* requires MMC3A behavior. Default to MMC3A unless NES 2.0 submapper specifies MMC3B (subm. 1) or MMC3C (subm. 2). +2. **MMC3 IRQ revision differences.** The Sharp MMC3B / MMC3C (the default) asserts whenever a clock leaves the counter at 0, so a latch of `$00` fires every scanline; the MMC3A and non-Sharp MMC3B (the alternate revision, `Mmc3Revision::Nec`) assert on a 1 -> 0 decrement and on a `$C001` reload to 0, but not when a counter already at 0 reloads 0 by itself. *Star Trek: 25th Anniversary* requires the Sharp behaviour. NES 2.0 submapper 4 selects the alternate revision (submapper 1 is the MMC6); an iNES 1.0 dump uses the default unless `Nes::set_mmc3_revision_override` forces one (v3.1.0). Until the v3.1.0 review this item called Sharp "MMC3A" and put MMC3B on submapper 1, both wrong. **The counter rule and the IRQ's deferred output (v2.9.9, T-ORACLE-001).** On each filtered A12 rise the counter follows the NESdev MMC3 page exactly: if the reload flag (set by a `$C001` write) is set or the counter is zero, it reloads from `$C000`'s latch and the flag clears; otherwise it decrements. The IRQ then asserts if the counter is zero and IRQs are enabled. On the default chip that is after any of the three paths; on the alternate chip it is only after a decrement. The IRQ line is raised **at the first per-cycle hook after the rise that asserts it**: `notify_a12` sets `irq_assert_pending_next_cycle`, and the next `notify_cpu_cycle` raises the line. Which CPU cycle that is comes from the bus's order: `Cpu::start_cycle` catches the PPU up to the access and then calls `SystemBus::cpu_clock`, which calls the hook, so a rise caught up before the access is raised in its own cycle and one caught up after it (`end_cycle`) from the next. That is the same split the removed `mmc3-m2-phase-irq` feature made from phase data (this paragraph said "one CPU cycle after the rise" for every rise until the #583 review; ADR 0002's 2026-10-05 correction). An `$E000` write in between cancels the assertion still in flight, as it is the same line. Which rises clock the counter is unchanged; only when the line is seen moves. Until v2.9.9 a `$C001` reload asserted only if the write had cleared a non-zero counter (a latch the page does not have), and the IRQ was seen on the cycle of the rise. That latch made `4-scanline_timing` sub-test 2 pass by raising the IRQ a scanline late, which is what failed sub-test 3. With the page's rule and the deferred output, both `4-scanline_timing` ROMs (`mmc3_test`, `mmc3_test_2`) first fail at sub-test 9 instead of 3, and `mmc3_test/5-MMC3` passes; with the PPU's A12 stream corrected (T-MMC3-BG-A12, `docs/ppu-2c02.md` pitfall 4: the background's fetches at the page's dot 324, and a visible line's dot 0 driving the background CHR address except where the odd-frame skip replaced it) they pass all 13 sub-tests. The delay replaced v2.0.0's `mmc3-m2-phase-irq` feature, which deferred only rises seen in the M2-high half of a cycle; it passed the same ROMs because it makes, in effect, the same split. This form takes that split from the order of the catch-up and the per-cycle hook, with no phase data from the bus, so it is kept and the feature is removed. The MMC3 save-state section is version 4 (it carries the in-flight flag); version 3 is refused. ADR 0002 keeps the history of the earlier attempts. 3. **MMC2/MMC4 latch on tile fetch.** PPU calls a "tile fetched" notification with the tile address; mapper switches CHR bank if tile == `$FD` or `$FE`. Used for Punch-Out's character animations. @@ -1031,7 +1031,7 @@ deleted. - **MMC3 default revision** when iNES (no submapper) is detected. *Answered:* the default is Sharp (the "normal" IRQ behaviour); NES 2.0 submapper 4 selects the alternate one (MMC3A and the non-Sharp MMC3B), and since v3.1.0 any - mapper-4 game can be forced either way (`Mapper::set_mmc3_revision_override`, + mapper-4 game can be forced either way (`Nes::set_mmc3_revision_override`, desktop `[emulation] mmc3_irq_revision`, `T-MMC3-NEC-OVERRIDE`). This line used to call the Sharp default "MMC3A", which is the other revision. - **Mapper #5 (MMC5) audio scope.** *Answered:* the two extra pulses and the diff --git a/docs/mobile-v2.9.3-run-sheet.md b/docs/mobile-v2.9.3-run-sheet.md index 65184b8a9..74736a16e 100644 --- a/docs/mobile-v2.9.3-run-sheet.md +++ b/docs/mobile-v2.9.3-run-sheet.md @@ -174,8 +174,11 @@ multi-touch (A13). One row was dropped because it is now **wrong**: "the picker offers iNES / NES 2.0 only". FDS and NSF shipped in v2.9.7 (T1-T7). Its accuracy paragraph ("AccuracyCoin 139/141") is replaced by the rule it stated: the device runs the host's byte-identical core, so AccuracyCoin (146/146 at -v3.1.0) is measured on the host and never on a phone, and a device that -diverges from the host's frames has a shell bug, not a core one. +v3.1.0) is measured on the host and never on a phone. A device that diverges +from the host's frames on the same ROM, from the same initial state, with the +same input stream points at the mobile integration (the shell, the bridge or +the build), not the shared core; if any of the three differ, compare those +first. | # | Platform | Step | Expect | Result | | --- | --- | --- | --- | --- | diff --git a/docs/ppu-2c02.md b/docs/ppu-2c02.md index 94520ee20..1783bb225 100644 --- a/docs/ppu-2c02.md +++ b/docs/ppu-2c02.md @@ -322,7 +322,9 @@ Per `ref-docs/research-report.md` §Sprite evaluation: position, `OAMADDR` stepping by one each, whatever the alignment: a start at `m = 3` copies the last byte of slot `n` and the first three of slot `n + 1` (until v3.1.0 the FSM stopped after one byte there). - - The fourth byte copied is the X position, and it is range-tested like Y. + - The fourth byte copied is evaluated AS the X position, and range-tested + like Y. In an aligned walk it is the sprite's X; from `m = 3` it is slot + `n + 1`'s attribute byte, read in the X position. **X in range: `OAMADDR += 1` only**, so a misaligned walk stays misaligned. **X out of range: `OAMADDR += 1`, then AND with `$FC`**, realigning. Aligned, both give the next multiple of four, so only misaligned OAM sees diff --git a/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md b/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md index d39646ee7..0a0bb7358 100644 --- a/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md +++ b/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md @@ -441,9 +441,10 @@ variant is what would put the first two in front of a user at all.)* All remaining hard-tier accuracy residuals share **one root cause** and converge on the v2.0.0 one-clock + every-cycle-bus-access refactor. They are **outside the -AccuracyCoin oracle** (zero production-ROM impact; AccuracyCoin is an exact -**141/141** on the shipping default core, up from 139/139 when this paragraph was -written — the denominator grew in the v2.0.1 re-sync and v2.0.3 closed the two new +AccuracyCoin oracle** (zero production-ROM impact; AccuracyCoin was an exact +**141/141** on the shipping default core at the time, up from 139/139 when this +paragraph was written, and is 146/146 since v3.1.0, the earlier figures each +including the masked `Misaligned OAM behavior` failure — the denominator grew in the v2.0.1 re-sync and v2.0.3 closed the two new tests). The maintainer's standing decision through v1.7.0 is "keep deferring" point-fixes (ADR 0002 stop-condition; 15+ documented rollbacks); v2.0.0 is the one release licensed to break save-state/determinism and @@ -572,7 +573,7 @@ take this on. See [v2.0.0 plan](plans/v2.0.0-master-clock-plan.md) and ### 6c. Other v2.0-axis items -- `[ ]` **CPU-multiplier overclock** — distinct from the F3 dot-resolution +- `[x]` **CPU-multiplier overclock** — distinct from the F3 dot-resolution scanline-insert overclock (which shipped off-by-default in v1.7.0 beta.1); needs the timebase rewrite. The v1.5.0 "Enhancements" group's **sprite-limit-disable + overclock** controls are **staged-but-inert** pending @@ -596,6 +597,12 @@ take this on. See [v2.0.0 plan](plans/v2.0.0-master-clock-plan.md) and movies and netplay carry them, with one `.rnm` format and protocol bump. The sprite limit is render-only, so the overflow flag and evaluation timing stay exact.)* + *(2026-10-08: **shipped in v3.1.0**, both. `Nes::set_cpu_overclock(1..=4)` + (`T-CPU-OVERCLOCK`) runs the CPU k times faster with the APU, the mapper + counters and the PPU timers at the stock rate, exact on every region; the + sprite-limit option (`T-SPRITE-LIMIT`) draws the dropped sprites behind the + eight the console shows. Both ride in `HardwareOptions` (`.rnm` format 6, + protocol 7) and apply to both consoles of a Vs. DualSystem cabinet.)* - `[x]` **Full Vs. DualSystem dual-core (C)** — *(shipped v2.0.0 "Timebase" beta.5, commit `9fe44a19`: `crates/rustynes-core/src/vs_dualsystem.rs`, `pub enum Emu` (Single / Dual); desktop presentation v2.1.2 (`render_dual` in diff --git a/to-dos/README.md b/to-dos/README.md index 58ebe8dc2..5e47cffe2 100644 --- a/to-dos/README.md +++ b/to-dos/README.md @@ -26,7 +26,8 @@ production polish) → **`v1.1.0` "Scriptable" → `v1.2.0` "Curator" → `v1.3. "Studio" → `v1.7.0` "Forge"** (+ `v1.7.1`) **→ `v1.8.0` … `v1.8.8` "Atlas"** (the Android platform train), and from there through **`v2.0.0` "Timebase"** (the master-clock rewrite, ADR 0002), the v2.x accuracy, platform and audit lines, -to the current release, **`v3.0.0` "Cornerstone"** (2026-10-06, the API major). +through **`v3.0.0` "Cornerstone"** (2026-10-06, the API major), to the current +release, **`v3.1.0` "Bellwether"** (2026-10-08). `to-dos/ROADMAP.md` and `docs/STATUS.md` carry the current state; this file's v1.8.x-era sections below are kept as history. Version markers in the phase bodies that read `v1.x`/`v2.x` are the inbound **engine's** prior lineage diff --git a/to-dos/ROADMAP.md b/to-dos/ROADMAP.md index 0b9369205..31ef07832 100644 --- a/to-dos/ROADMAP.md +++ b/to-dos/ROADMAP.md @@ -59,8 +59,8 @@ v2.8.0 → v0.9.7; the synthesis itself = **v1.0.0**. - **Current release:** **RustyNES v3.1.0 "Bellwether"** — the AccuracyCoin re-sync and an honest 146/146, the CPU overclock and the sprite-limit option in movies and netplay, PAL emphasis, the alternate MMC3, rewind and run-ahead on the Vs. cabinet, and the MiSTer core matched to all of it. Built on **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). **No hardware has run any bitstream.** - **The line after v3.0.0 (drafted 2026-10-07):** [`plans/v3.1-to-v4.0-line-plan.md`](plans/v3.1-to-v4.0-line-plan.md) indexes v3.1.0 → v4.0.0, with one plan file per release and the maintainer's 29 decisions (D1-D29) tabled there. In order (D29 moved the hardware release to the end of v3.9.x the same day): - - v3.0.1 "Mortar" (in progress); - - v3.1.0: options, emphasis, the AccuracyCoin re-sync; + - v3.0.1 "Mortar" (released 2026-10-07); + - v3.1.0 "Bellwether" (2026-10-08): options, emphasis, the AccuracyCoin re-sync; - v3.2.0 to v3.8.0: oracle themes alongside MiSTer phases F1-F4, the MiSTer features verified in simulation; - v3.9.0: the MiSTer RTL feature freeze and RC pair, mobile signing set up, and v4.0 preparation; - the hardware release, the last v3.9.x: the board session on that pair and the mobile device run, then the store listings; numbered after the session, no later than v4.0.0, with no feature RTL; diff --git a/to-dos/plans/v3.1.0-plan.md b/to-dos/plans/v3.1.0-plan.md index 674bd78a2..381f8168f 100644 --- a/to-dos/plans/v3.1.0-plan.md +++ b/to-dos/plans/v3.1.0-plan.md @@ -89,7 +89,7 @@ on the v3.0.1 tree, and a mutation that reverts the fix and is caught. | # | Outcome | Evidence | | --- | --- | --- | | 1 | **Done.** Upstream `f5f41dc2`, 151 rows / 146 scored, **146 of 146**. Two defects fixed red first: the DMC load DMA a write refuses (4 cycles, not 3), and misaligned sprite evaluation (seed at dot 65, a four-byte copy from `m = 3`, the X range test). `Misaligned OAM behavior` had been a false pass on the old ROM (its fail path did not pop its return address, upstream `adacbc23`). `EMULATION_EPOCH` 3, BUS section 3. Extractor and `derive_indices.py` read `tblf1`/`tblf2` and fail closed; the mirror ROM is rebuilt. **Sibling consequence (read in `rtl/ppu2c02.sv`, 2026-10-07):** the RTL already seeds evaluation from OAMADDR at the end of the clear and already copies four bytes by a byte counter, but it never applies the X out-of-range `&$FC` (the inverse of the oracle's old defect); the DMC write-refusal path is unread yet. The pin move will move the AccuracyCoin goldens; the RTL needs the X rule (and the DMC rule, if absent), each with a gate that catches its mutant, written from the ROM's comments and nesdev, not from the oracle's Mesen2-derived evaluation region | battery red at errors 9, 3, 6, 7 in turn, each fix green; `misaligned_oam_eval_starts_at_dot_65_copies_four_bytes_and_tests_x` catches 3 of 3 mutants; TriCNES per-cycle diff at the test-9 `STA $5000` | -| 2 | **Done.** `tests/epoch_fingerprint.rs` + `golden/epoch_fingerprint.tsv` (epoch 3, `last_release_epoch` 2): seven panel ROMs, every frame's framebuffer, audio, end RAM, CPU cycles. Fails on moved output while the epoch equals the last release's and refuses that bless; after a rise, a re-bless suffices. ADR 0045 amended; the release cut must set `last_release_epoch` (`docs/agents/ci-and-release.md`) | with the cut simulated: control passes; both of this release's fixes reverted as mutants fail and their bless is refused (table unchanged); epoch 4 without bless fails, with bless passes; all files restored byte for byte | +| 2 | **Done.** `tests/epoch_fingerprint.rs` + `golden/epoch_fingerprint.tsv` (epoch 3; `last_release_epoch` 2 through development, set to 3 at the cut, `fb8a1243`): seven panel ROMs, every frame's framebuffer, audio, end RAM, CPU cycles. Fails on moved output while the epoch equals the last release's and refuses that bless; after a rise, a re-bless suffices. ADR 0045 amended; the release cut must set `last_release_epoch` (`docs/agents/ci-and-release.md`) | with the cut simulated: control passes; both of this release's fixes reverted as mutants fail and their bless is refused (table unchanged); epoch 4 without bless fails, with bless passes; all files restored byte for byte | | 3 | **Done.** `Nes::set_cpu_overclock(1..=4)`: the CPU divider is divided; the APU, DMC, mapper cycle hooks and PPU timers run on "stock steps" from an exact cycle schedule (`overclock_phase`, `apu_cycle`, BUS section 3; integer division made PAL x3 run x3.2 and Dendy x4 x5 until the PR #594 review); `run_frame`'s budget scales. `x1` byte-identical (epoch gate). Desktop: Settings combo, recorded by movies and matched by netplay (not held at stock like the scanline overclock). Not done: libretro and mobile options (neither exposes the scanline overclock either) | `cpu_overclock.rs` 6/6; mutants: every cycle a stock step (2 tests fail), `apu_cycle` parity flip and dropped debt on restore (snapshot test fails, after moving it to an audible ROM: ny2011 is silent for 300 frames and made it blind) | | 4 | **Done.** Render-only extras: up to 56 sprites fetched after the eighth real fetch, drawn behind the hardware eight, PPU snapshot v13. `Mapper::chr_reads_are_pure` (5 impure boards found by scanning every `ppu_read` body; pinned for every constructible id against `save_state`). Desktop checkbox now live | `sprite_limit.rs` 5/5 (RAM, audio, cycles identical frame by frame; blargg sprite_overflow 1-5 pass with it on); stimulus built in the test (spritecans never exceeds 7 per line, NEStress's 62 are transparent); mutants: extras not drawn, extras dropped on restore, MMC2 / J.Y. ASIC claiming purity, all caught | | 5 | **Done.** `.rnm` format 6 (minimum 6), netplay protocol 7 (`"RNE7"`, same `Sync` layout; `"RNE6"` refused naming its epoch); ADR 0044 amended | `a_format_5_movie_is_refused_as_too_old`, `a_protocol_6_peer_is_refused_as_another_version_naming_its_epoch` | @@ -97,7 +97,7 @@ on the v3.0.1 tree, and a mutation that reverts the fix and is caught. | 7 | **Done (scoped).** The inter-pixel colour artifacts were already reproduced by the raw signal-decode pass (v2.1.9); added the missing differential phase distortion there, as an opt-in knob (`diff_phase`, deg/row, default 0) in `signal_decode.wgsl`. Desktop only; mobile/web write 0; the Bisqwit CPU filter does not model it | `crt_stack_shaders_parse_and_validate` (naga, the wgpu front end) passes on the changed shader; the param test pins the knob and its 0 default. NOT shown on screen by an automated test: no GPU-output test exists for this pass, so the visual check is the maintainer's | | 8 | **Done.** `Mapper::set_mmc3_revision_override`, `Nes` / `HardwareOptions` (`mmc3_revision`), re-applied after a power-cycle rebuild; desktop `[emulation] mmc3_irq_revision`. Running `mmc3_test_2/6` under it FOUND a defect: the alternate revision did not assert on a `$C001` reload to 0 ("IRQ should be set when reloading due to clear"), which MMC3.md documents; fixed, Sharp unchanged | `mmc3_test_2_6_passes_under_the_alternate_revision_override` (and 5 then fails, proving the override acts); `the_mmc3_override_survives_a_power_cycle_and_clears`; all 20 MMC3 tests pass. Two older tests pinned the opposite (NEC silent on the reload) and were corrected with the wiki's text: `nec_asserts_once_on_a_c001_reload_to_zero_and_not_after`, and `mmc3_submapper_4_is_nec_and_0_is_sharp` now expects the single IRQ | | 9 | **Done.** `VsDualSystem` owns a rewind ring of whole RVSD containers (framebuffers included), `restore_quiet` keeps it, `restore` and `power_cycle` empty it; `RunAhead::run_cabinet_ahead` / `finish_cabinet`; `produce_dual_frame` routes `rewind_held` and the run-ahead depth; the desktop sizes the ring from `[rewind]` (`rewind_budget`, one definition replacing three copies). ADR 0032's amendment gained an outcome note | `vs_dualsystem_rewind.rs` (6 tests, on a cart whose screens change every frame; mutants: a slim sub block in the capture, a loud restore in the step back, both caught); `cabinet_runahead_matches_a_plain_run_on_both_screens` at n = 1, 2 (mutants: no rollback, capture left on, both caught); `a_cabinet_rewinds_and_runs_ahead_through_the_produce_path` (mutants: rewind ignored, run-ahead ignored, both caught) | -| 10 | **Done.** DOC-01..DOC-09 below, each checked against the code; the v1.8.x Android checklist folded into `docs/mobile-v2.9.3-run-sheet.md` as rows G1-G17 (only the rows nothing there covered; one row dropped as now wrong) and deleted, its three live links repointed. Also: the CHANGELOG now says plainly that every earlier AccuracyCoin 100% included the masked `Misaligned OAM behavior` failure | the table below | +| 10 | **Done.** DOC-01..DOC-09 below, each checked against the code; the v1.8.x Android checklist folded into `docs/mobile-v2.9.3-run-sheet.md` as rows G1-G18 (only the rows nothing there covered; one row dropped as now wrong; G18, the `play` flavor, restored after the PR #594 review) and deleted, its three live links repointed. Also: the CHANGELOG now says plainly that every earlier AccuracyCoin 100% included the masked `Misaligned OAM behavior` failure | the table below | ## Records item 10: DOC-01..DOC-09 @@ -111,7 +111,7 @@ transcript until now, with the v3.1.0 outcome of each. | DOC-03 | `docs/mappers.md` open questions | MMC3 default answered (and its "Sharp (MMC3A)" corrected), MMC5 audio answered, pirate boards answered by policy | | DOC-04 | `docs/nesdev-hardware-emulation-checklist.md` | AccuracyCoin residuals and the MMC3 #3 residual closed; the stale shifter kept as ACC-04; `vrc24test` confirmed still absent from the corpus | | DOC-05 | `to-dos/ROADMAP.md` | CI-05 closed as obsolete (no Intel macOS target since v1.6.0, ADR 0009); the open-questions placeholder and the forward ticket table were already done on 2026-10-07 | -| DOC-06 | `docs/libretro/UPSTREAM_SYNC.md` | libretro-super#2131 merged 2026-10-06 (`9c08e5e6f3`); docs#1215 open | +| DOC-06 | `docs/libretro/UPSTREAM_SYNC.md` | libretro-super#2131 merged 2026-10-06 (`9c08e5e6f3`); docs#1215 merged 2026-10-08 (`4a0c09f236`) | | DOC-07 | `VERSION-PLAN.md` "Planned next" | Already correct: the v3.0.1 cut added its row and the v3.1 line; verified, not edited | | DOC-08 | stale code comments | `rustynes-android/src/gfx.rs` (Bisqwit is implemented), `wasm.rs` (winit build, audio and IndexedDB all exist), `ntsc.rs` (the composite ladder exists), `netplay_ui.rs` (`mesh_net` exists; the desktop does not use it yet, v3.4.0) | | DOC-09 | `docs/ios-v1.9.9-readiness.md` | A pointer added under the historical text: FDS, NSF and DualSystem shipped on the bridge in v2.9.7 | From d75fc34e88b846d887447a12abe99c44f0f941dd Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Thu, 8 Oct 2026 07:52:15 -0400 Subject: [PATCH 4/4] review(v3.1.0): sync slice B to release head 72747541 (do not merge) Brings this review-only slice up to #594 at 72747541; every path in the slice equals the release head (git diff --quiet checked). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- .github/release-notes/v3.1.0.md | 62 +++++++++++++++++++++++++++++++++ CHANGELOG.md | 2 +- docs/STATUS.md | 2 +- docs/scheduler.md | 2 +- 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 .github/release-notes/v3.1.0.md diff --git a/.github/release-notes/v3.1.0.md b/.github/release-notes/v3.1.0.md new file mode 100644 index 000000000..ec39ad7ae --- /dev/null +++ b/.github/release-notes/v3.1.0.md @@ -0,0 +1,62 @@ +# RustyNES v3.1.0 — "Bellwether" + +The first release of the v3.1 to v4.0 line. AccuracyCoin is re-synced to its newest upstream, and the new ROM showed that every earlier 100% score included a failure the old ROM hid; both defects it found are fixed. Two long-shown options now work and travel with movies and netplay: a CPU overclock and the sprite-limit switch. PAL and Dendy emphasis is right, the alternate MMC3 revision is selectable and correct, the NTSC decode gains differential phase, and the two-screen Vs. cabinet gets rewind and run-ahead. The MiSTer core (RTL-1) now matches the emulator when a `$2006` write meets the PPU's address pipeline. **Save states, movies and netplay from v3.0.1 are refused.** + +## Breaking changes + +- **Save states from v3.0.1 do not load**: BUS section 3 (the DMC write-refusal latch and the overclock's position) and `PPU_SNAPSHOT_VERSION` 13 (the sprite-limit option's pending sprites). +- **Movies from v3.0.1 are refused**: `.rnm` format 6, whose options record carries the CPU overclock, the sprite-limit switch and the MMC3 revision. `EMULATION_EPOCH` rises from 2 to 3, because the two AccuracyCoin fixes change bus cycles and sprite evaluation. +- **Netplay**: protocol 7 (`"RNE7"`). A v3.0.x peer is refused as another emulator version, naming its epoch. + +## AccuracyCoin, and an overstated score + +AccuracyCoin is re-synced to upstream `f5f41dc2`: 151 rows, 146 scored, **146 of 146 pass**. The old ROM had a bug: when `Misaligned OAM behavior` failed part-way, its fail path returned into the test instead of exiting, and the test recorded a pass. Upstream fixed it. On the fixed ROM RustyNES failed parts of that test, and so did every earlier release. **Every AccuracyCoin 100% reported before v3.1.0 (144/144, and 141/141 before it) included that masked failure.** v3.1.0 fixes it, and the DMC load-on-write defect the new `DMA Landing on Write` test found, so this 146/146 is the first without it. + +## Fixed + +- **Misaligned sprite evaluation**: evaluation starts from OAMADDR as of dot 65, copies four bytes from a start at a slot's last byte, and stays misaligned after an in-range X. +- **A DMC load DMA refused by a write takes four cycles, not three.** +- **PAL and Dendy colour emphasis**: PPUMASK bits 5 and 6 swap meaning on the 2C07 and the Dendy, so every PAL game that set emphasis was tinted the wrong way. +- **The alternate MMC3 revision fires on a `$C001` reload to 0**, the single IRQ the NESdev page documents; blargg's `6-MMC3_alt` now passes under it. +- **The pattern viewer and HD packs no longer change the game on MMC2 / MMC4 boards** (Punch-Out!!, Fire Emblem) or three others: their "side-effect-free" CHR read flipped the board's CHR latch. + +## Added + +- **CPU overclock**, 2 to 4 times, against the same picture and sound: the APU, mapper counters and PPU stay at the stock rate. Movies record it; netplay peers must match. +- **"Disable 8-sprite-per-scanline limit" works.** It draws the dropped sprites behind the eight the console shows and changes nothing the game can see. +- **MMC3 IRQ revision** setting (Auto / Sharp / Alternate), carried in movies and netplay. +- **Differential phase** in the raw NTSC decode, display only, off by default. +- **Rewind and run-ahead on the Vs. DualSystem cabinet**, on the whole cabinet, so the two consoles never fall out of step. +- **The emulation-epoch rule is a test** (`T-EPOCH-FINGERPRINT`): output that moves without an epoch rise fails CI. + +## The MiSTer core + +- **Release candidate, not hardware-verified.** No hardware has run either bitstream. +- **It matches the re-synced AccuracyCoin, all 146 entries.** The new ROM found the core's version of both defects: a misaligned sprite evaluation now masks the address after an out-of-range X byte, and a `$4010` write on the DMC timer's reload edge sets that reload's period. The old `Misaligned OAM behavior` sub-test ROM, whose fail path fell through to a pass, is rebuilt from the new source; on it the core without the fix fails. +- **A `$2006` write that meets the PPU's address pipeline matches the emulator** (RTL-1). A new test ROM lands the copy on every dot of a rendering line: the core diverged on 28,129 of 331,838 background fetches and now matches all of them. One of the three rules involved, a copy delay that depends on where in the line the write lands, is the emulator's and is not documented (NESdev says a constant "1 to 1.5 dots"); it is provisional until a board can settle it. +- **30 of 34 AccuracyCoin sub-test ROMs now gate the core**, the other four out for stated reasons, and the core's oracle pin moves to v3.1.0: every golden that changed is attributed, and the other 646 artifacts are byte-identical. +- **The co-simulation ladder is 212 passed, 0 failed, 1 expected failure on-die and 213 passed, 0 failed, 1 expected failure off-die**, each from one run of a frozen tree with nothing skipped: v3.0.1's 200 / 201 plus twelve new gates, and no existing gate moved. +- **New bitstreams, because the fixes are RTL.** Both builds are compiled at fitter seed 1, chosen from eight seeds swept on one build date (261008), every one of which closes on both builds. Each was compiled twice to the same bytes: + - on-die `RustyNES_MiSTer-v3.1.0.rbf`, md5 `4afffd23f6c6d2577c2ead24fa2e6f54` (timing margin +0.255 ns setup, +0.100 ns hold, the largest on-die hold of the eight seeds); + - off-die `RustyNES_MiSTer-v3.1.0-offdie.rbf`, md5 `7b48198cc3b6a822e71ce54e53d2adbc` (+0.188 / +0.001 ns; the SDRAM read +0.449 / +1.184 ns, assuming zero board delay). The off-die build shares the on-die build's seed, and at it closes by one picosecond of hold, its thinnest of the eight. +- The bitstreams carry the sweep's build date, which may be earlier than the release date. +- A self-hosted workflow now runs the whole ladder on the maintainer's runner, on manual dispatch. + +## Records + +The MiSTer contribution page was rewritten in September; the new page and what it means for the submission are recorded, and the checklist re-scoped. Nine stale documents were corrected against the code, and the v1.8.x Android checklist is folded into the mobile run sheet. + +## Verification + +- `--features test-roms`: 3,269 passed, 0 failed, 11 ignored. +- The local commercial suites: `external_real_games` 60/0, `external_extended` 137/0, `external_coverage` 6/0 over 744 staged ROMs. The one moved baseline is *Millionaire* (Sachen), a PAL game whose emphasis colours the PAL fix corrected. +- AccuracyCoin 146/146 at upstream `f5f41dc2`, nestest 0-diff. +- The epoch fingerprint gate passes at epoch 3, now recorded as the released epoch. + +## Install + +- Download the pre-built binaries for Linux, macOS, and Windows below. +- The MiSTer core bitstreams are attached below, as a release candidate, not hardware-verified. The on-die build is `RustyNES_MiSTer-v3.1.0.rbf`, also attached under its datecoded name `RustyNES_20261008.rbf`, and the off-die build is `RustyNES_MiSTer-v3.1.0-offdie.rbf`. +- The WebAssembly build is live at [doublegate.github.io/RustyNES](https://doublegate.github.io/RustyNES/). +- The RetroArch core is in RetroArch's Online Updater on the platforms the libretro buildbot publishes to. +- Licensed under GPL-3.0-or-later. diff --git a/CHANGELOG.md b/CHANGELOG.md index 90b652870..96e49f10a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -169,7 +169,7 @@ run-ahead. **Save states, movies and netplay from v3.0.1 are refused.** ### Verification -- `cargo test --workspace --features test-roms --release`: **3,263 passed, 0 +- `cargo test --workspace --features test-roms --release`: **3,269 passed, 0 failed, 11 ignored**. The epoch fingerprint gate passes at epoch 3, now recorded as the released epoch. - AccuracyCoin **146/146** at upstream `f5f41dc2`; `nestest` 0-diff. diff --git a/docs/STATUS.md b/docs/STATUS.md index 09cbcb79d..d55a9de07 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1883,7 +1883,7 @@ without panicking (no `$6000` status protocol). | `vrc24test` | — | — | — | — | **Skipped (Track B1)**: link rot. AWJ's original forum attachment (id=10017 on forums.nesdev.org/viewtopic.php?p=203716) is auth-walled; the deletion is documented at archive.nes.science. No GitHub mirror found. | | `AccuracyCoin` | 1 | 1 | — | — | 100thCoin / Chris Siebert single-NROM accuracy battery (MIT license, 151 catalog rows across 22 suites: 146 scored tests + 5 visual-only `Power On State` tests sharing `$03FF`; the v2.0.1 upstream re-sync grew the catalog from 144 to 146 rows / 139 to 141 assigned tests, adding the PPU "ALE + Read" and "Hybrid Addresses" tests, and the **2026-09 re-sync** to upstream `69c8860` grew it again to 149 rows / 144 assigned across 22 suites — two new pages, `Advanced Background Evaluation` and `Advanced Sprite Evaluation`, which re-home eleven existing PPU tests that had outgrown `PPU Misc.`, plus three genuinely new tests, and the **v3.1.0 re-sync** to upstream `f5f41dc2` grew it to 151 rows / 146 scored with two new `CPU Behavior 2` tests, `DMA Landing on Write` and `DMC Reload Timing`). Interactive (D-Pad menu); the harness presses `START` to "run all tests on the ROM" then takes two parallel measurements. **(1) Framebuffer decoder** reads the 10×16 on-screen result grid by exact-pixel colour (5-colour palette: `#64A0FF` = pass, `#4F1000` = fail, `#DC834C` = partial-pass, `#4C4C4C` = no-test / not-run, `#FFFFFF` = border); this is the legacy path and has a known grid-stride bug that under-samples by ~31 cells. **(2) RAM-direct decoder** reads each test's result byte from its fixed CPU-RAM address (catalogued from upstream `AccuracyCoin.asm` in `crates/rustynes-test-harness/src/accuracy_coin_catalog.rs` and `tests/roms/AccuracyCoin/SOURCE_CATALOG.tsv` — 151 `(suite, name, addr)` triples, regenerated by `scripts/accuracycoin-build/extract_catalog.py`) and decodes per-test pass/fail/error-code names + per-suite breakdowns. This is the authoritative path. **Current measured pass rate (RAM-direct): 100.00% (146/146, upstream `f5f41dc2`, v3.1.0)** on the default build. The re-sync's ROM found two defects, fixed in the same change: a DMC load DMA refused by a write took three cycles instead of four (`DMA Landing on Write`), and sprite evaluation took its start from OAMADDR at dot 0 instead of dot 65 and dropped a misaligned OAMADDR after an in-range X (`Misaligned OAM behavior`, which the pre-`f5f41dc2` ROM had recorded as a pass because its fail path returned into the test body without popping the return address). Before that, the two upstream PPU tests "ALE + Read" and "Hybrid Addresses" (briefly the only gaps at 139/141 after the v2.0.1 catalog re-sync) were **closed in v2.0.3** by promoting the 2-cycle-ALE PPU fetch model to the unconditional default. The `90.65%`, `84.17%` and the trajectory figures below are historical engine-lineage milestones (the pre-promotion v1.0.0-rc2 / Session-26 era), retained as history. Historical trajectory: `64.03%` (post-D2 baseline) → `67.63%` (post-D3, 7 6502 bus-pattern fixes) → `69.06%` (post-Phase-3 OAM DMA parity fix, +1 strict test flipped) → `69.78%` (post-FSM-fix recovery, +1 sprite-related sub-test flipped as a side-benefit of the `crates/rustynes-ppu/src/ppu.rs` dot-64 reset removal) → `76.98%` (post-Cascade-B DMC DMA scheduler, commit `9b0c81c` — closes all 8 tests in the `APU Registers and DMA tests` suite + 3 net elsewhere as side-benefits; +11 tests flipped) → `78.42%` (post-Cascade-A OAMADDR-during-rendering reset, commit `f29f7ca` — hardware-accurate per nesdev: OAMADDR is reset to 0 during dots 257-320 of every rendered scanline; +2 tests flipped — Sprite overflow behavior PASSES, Sprite 0 Hit advances from error 1 → error 13) → `79.14%` (post-session-7 OAMADDR-walks-during-eval + $4-aligned `$2004` write, commit `c230489` — closes `Address $2004 behavior` with code 16; +1 net flip) → `79.86%` (post-session-7 RMW ABS,X/Y unfixed-address dummy read, commit `32d5b18` — 18 RMW opcodes get the canonical cycle-4 unfixed-address dummy; flips `APU Tests :: Controller Clocking` and advances `Implied Dummy Reads` 2→3 + `Frame Counter IRQ` 6→7 via the SLO $4015,X bracket; +1 net flip) → `82.73%` (post-session-8 BG-pipeline cycle-9 reload + post-emit shift, commit `086ce4d` — fixes the long-standing 1-column BG pixel off-by-one identified in `docs/audit/cascade-a-investigation-2026-05-19.md`; flips `Sprite 0 Hit behavior` + `Sprite overflow behavior` + `Suddenly Resize Sprite` + `$2007 read w/ rendering`; +4 net flips, +2.87pp) → `83.45%` (post-session-24 Controller Strobing M2-low-defer write, Session-24 Phase 3 — deferred `$4016` commit buffer on `LockstepBus` mirrors Mesen2's `NesControlManager::ProcessWrites`; flips `APU Tests :: Controller Strobing` from `[error 4]` to PASS; +1 net flip) → **`84.17%` (post-session-26 Sprint 2 iter 5 Frame-Counter-IRQ split, 2026-05-23 — separates `FrameCounter::irq_flag` ($4015 bit 6 visibility) from `FrameCounter::irq_line_active` (CPU IRQ source driver) so Tests I/J/K/L/M/N/O all PASS without spuriously asserting the CPU IRQ line on inhibited frame-counter cycles; flips `APU Tests :: Frame Counter IRQ` from `[error 19]` to PASS; +1 net flip)**. Session-26 Sprint 2 iter 4 (APU Register Activation OAM-DMA chip-select gate) advanced the same suite's APU Register Activation entry internally from `[error 4]` to `[error 6]` but did not flip the catalog-headline metric. The previous `75.93%` headline reflected the framebuffer decoder's stride bug, not real accuracy. Strict floor in CI is **60%** — see `crates/rustynes-test-harness/tests/accuracycoin.rs::MIN_PASS_RATE`. the v0.9.x 80% target and the v1.0.0 90% gate were both cleared, and the default build measured **100.00% (144/144)** until the v3.1.0 re-sync (146/146 since) — the master-clock core is the default, the former C1 + sub-cycle residuals are closed, and the two v2.0.1 PPU tests were closed in v2.0.3 (see "Accuracy residuals" below). **No open AccuracyCoin gap, as of v2.6.18.** The 2026-09 re-sync opened two, both in the new `Advanced Sprite Evaluation` page. `Misaligned OAM2 Address` ($0495) closed in v2.6.17 by modelling `OAM2Address` as a live counter through sprite fetch. `Frozen OAM2 Increment` ($0493) closed in **v2.6.18**, and the long diagnosis this row used to carry is **RETRACTED in all three of its claims** — it said a `$2001` enable on dot 256 took effect a dot early (all four writes the ROM names are in force from the START of the dot it names), it blamed test 2 or 3 (the failure was test **4**, the false-positive guard, and the ROM omits an `INC