From fa07bee14e8f2cabb6d58a936681418c5ee83404 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Wed, 7 Oct 2026 06:41:01 -0400
Subject: [PATCH 1/5] review(v3.0.1): slice C -- docs, plans and records
Review-only slice of release/v3.0.1 (head fa48dfc5), never merged, stacked
on slice B: docs/, to-dos/ (the v3.1-to-v4.0 roadmap among them), NOTICE and
the top-level Markdown, each equal to the head. After this slice the tree
equals the head except the 62 TriCNES deletions.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
AGENTS.md | 7 +-
ARCHITECTURE.md | 4 +-
CHANGELOG.md | 152 +++++-
CONTRIBUTING.md | 4 +-
NOTICE | 14 +-
OVERVIEW.md | 8 +-
README.md | 12 +-
ROADMAP.md | 6 +-
SECURITY.md | 2 +-
SUPPORT.md | 8 +-
VERSION-PLAN.md | 54 +-
docs/STATUS.md | 6 +-
...0032-vs-dualsystem-desktop-presentation.md | 20 +
...-rustynes-is-permanently-non-commercial.md | 19 +
.../0036-relicense-gplv3-derivative-work.md | 17 +
...pga-core-independent-hdl-implementation.md | 29 +
...-api-major-and-a-release-candidate-core.md | 70 +++
docs/agents/accuracy-oracle.md | 2 +-
docs/agents/ci-and-release.md | 2 +-
docs/agents/libretro.md | 2 +
docs/agents/review-bots.md | 2 +-
docs/agents/tooling-traps.md | 5 +-
docs/ai-emulator-provenance-guardrails.md | 20 +
docs/apu-2a03.md | 2 +-
docs/build-and-tooling.md | 8 +-
docs/cpu-6502.md | 15 +-
docs/dev/BUILD.md | 11 +-
docs/dev/STYLE_GUIDE.md | 2 +-
docs/expansion-audio.md | 2 +-
docs/frontend.md | 34 +-
docs/mappers.md | 2 +-
docs/mister.md | 35 +-
docs/originality-and-provenance.md | 7 +-
docs/performance.md | 30 +-
docs/provenance-failure-postmortem.md | 5 +-
docs/scheduler.md | 13 +-
docs/testing-strategy.md | 2 +-
docs/tooling/oracle-tooling-setup.md | 42 +-
docs/user-guide/getting-started.md | 2 +-
.../Famicom Yarou Vol.1 7-in-1 (Unl).png | Bin 3816 -> 3110 bytes
to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md | 105 +++-
to-dos/ROADMAP.md | 105 +++-
to-dos/libretro/IMPLEMENTATION_PLAN.md | 6 +
to-dos/libretro/SPRINT_PLAN.md | 20 +
to-dos/mister/IMPLEMENTATION_PLAN.md | 83 ++-
to-dos/mister/SPRINT_PLAN.md | 49 ++
to-dos/mister/TASKS.md | 40 +-
to-dos/mister/contribution-checklist.md | 3 +-
.../plans/v2.0.x-mobile-finalization-plan.md | 17 +-
to-dos/plans/v2.6.16-interlock-plan.md | 4 +-
to-dos/plans/v3.0.1-mortar-plan.md | 40 ++
to-dos/plans/v3.1-to-v4.0-line-plan.md | 515 ++++++++++++++++++
to-dos/plans/v3.1.0-plan.md | 91 ++++
to-dos/plans/v3.2.0-plan.md | 46 ++
to-dos/plans/v3.3.0-plan.md | 44 ++
to-dos/plans/v3.4.0-plan.md | 37 ++
to-dos/plans/v3.5.0-plan.md | 38 ++
to-dos/plans/v3.6.0-plan.md | 31 ++
to-dos/plans/v3.7.0-plan.md | 33 ++
to-dos/plans/v3.8.0-plan.md | 34 ++
to-dos/plans/v3.9.0-plan.md | 59 ++
.../plans/v3.x-hardware-verification-plan.md | 101 +++-
to-dos/plans/v4.0.0-plan.md | 52 ++
63 files changed, 2064 insertions(+), 166 deletions(-)
create mode 100644 to-dos/plans/v3.0.1-mortar-plan.md
create mode 100644 to-dos/plans/v3.1-to-v4.0-line-plan.md
create mode 100644 to-dos/plans/v3.1.0-plan.md
create mode 100644 to-dos/plans/v3.2.0-plan.md
create mode 100644 to-dos/plans/v3.3.0-plan.md
create mode 100644 to-dos/plans/v3.4.0-plan.md
create mode 100644 to-dos/plans/v3.5.0-plan.md
create mode 100644 to-dos/plans/v3.6.0-plan.md
create mode 100644 to-dos/plans/v3.7.0-plan.md
create mode 100644 to-dos/plans/v3.8.0-plan.md
create mode 100644 to-dos/plans/v3.9.0-plan.md
create mode 100644 to-dos/plans/v4.0.0-plan.md
diff --git a/AGENTS.md b/AGENTS.md
index 092e93a1a..a49cf998f 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -28,6 +28,7 @@
**This rule outranks everything else in this file.** RustyNES exists because of a real, corrected provenance failure (GPL emulator code was reproduced despite a black-box instruction, then the honest "ported from" comments were scrubbed; the project was relicensed to **GPL-3.0-or-later** and every derived site re-attributed). The full account is `docs/provenance-failure-postmortem.md`; the preventive ruleset is **`docs/ai-emulator-provenance-guardrails.md`** (PDFs of both in `ref-docs/`). **Read the guardrails doc and treat it as binding.** The non-negotiable core:
- **REFERENCE FIREWALL.** Reference emulators (Mesen2, puNES, FCEUX, Nestopia, higan, ares, GeraNES, TriCNES, tetanes, …) are **black-box oracles**. You may run them and read their *output* (framebuffers, traces, audio, logs). You **must not** open, read, quote, or reproduce their **source** (`.c`/`.cpp`/`.h`/`.cs`/`.rs`), constants, tables, variable names, code ordering, or comments — not "for reference," not once. **The local `ref-proj/` reference-emulator clone has been removed from disk and stays gitignored (`/ref-proj/`), so the source is out of reach by design. Do not re-clone it into the working tree.** If you find such source in reach, report that it should be removed; do not read it.
+- **TriCNES IS THE ONE NAMED EXCEPTION (maintainer, 2026-10-07).** It is MIT and written by the AccuracyCoin author, and several of its models are already ported and attributed. Its source may be consulted to troubleshoot an AccuracyCoin test, after rungs 1-3 of the escalation ladder below, and only from OUTSIDE the repository: `~/reference-oracles/TriCNES` (upstream clone at `94f1b117`) and `~/reference-oracles/TriCNES-rustynes-harness` (the instrumented harness), moved out of `crates/rustynes-test-harness/golden/tricnes/` in v3.0.1 so no repository search reaches it. Anything written after reading it is a port: a `// Provenance:` header, a section 1 row and `NOTICE`, in the same change. It covers TriCNES only, and it does not lift the HDL rule below. Terms: `docs/ai-emulator-provenance-guardrails.md` section 3a.
- **THE FIREWALL COVERS HDL TOO (ADR 0037, 2026-08-20).** The v2.4.1 → v2.5.0 "Fabric" line writes a **new** NES core in SystemVerilog. `NES_MiSTer`, `fpganes`, and any other NES `rtl/` are **strict black boxes** on exactly the same terms as emulator source: never opened, read, quoted or transcribed — not the RTL, not its constants, not its module or signal names. **Permitted:** instantiating a third-party core as an opaque testbench module and comparing its *outputs*. **Not permitted:** reading it. Keep those repositories physically outside the workspace, exactly as was done for `ref-proj/`. Anything genuinely unimplementable from documentation escalates to a **new ADR before any source is opened** — this is the rule most at risk of quiet erosion, because the pull toward reading a working core is strongest exactly when the DUT and RustyNES disagree at dot 260 of scanline 241 and nesdev is ambiguous.
- **IMPLEMENT FROM DOCS.** Write hardware behavior from public documentation (`nesdev_wiki/`, `ref-docs/`, datasheets, die studies) and pin it to public test ROMs / golden vectors. Hardware behavior is a fact; the specific *code expression* is copyrighted.
- **IF YOU DERIVE, SAY SO — AND STOP.** If you do port/adapt/closely-model an external source, (1) it is a derivative work under that source's license; (2) attribute it at the site + in `docs/originality-and-provenance.md` §1 + in `NOTICE` + via an SPDX header; (3) the project license must stay compatible (GPL-3.0-or-later) — flag it to the maintainer before proceeding.
@@ -43,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.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). v2.9.2 triaged a fifth, AI-written audit of both repositories (`docs/audits/v2.9.2-full-audit-report.md`, 32 findings) in its ledger, `docs/audits/v2.9.2-full-audit-disposition.md`: 16 fixed red-first, 2 changed but verifiable only on a device (the Swift halves of AUD-10/14), and the rest refuted, declined or deferred with evidence. The sweep written for AUD-02 found that save states dropped the cartridge RAM of twelve board families; `every_board_snapshot_carries_cartridge_ram` now round-trips the RAM of all 174 mapper ids. It changes emulation behaviour in one deliberate place: an unmapped `$4020-$FFFF` read now updates the CPU's internal data bus (AUD-03). Opposing-direction cancel (AUD-08/09/10) is on by default, by the maintainer's decision (2026-09-28). The MiSTer CPU's /NMI edge detector ran on the DMA-stalled enable and lost an NMI raised inside a DMA (AUD-24, gate `dmanmi074`). v2.9.1 fixed `scripts/perf/ab_check.sh` (both sides had built into one target directory) and re-measured the old rejections; the dead NMI detector's per-dot call (−4.1% to −4.7% on palette frames) is removed in v2.9.8, which carries ADR 0042's removals (its 2026-10-01 amendment). The libretro core is built with `panic = "unwind"`, exercised by a C-ABI harness (`crates/rustynes-libretro/src/abi_tests.rs`), and patches a vendored `rust-libretro-sys` (`vendor/`). **AccuracyCoin 144/144 and nestest 0-diff** hold on the v2.9.2 tree, and the full `--features test-roms` suite passes 2,867 tests. v2.7.4's mobile changes, and v2.9.2's Swift, have a device checklist (`docs/mobile-v2.7.4-device-checklist.md`), consolidated at v2.9.3 into `docs/mobile-v2.9.3-run-sheet.md` with an emulator pre-run; the device run itself moved after v3.0.0 (maintainer, 2026-09-29). The co-simulation is **173 passed, 0 failed, 1 expected failure** on-die and **174 / 0 / 1** off-die (`USE_SDRAM=1`), each ONE frozen-worktree ladder run with nothing skipped; both bitstreams compile at fitter seed 2, chosen from eight per build at one build date (on-die +0.510 ns setup / +0.108 ns hold, off-die +0.390 / +0.081; the SDRAM read is +0.447 setup / +1.184 hold), and two clean compiles of each are byte-identical. The SDRAM pin constraints stay provisional until the SuperStation One's memory is read. **No hardware has run any bitstream**. The board session was planned on v2.9.2's bitstreams (`tools/stage_board_kit.sh` in the sibling stages what it needs); the maintainer moved it after v3.0.0 (2026-09-29). That settled what v3.0.0 ships: [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) makes it the API major with a release-candidate core, and hardware verification moves to a later v3.x release (it supersedes [ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)'s hardware-verified v3.0.0). Per-release detail lives in `CHANGELOG.md` and the GitHub releases; it is deliberately not duplicated here.
+**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. 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). v2.9.2 triaged a fifth, AI-written audit of both repositories (`docs/audits/v2.9.2-full-audit-report.md`, 32 findings) in its ledger, `docs/audits/v2.9.2-full-audit-disposition.md`: 16 fixed red-first, 2 changed but verifiable only on a device (the Swift halves of AUD-10/14), and the rest refuted, declined or deferred with evidence. The sweep written for AUD-02 found that save states dropped the cartridge RAM of twelve board families; `every_board_snapshot_carries_cartridge_ram` now round-trips the RAM of all 174 mapper ids. It changes emulation behaviour in one deliberate place: an unmapped `$4020-$FFFF` read now updates the CPU's internal data bus (AUD-03). Opposing-direction cancel (AUD-08/09/10) is on by default, by the maintainer's decision (2026-09-28). The MiSTer CPU's /NMI edge detector ran on the DMA-stalled enable and lost an NMI raised inside a DMA (AUD-24, gate `dmanmi074`). v2.9.1 fixed `scripts/perf/ab_check.sh` (both sides had built into one target directory) and re-measured the old rejections; the dead NMI detector's per-dot call (−4.1% to −4.7% on palette frames) is removed in v2.9.8, which carries ADR 0042's removals (its 2026-10-01 amendment). The libretro core is built with `panic = "unwind"`, exercised by a C-ABI harness (`crates/rustynes-libretro/src/abi_tests.rs`), and patches a vendored `rust-libretro-sys` (`vendor/`). **AccuracyCoin 144/144 and nestest 0-diff** hold on the v2.9.2 tree, and the full `--features test-roms` suite passes 2,867 tests. v2.7.4's mobile changes, and v2.9.2's Swift, have a device checklist (`docs/mobile-v2.7.4-device-checklist.md`), consolidated at v2.9.3 into `docs/mobile-v2.9.3-run-sheet.md` with an emulator pre-run; the device run itself moved after v3.0.0 (maintainer, 2026-09-29). The co-simulation is **173 passed, 0 failed, 1 expected failure** on-die and **174 / 0 / 1** off-die (`USE_SDRAM=1`), each ONE frozen-worktree ladder run with nothing skipped; both bitstreams compile at fitter seed 2, chosen from eight per build at one build date (on-die +0.510 ns setup / +0.108 ns hold, off-die +0.390 / +0.081; the SDRAM read is +0.447 setup / +1.184 hold), and two clean compiles of each are byte-identical. The SDRAM pin constraints stay provisional until the SuperStation One's memory is read. **No hardware has run any bitstream**. The board session was planned on v2.9.2's bitstreams (`tools/stage_board_kit.sh` in the sibling stages what it needs); the maintainer moved it after v3.0.0 (2026-09-29). That settled what v3.0.0 ships: [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) makes it the API major with a release-candidate core, and hardware verification moves to a later v3.x release (it supersedes [ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)'s hardware-verified v3.0.0). Per-release detail lives in `CHANGELOG.md` and the GitHub releases; it is deliberately not duplicated here.
- **Timebase (v2.0.0)** — the scheduler substrate is rewritten from a five-counter dot-lockstep model to a single canonical cycle counter, every CPU cycle clocked in two halves (`start_cycle` / `end_cycle`) with any bus access split between them, and the PPU caught up to each half (ADR 0002 / ADR 0029), now the *only* scheduler path. This is a MAJOR-boundary breaking change (ADR 0003): `.rns` save-state and `.rnm` movie format epochs bump (ADR 0028) — a pre-v2.0.0 `.rns` slot now fails to load with a clear error instead of silently misinterpreting stale bytes. Landed across five betas + rc.1 (PRs #217–223). Also new: core-level **Vs. `DualSystem`** dual-console support (`Emu::Dual`, `crates/rustynes-core`) for the four Vs. arcade cabinet boards — core-and-test-harness-only, frontend wiring deferred. The R1/R2 MMC3 IRQ-timing residual is by-design-deferred beyond this release with a mechanism-level finding recorded in ADR 0002 (not closed, not silently dropped). **AccuracyCoin now measures 141/141 (100.00%)**: the v2.0.1 upstream AccuracyCoin re-sync grew the catalog to 146 rows / 141 assigned tests and briefly opened two new PPU gaps ("ALE + Read" $0491, "Hybrid Addresses" $0492), which **v2.0.3** closed by promoting the 2-cycle-ALE PPU fetch model to the unconditional default (both experimental flags retired; additive `PPU_SNAPSHOT_VERSION` v5 tail). AccuracyCoin held 100% (139/139) throughout the v2.0.0 betas and final cut, dipped to 139/141 under the v2.0.1 re-sync, and is back to a full 141/141 from v2.0.3 onward.
@@ -152,7 +153,7 @@ cargo bench -p rustynes-mappers
cargo bench -p rustynes-core
```
-Toolchain is **Rust 1.96** pinned in `rust-toolchain.toml` (bumped from 1.86 in v1.3.0 to unblock the edition-2024 + egui 0.34 / wgpu 29 / rfd 0.17 dependency tier). CI runs the test job on stable across Linux/macOS/Windows plus an MSRV pin at 1.96 on Linux.
+Toolchain is **Rust 1.99** pinned in `rust-toolchain.toml` (1.86 until v1.3.0, 1.96 until v3.0.1). **The libretro buildbot follows the same pin**: `.gitlab-ci.yml`'s `RUSTUP_TOOLCHAIN` must equal `rust-toolchain.toml`'s `channel`, and CI's `libretro-cross` job fails if they differ, so move both in one change. v3.0.1 first held the buildbot on 1.96.0 (its image passed `-C ar`, a hard error from Rust 1.97), then lifted the hold once a test branch proved the image had dropped the flag (pipeline 119614, all 15 jobs on 1.99.0); every crate now inherits the workspace `rust-version`. A pushed branch gets its own buildbot pipeline, so test a toolchain move there before merging (`docs/agents/libretro.md`). CI runs the tests on the pinned toolchain across Linux/macOS/Windows.
On Linux, anything that pulls in `rustynes-frontend` (which `cargo test --workspace` does) needs the wgpu/winit/cpal system deps:
@@ -237,7 +238,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.0 "Cornerstone"** (2026-10-06). **Never claim any version *later* than v3.0.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.
+- 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.
- **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 b8712c862..33d2d21fb 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -2,7 +2,7 @@
**Document Version:** 2.1.0
**Last Updated:** 2026-08-30
-**Applies to:** RustyNES v3.0.0 (the scheduling model is v2.0.0 "Timebase" onward)
+**Applies to:** RustyNES v3.0.1 (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.
@@ -53,7 +53,7 @@ These cross-cutting choices span many files and are not negotiable without re-de
```text
rustynes/
-├── Cargo.toml # Workspace definition (edition 2024, MSRV 1.96)
+├── Cargo.toml # Workspace definition (edition 2024, MSRV 1.99; libretro path 1.96)
├── crates/
│ ├── rustynes-core/ # Glue: Nes struct, run loop, scheduler, Bus,
│ │ # save state, region config. Re-exports chip crates.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6d46e2109..ba07dda38 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,147 @@ cycle-accurate core later replaced.
## [Unreleased]
+## [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:
+*Famicom Yarou Vol.1*'s CHR-RAM, the MiSTer core's odd-frame A12 exception
+(now reached by a test ROM, which found a real one-cycle defect). It moves the toolchain and every dependency to its
+newest release (the libretro buildbot included, once a test pipeline proved its
+build image no longer blocks it), answers every bot review left unanswered since
+PR #1, settles two provenance questions, and writes the plan from v3.1.0 to
+v4.0.0 from 29 maintainer decisions. **Movies and netplay from v3.0.0 are
+refused**: the mapper 45 fix changes one game's output, so `EMULATION_EPOCH`
+rises to 2 (ADR 0045). Save states are unaffected. The MiSTer bitstreams are
+rebuilt, because the A12 fix is RTL, and remain a **release candidate, not
+hardware-verified**. The maintainer's decisions are in
+`to-dos/plans/v3.0.1-mortar-plan.md` and `to-dos/plans/v3.1-to-v4.0-line-plan.md`.
+
+### Fixed
+
+- **Mapper 45 (GA23C) CHR-RAM is unbanked (T-GA23C-CHRRAM).** *Famicom Yarou
+ Vol.1 7-in-1* now draws its menu instead of noise. Its CHR-RAM is addressed
+ straight from PPU A10-A12, so the MMC3 CHR banks and the outer CHR registers
+ no longer apply to it. The mapper 45 page says nothing about CHR-RAM; the
+ GA23C variant with a ROM/RAM switch (mapper 372) documents its RAM as
+ unbanked. The four mapper 45 carts with CHR-ROM are unchanged.
+- **Provenance: a softened comment restored.** In the Sunsoft 5B mixer
+ (`m069_sunsoft_fme7.rs`), v2.2.5 had reworded "Target, derived from Mesen2"
+ to "calibrated against Mesen2 ... as an oracle", which reads as a black-box
+ comparison. The derivation was always disclosed in the file's `Provenance`
+ header and in `docs/originality-and-provenance.md`, but the site said
+ otherwise; it says "derived from" again (maintainer, 2026-10-06). The ten
+ other site comments that release reworded had already been restored.
+- **Provenance: the shared Bisqwit NTSC pass recorded as derived
+ (T-NTSC-PROVENANCE).** `rustynes-gfx-shaders` called `BISQWIT_WGSL` "an
+ independent implementation ... no third-party emulator code is incorporated",
+ but it is a generated verbatim copy of `ntsc_bisqwit.rs`, whose tables have
+ long been recorded as ported from Bisqwit's C via Mesen2. It now carries a
+ `Provenance` header and its own row in `docs/originality-and-provenance.md`,
+ `NOTICE` names it, and the provenance audit lost the exception that had hidden
+ it (maintainer, 2026-10-07).
+- **Provenance: the TriCNES source moved out of the repository.** The vendored
+ MIT TriCNES trees under `crates/rustynes-test-harness/golden/tricnes/` were
+ removed so no repository search reaches reference-emulator source; the
+ committed cross-diff outputs stay. TriCNES remains the one reference whose
+ source may be consulted, for AccuracyCoin work and always attributed
+ (`docs/ai-emulator-provenance-guardrails.md` section 3a; maintainer,
+ 2026-10-07). `NOTICE` also corrects the last vendored commit to `94f1b117`.
+- **MiSTer core: the dot-0 A12 rule now asks whether cycle 0 was rendering.**
+ T-MMC3-BG-A12's rule (a visible line's cycle 0 drives the background CHR
+ address) was gated on the live rendering state at the two dots where this
+ core shows it, so a `$2001` write taking effect one dot late still applied
+ it, and the MMC3 IRQ came one CPU cycle early. A new generated test ROM
+ (`mapper4mmc3oddskip080`, written to reach the rule's odd-frame exception,
+ which nothing could) found it; the core and the emulator now agree on all
+ 2,978,055 cycles of it. The exception itself is now gated: a new comparison
+ of the cycle each /IRQ rises on catches its mutant, which the bus gate alone
+ could not. The bitstreams are rebuilt for v3.0.1 because this is RTL.
+- **Every unanswered bot review, back to PR #1, answered.** 290 unanswered
+ review threads, review-body findings and Antigravity reviews across both
+ repositories became 473 verdicts; the 80 still valid were fixed, among them:
+ the Bisqwit NTSC filter kept showing the last game frame after a ROM was
+ closed; a mapper-0 override saved from the ROM Database panel vanished on
+ restart; `bump_release.py` could exit 0 with a stale co-simulation lockfile,
+ garble a non-ASCII anchor marker, and append a stray stop after a quote;
+ three release audits could pass on prose they should fail; a review-thread
+ lister reported "0 unresolved" for a malformed response; a perf-log check
+ called a capture with no presentation clock VALID; and Dependabot's titles
+ read `chore(deps)(deps)`, with stale egui holds that blocked every future
+ egui and wgpu update (now a group that moves the five together). The
+ co-simulation crate's checkpoint parser rejects a corrupt stream instead of
+ underflowing, and CI now builds its rustdoc. Every reply is posted and every
+ open thread resolved.
+
+### Changed
+
+- **The plan from v3.1.0 to v4.0.0.** `to-dos/plans/v3.1-to-v4.0-line-plan.md`
+ and one plan per release, written from three research passes (the oracle's
+ backlog, the MiSTer core's, and the outside ecosystem) and 29 maintainer
+ decisions taken on 2026-10-07. v4.0.0 is the remaining public enums made
+ `#[non_exhaustive]` plus MiSTer feature parity. The hardware-verification
+ release (the SuperStation One board session and the mobile device run) moves
+ to the end of the v3.9.x line, so it tests the near-final core, and is
+ numbered after the session. `VERSION-PLAN.md` now lets save-state, movie,
+ netplay and epoch breaks land in any release that says so; a MAJOR is a
+ public Rust API break or a new kind of deliverable (ADR 0043 amendments).
+- **Rust 1.99, everywhere.** The pinned toolchain moves from 1.96.0 to 1.99.0,
+ the newest stable, and the libretro buildbot moves with it. The release first
+ held the buildbot on 1.96.0, because its build image passed `-C ar`, a hard
+ error from Rust 1.97; the image turned out to have dropped that flag on
+ 2026-09-03, and a test branch built on 1.99.0 passed all 15 buildbot jobs,
+ the four Apple ones included. CI's `libretro-cross` job now fails if
+ `.gitlab-ci.yml`'s toolchain differs from `rust-toolchain.toml`. While the
+ split stood, the 1.96 build caught a clippy 1.99 rewrite in `rustynes-core`
+ that only 1.97+ accepts. Rust 1.99's new `extern "C"` variadic definitions do
+ not simplify the RetroAchievements bridge: none of the rcheevos functions it
+ calls is variadic. About 70 new clippy findings were fixed, all exact
+ rewrites (`as_chunks`, `fill`, `assert_eq!` against an empty value so a
+ failure shows the contents).
+- **Every dependency at its newest release.** Crates (`cargo update`; the only
+ holds are forced upstream: `getrandom` 0.2/0.3 by `piccolo` 0.3.3, and
+ `generic-array` 0.14.7 by `crypto-common` 0.1.7). GitHub Actions:
+ `taiki-e/install-action` 2.87.26 (supersedes Dependabot's PR for 2.87.22)
+ and `dtolnay/rust-toolchain` at its current `v1`. Android: `cargo-ndk` 4,
+ NDK r30, Gradle run on Temurin 25, `org.json` 20260814. Web: wasm-opt is
+ now pinned (`version_133`; it was unpinned, so trunk used its built-in
+ `version_123`). Docs build on Python 3.14; Docker images on Rust 1.99 and
+ Debian 13 (the signaling image still named Rust 1.86); `ruff` 0.16.10.
+ macOS jobs move from the deprecated `macos-14` image to `macos-15`.
+- **`EMULATION_EPOCH` is 2.** The mapper 45 fix changes what *Famicom Yarou
+ Vol.1* produces, so movies recorded and netplay peers running v3.0.0 are
+ refused, naming both epochs (ADR 0045).
+
+### Verification
+
+- `cargo test --release --workspace --features test-roms --no-fail-fast`:
+ 3,234 passed, 0 failed, 11 ignored on the release tree (v3.0.0: 3,223 / 0 /
+ 11). `cargo test --workspace`: 2,886 / 0 / 7; the cosim crate 54 / 0.
+ AccuracyCoin 144/144, nestest 0-diff.
+- The local commercial suites (`--features test-roms,commercial-roms`):
+ `external_real_games` 60/0, `external_extended` 137/0, `external_coverage`
+ 6/0. The one moved baseline is *Famicom Yarou Vol.1* (T-GA23C-CHRRAM), which
+ now draws its menu.
+- fmt; clippy for all 18 feature combinations, including `retroachievements`,
+ `full` and both wasm builds; rustdoc `-D warnings`; the `no_std` build; the
+ release audits; markdownlint. The code fixes carry tests, with the mutation
+ that checks each one recorded in its commit body.
+- The libretro buildbot: a test branch on Rust 1.99.0 (pipeline 119614) passed
+ all 15 jobs, the four Apple ones included, before the pin was lifted.
+- The MiSTer core: on-die ladder 200 passed, 0 failed, 1 expected failure,
+ off-die 201 / 0 / 1, each one frozen-worktree run of the final sibling RTL
+ against the oracle pinned at this release branch, nothing skipped. The new
+ odd-frame A12 gate matches all 2,978,055 cycles and the cycle of every /IRQ
+ rise; its three mutants were classified (two caught, the third inert by
+ construction and documented at the site). Both builds were swept at seeds
+ 1-8 on one build date (261007), and every seed closes on both. Seed 2 is
+ pinned (on-die +0.448 / +0.113 ns, off-die +0.401 / +0.096 ns, SDRAM read
+ +0.447 / +1.184 ns), and two clean compiles of each are byte-identical
+ (on-die `7e81a718...`, off-die `88d1dfa5...`). The stuck-register and
+ suppressed-message checks pass on both. That pair ships as a release
+ candidate. **No hardware has run any bitstream.**
+- The Android unit tests run in CI on the release PR; the iOS Swift and the
+ mobile device behaviour are unverified on this Linux host.
+
## [3.0.0] - 2026-10-06 - "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)
The MAJOR release the v2.9.x line prepared for (ADR 0043). v2.9.8 and v2.9.9
@@ -8281,8 +8422,9 @@ against the sweep: five CAUGHT, two NOT CAUGHT and both explained.
This release also carries **v2.4.0 "Concordance"**, which merged to `main` and was never
tagged; entries below marked *(v2.4.0 item)* belong to it. Two further entries — the
-standing release-anchor audit and the deferred-backlog sweep — belong to neither, having
-landed between the two, and are called out where they appear.
+standing release-anchor audit and the deferred-backlog sweep — belong to neither: they
+landed separately in #427, after v2.3.9 and before v2.4.0 merged, and ship in v2.4.1.
+They are called out where they appear.
### Added
@@ -8491,7 +8633,7 @@ landed between the two, and are called out where they appear.
that diagnostic.
- **A standing release-anchor audit — the drift v2.3.9 corrected by hand cannot
- recur silently.** (Landed between v2.4.0 and v2.4.1; part of neither.) `crates/rustynes-test-harness/tests/release_anchor_audit.rs`
+ recur silently.** (Landed separately in #427, after v2.3.9 and before v2.4.0 merged; part of neither, shipped in v2.4.1.) `crates/rustynes-test-harness/tests/release_anchor_audit.rs`
pins **15 anchors across 10 documents** against `[workspace.package] version`:
the README badge and Current Release section, `docs/STATUS.md`, both `AGENTS.md`
anchors plus its "never claim a later version" guard, `VERSION-PLAN.md` (header
@@ -8563,7 +8705,7 @@ landed between the two, and are called out where they appear.
immediately** — the rule that incident produced in the first place.
- **`to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md` swept entry by entry** (landed
- between v2.4.0 and v2.4.1; part of neither), against
+ separately in #427, after v2.3.9 and before v2.4.0 merged; part of neither), against
`main` @ `fdfb2c04`. Eleven entries struck, each carrying its evidence inline —
a file that exists, a workflow line number, a test that says so — rather than a
bare tick, so a closure can be disagreed with.
@@ -9993,7 +10135,7 @@ optimization campaign is closed on the strength of three measured rejections.
where the prediction fits, which a test pins directly. Measured at
`run_ahead = 3` over five paired, Latin-square rounds: convergence **12.12 s →
2.80 s**, frames held for the wrong duration **4.82% → 2.24%**, 5/5 pairs on
- both, exact one-sided sign p = 0.0312. An alternative arm that cleared the
+ both, exact one-sided sign p = 1/32 = 0.03125. An alternative arm that cleared the
produce-cost ring on each depth change converged in 4.0 s and matched on
cadence but produced an audio underrun in **every** capture, and was rejected.
Additive-only (65 insertions, 0 deletions) inside the engage branch, which the
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0165556e6..95c2ad020 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -50,7 +50,7 @@ Areas where help is especially valued:
### Prerequisites
-- **Rust 1.96** (pinned in `rust-toolchain.toml`; `rustup` auto-installs it, including the `wasm32-unknown-unknown` and `thumbv7em-none-eabihf` targets).
+- **Rust 1.99** (pinned in `rust-toolchain.toml`; `rustup` auto-installs it, including the `wasm32-unknown-unknown` and `thumbv7em-none-eabihf` targets).
- **Git**.
- **System libraries** for the `winit` + `wgpu` + `cpal` frontend.
@@ -134,7 +134,7 @@ forbids, so a flaky test is a bug to fix, not to retry.
- **Format:** `cargo fmt` (rustfmt defaults).
- **Lint:** pass `cargo clippy --workspace --all-targets -- -D warnings` with no warnings.
-- **Edition:** Rust 2024. **MSRV:** 1.96 (pinned in `rust-toolchain.toml`).
+- **Edition:** Rust 2024. **Toolchain:** 1.99 (pinned in `rust-toolchain.toml`). **MSRV:** 1.99, except the seven crates the libretro core builds (`rustynes-{cpu,ppu,apu,mappers,core,gamedb,libretro}`), which keep 1.96 for the libretro buildbot; CI checks them on 1.96.
- The chip stack (`rustynes-{cpu,ppu,apu,mappers,core}`) is `#![no_std]` + `extern crate alloc;`. `unsafe` is only permitted at FFI boundaries (`rustynes-cheevos`) and the one native priority hook in `rustynes-frontend`, and **must** carry a `// SAFETY:` comment explaining the invariant.
- No emojis in code, comments, or commits (project policy).
diff --git a/NOTICE b/NOTICE
index 95a6f7965..a76f68916 100644
--- a/NOTICE
+++ b/NOTICE
@@ -120,11 +120,14 @@ Incorporated third-party components (permissively licensed, GPL-compatible)
test-driven accuracy"); its
PPU address/data-multiplex (ALE / octal-latch), OAM-corruption, per-cycle
DMA-dispatch and DMC-DMA state models are ported into RustyNES
- (crates/rustynes-ppu, rustynes-cpu, rustynes-core, rustynes-apu), and its full source is vendored as a golden
- oracle at crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/.
+ (crates/rustynes-ppu, rustynes-cpu, rustynes-core, rustynes-apu). Its full
+ source was vendored as a golden oracle under
+ crates/rustynes-test-harness/golden/tricnes/ until v3.0.1, when it moved
+ outside the repository (the committed cross-diff outputs remain).
https://github.com/100thCoin/TriCNES
- Ported models were taken from commit 9199870; the VENDORED oracle is commit
- f388af0b (re-synced 2026-09-11). The two are stated separately on purpose --
+ Ported models were taken from commit 9199870; the last VENDORED oracle was
+ commit 94f1b117 (re-synced 2026-09-19; this line said f388af0b until v3.0.1,
+ one re-sync stale). The two are stated separately on purpose --
one records what was derived from, the other what is in the tree, and this
entry previously gave one id for both after the vendored copy moved on.
Copyright (c) 2025 Chris Siebert -- MIT
@@ -222,7 +225,8 @@ Video shaders and NTSC filters
--------------------------------------------------------------------------------
RustyNES's optional CRT shader stack (crates/rustynes-gfx-shaders/) and NTSC
-filters (crates/rustynes-frontend/src/ntsc_bisqwit.rs, ntsc_lmp88959.rs):
+filters (crates/rustynes-frontend/src/ntsc_bisqwit.rs, ntsc_lmp88959.rs, and the
+generated copy of the Bisqwit pass in crates/rustynes-gfx-shaders/src/bisqwit.wgsl):
* The Bisqwit NTSC filter's numeric coefficient tables were ported verbatim (via
Mesen2's implementation) and are therefore GPL-derived (listed above and in
diff --git a/OVERVIEW.md b/OVERVIEW.md
index 4a407826e..06fa56f0d 100644
--- a/OVERVIEW.md
+++ b/OVERVIEW.md
@@ -1,8 +1,8 @@
# RustyNES Overview
**Document Version:** 2.1.0
-**Last Updated:** 2026-09-25
-**Applies to:** RustyNES v3.0.0
+**Last Updated:** 2026-10-06
+**Applies to:** RustyNES v3.0.1
---
@@ -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 one deliberate breaking release), and the v2.1.x → v2.3.x lines deepened accuracy, presentation, and analysis tooling. The current release is **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 one deliberate 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.
-> 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.0**.
+> 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**.
---
diff --git a/README.md b/README.md
index ed663da2b..d09cd2c62 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-

+


@@ -109,7 +109,7 @@ Launch it without a ROM and use **F12**, the File menu, or drag and drop a
### Build from source
-You need **Rust 1.96** (pinned in `rust-toolchain.toml`; [rustup](https://rustup.rs)
+You need **Rust 1.99** (pinned in `rust-toolchain.toml`; [rustup](https://rustup.rs)
installs it) and Git.
```bash
@@ -357,7 +357,7 @@ detailed in [`docs/architecture.md`](docs/architecture.md) and
## Current release
-RustyNES's current release is **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.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
@@ -397,10 +397,10 @@ one, cycle by cycle.
Its co-simulation ladder covers the 6502, the bus and interrupts, the 2C02, the
2A03, AccuracyCoin parity and six mapper boards. Each release attaches a timing-closed
-bitstream pair; v3.0.0's (on-die and off-die, both at fitter seed 5, each compiled
+bitstream pair; v3.0.1's (on-die and off-die, both at fitter seed 2, each compiled
twice to the same bytes) is a **release candidate, not hardware-verified**. Its
-co-simulation ladder reads 199 passed, 0 failed, 1 expected failure on-die and
-200 / 0 / 1 off-die. **No hardware has run any bitstream yet**, so a booting core, a synced
+co-simulation ladder reads 200 passed, 0 failed, 1 expected failure on-die and
+201 / 0 / 1 off-die. **No hardware has run any bitstream yet**, so a booting core, a synced
display, audible sound and a working pad are not claimed. The details, and what
each rung can and cannot verify, are in [`docs/mister.md`](docs/mister.md).
diff --git a/ROADMAP.md b/ROADMAP.md
index 76e23aab3..4df8a813b 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,14 +1,14 @@
# RustyNES Development Roadmap
**Document Version:** 2.0.4
-**Last Updated:** 2026-10-04
-**Project Status:** v3.0.0 "Cornerstone" released — 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: 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).
+**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).
---
## Where we are
-RustyNES is well past v1.0.0. The current release is **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.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 10359fa1a..09903d496 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -2,7 +2,7 @@
## Supported Versions
-The current release is **v3.0.0 "Cornerstone"**. Built on **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.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
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 da60b8e92..ee55055e3 100644
--- a/SUPPORT.md
+++ b/SUPPORT.md
@@ -9,7 +9,7 @@ Thank you for using RustyNES! This document provides guidance on how to get help
1. **Check the Documentation**
- [README.md](README.md) - Project overview and quick start
- [docs/](docs/) - Comprehensive documentation
- - [`to-dos/ROADMAP.md`](to-dos/ROADMAP.md) - Current development status (the root `ROADMAP.md` is a pre-1.0 historical snapshot)
+ - [`to-dos/ROADMAP.md`](to-dos/ROADMAP.md) - Current development status (the root `ROADMAP.md` is the project-level roadmap, updated with each release)
- [FAQ](#frequently-asked-questions) - Common questions (below)
2. **Search Existing Resources**
@@ -94,7 +94,7 @@ 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.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.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?**
@@ -140,13 +140,13 @@ cargo build --release --workspace
**Q: What are the prerequisites?**
-A: Rust 1.96 (pinned in `rust-toolchain.toml`; `rustup` auto-installs it) and the `winit` + `wgpu` + `cpal` system libraries (libxkbcommon / wayland / alsa / udev on Linux; nothing extra on macOS/Windows). See [docs/dev/BUILD.md](docs/dev/BUILD.md) for platform-specific instructions.
+A: Rust 1.99 (pinned in `rust-toolchain.toml`; `rustup` auto-installs it) and the `winit` + `wgpu` + `cpal` system libraries (libxkbcommon / wayland / alsa / udev on Linux; nothing extra on macOS/Windows). See [docs/dev/BUILD.md](docs/dev/BUILD.md) for platform-specific instructions.
**Q: Build is failing, what do I do?**
A:
-1. Ensure you have Rust 1.96 or newer: `rustc --version`
+1. Ensure you have Rust 1.99 or newer: `rustc --version`
2. Install the frontend system libraries (see [BUILD.md](docs/dev/BUILD.md))
3. Try a clean build: `cargo clean && cargo build --workspace`
4. Check [GitHub Issues](https://github.com/doublegate/RustyNES/issues) for known build problems
diff --git a/VERSION-PLAN.md b/VERSION-PLAN.md
index 8125b9f19..43b70541f 100644
--- a/VERSION-PLAN.md
+++ b/VERSION-PLAN.md
@@ -1,6 +1,6 @@
# RustyNES Version Plan
-**Current release: 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.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/).
@@ -19,9 +19,9 @@ v1.0.0 is the **production cut that integrates the cycle-accurate emulation engi
MAJOR.MINOR.PATCH[-PRERELEASE]
```
-- **MAJOR** — incompatible public-API or save-state-format breaks (now at `2`, since **v2.0.0 "Timebase"** broke the `.rns` save-state / `.rnm` movie epochs per ADR 0028), or the first release of a new deliverable class ([ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md): the first hardware-verified FPGA core). **v3.0.0 is MAJOR by the first trigger** (ADR 0042's API and save-state breaks) and ships an unverified release-candidate core; the hardware-verified core is a later v3.x release ([ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md)).
-- **MINOR** — backwards-compatible features (new mappers, new frontend features, new platforms).
-- **PATCH** — backwards-compatible bug fixes and accuracy refinements.
+- **MAJOR** — an incompatible break of the public Rust API (`rustynes-core` and the chip-crate types it re-exports), or the first release of a new deliverable class ([ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md): the first hardware-verified FPGA core). Now at `3`: **v3.0.0 "Cornerstone"** was MAJOR by the first trigger and shipped an unverified release-candidate core; the hardware-verified core is a later v3.x release, numbered after the board session and no later than v4.0.0 ([ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) and its 2026-10-07 amendment). **A format break alone is not a MAJOR trigger since 2026-10-07** (maintainer decision; see "Breaking-change policy" below).
+- **MINOR** — features (new mappers, new frontend features, new platforms). May carry a documented format break.
+- **PATCH** — bug fixes and accuracy refinements. May carry a documented format break when the fix needs one (v3.0.1 raised `EMULATION_EPOCH`).
- **PRERELEASE** — `-alpha.N` / `-beta.N` / `-rc.N` when stabilizing a future minor/major.
## Version history
@@ -62,9 +62,28 @@ The cycle-accurate engine was integrated as the core in a sequence of documentar
Scoped but unreleased. The `(current)` row in the table below stays on the
released version; these are plans, and `to-dos/plans/` holds the detail.
+The line from v3.1.0 to v4.0.0 is indexed in
+[`v3.1-to-v4.0-line-plan.md`](to-dos/plans/v3.1-to-v4.0-line-plan.md), drafted
+2026-10-07 with the maintainer's decisions D1-D29. Version numbers after v3.1.0
+are slots. The hardware release is the last of v3.9.x, after v3.9.0's RTL
+feature freeze and before v4.0.0 (D29); it takes a number only after its board
+session (D1), and folds into v4.0.0 if its fixes are large. (The first draft
+placed it right after v3.1.0, before any feature RTL; D29 reversed that the same
+day.)
+
| Version | Scope | Plan |
|---------|-------|------|
-| v3.x | **Hardware verification**: the SuperStation One board session (Strands A-F), the mobile device runs, and the fixes each produces | [`v3.x-hardware-verification-plan.md`](to-dos/plans/v3.x-hardware-verification-plan.md), [`v2.9.x-final-audit-and-hardware-plan.md`](to-dos/plans/v2.9.x-final-audit-and-hardware-plan.md) (Strands A-F), `docs/mobile-v2.9.3-run-sheet.md` |
+| 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) |
+| v3.5.0 | The full movie input stream, Lua sockets and shared memory, desktop polish. MiSTer F3b: MMC5, N163, VRC6/7, Bandai FCG, expansion audio | [`v3.5.0-plan.md`](to-dos/plans/v3.5.0-plan.md) |
+| v3.6.0 | Libretro Core Options v2, float audio, console targets; Android API 37. MiSTer F4a: PAL and Dendy | [`v3.6.0-plan.md`](to-dos/plans/v3.6.0-plan.md) |
+| v3.7.0 | The mobile extras (iOS box art, widget and external display; EQ and cheat DB; zero-copy frames after UniFFI 0.33). MiSTer F4b: FDS | [`v3.7.0-plan.md`](to-dos/plans/v3.7.0-plan.md) |
+| v3.8.0 | The remaining accuracy residuals, Vs. cabinets, the long-tail mappers. MiSTer F4c: NSF, Vs. System, band-limited audio | [`v3.8.0-plan.md`](to-dos/plans/v3.8.0-plan.md) |
+| v3.9.0 | The MiSTer RTL feature freeze and RC pair; the enum-break trial; the audits re-run; mobile signing set up | [`v3.9.0-plan.md`](to-dos/plans/v3.9.0-plan.md) |
+| HW (the last v3.9.x, numbered after the session) | **Hardware verification** (D29): the SuperStation One board session (Strands A-F) on v3.9.0's frozen pair, the mobile device run, the fixes each produces, then the store listings. No feature RTL | [`v3.9.0-plan.md`](to-dos/plans/v3.9.0-plan.md), [`v3.x-hardware-verification-plan.md`](to-dos/plans/v3.x-hardware-verification-plan.md), [`v2.9.x-final-audit-and-hardware-plan.md`](to-dos/plans/v2.9.x-final-audit-and-hardware-plan.md) (Strands A-F), `docs/mobile-v2.9.3-run-sheet.md` |
+| v4.0.0 | **The API major**: the remaining public enums `#[non_exhaustive]`; MiSTer feature parity | [`v4.0.0-plan.md`](to-dos/plans/v4.0.0-plan.md) |
### Post-1.0 release line (v1.1.0 → current)
@@ -85,7 +104,7 @@ The 1.x line was **additive / off-by-default** — every release stayed byte-ide
| **v2.2.1 – v2.2.5** | Housekeeping (v2.2.1); build / distribution / CI-integrity — libretro buildbot + supply-chain hardening (v2.2.2 "Conduit"); performance + accuracy-closure (v2.2.3 "Datum"); libretro/RetroArch distribution (v2.2.4 "Cartridge"); provenance / licensing / documentation integrity (v2.2.5 "Colophon") |
| **v2.2.6 – v2.2.9** | The **de-monetization + NESdev-remediation** line — RustyNES made permanently open-source and income-free (v2.2.6 "Almanac", ADR 0035); expansion-audio fidelity (v2.2.7 "Timbre II"); gamma-correct presentation (v2.2.8 "Aperture II"); TAS/movie wiring + detachable tool windows + the **relicense to GPL-3.0-or-later** (v2.2.9 "Studio II", ADR 0036) |
| **v2.2.9 "Studio II"** | TAS/movie wiring + the GPL-3.0-or-later relicense — see `CHANGELOG.md` `[2.2.9]` |
-| **v2.3.0 "Datum II"** | Head of the v2.x line; **closes** the v2.2.6 → v2.3.0 remediation line. PPU-accuracy capstone — SMB left-edge + hybrid-address (Rad Racer) verified already-correct against the AccuracyCoin oracle and locked with an exact-141/141 regression gate; hybrid-address provenance finalized (doc/oracle-derived); true multi-viewport OS-window detach; the emulator-lock frame-pacing fix; a −5.1% byte-identical PPU optimization — see `CHANGELOG.md` `[2.3.0]` |
+| **v2.3.0 "Datum II"** | **Closes** the v2.2.6 → v2.3.0 remediation line. PPU-accuracy capstone — SMB left-edge + hybrid-address (Rad Racer) verified already-correct against the AccuracyCoin oracle and locked with an exact-141/141 regression gate; hybrid-address provenance finalized (doc/oracle-derived); true multi-viewport OS-window detach; the emulator-lock frame-pacing fix; a −5.1% byte-identical PPU optimization — see `CHANGELOG.md` `[2.3.0]` |
| **v2.3.1 "Plumb Line"** | Measurement apparatus made trustworthy, then used: a harness-free frame probe, per-source-file subsystem attribution (which recovers the **APU at 18.7% of frame**, invisible in the symbol profile), an adoption A/B with an A/B/A order-bias control, and a contention-aware relative gate. **Ten core hot-path candidates measured, all ten rejected** via six distinct mechanisms — **no emulation-core changes**, AccuracyCoin exactly 141/141 — see `CHANGELOG.md` `[2.3.1]` |
| **v2.3.2 "Lucid"** | Pixel provenance — click any pixel for its full causal chain, down to **the CPU instruction and cycle that last wrote each byte** — plus deterministic replay attestation (`rustynes verify`). All `debug-hooks`-gated and output-only, so AccuracyCoin holds exactly 141/141 — see `CHANGELOG.md` `[2.3.2]` |
| **v2.3.3 "Cadence"** | Display pacing. The run-ahead throttle oscillation attributed to a **stale median** — the gate counted 120 frames of a 600-sample ring, so a p50 at index 300 could not leave the previous depth; **6-7 transitions per 24 s → 1**, spurious releases **2 → 0**. The engage arm now predicts instead of waiting (`run_ahead = 3` converges in **2.8 s vs 12.1 s**, 5/5 paired rounds, p = 0.0312) while releasing still demands a real measurement. Compositor refresh via `wp_presentation`, divisor display-sync, and a validity gate that fails closed; dropped frames **135-254 → 1-9**. Two arms measured and **rejected** with their numbers. No emulation-core changes — see `CHANGELOG.md` `[2.3.3]` |
@@ -95,7 +114,7 @@ The 1.x line was **additive / off-by-default** — every release stayed byte-ide
| **v2.3.7 "Overtone"** | Audio provenance — the APU counterpart of Pixel Provenance: a per-register write attribution (*what wrote this, and from which instruction*) and a per-CPU-cycle mix trace carrying **raw** pre-mix channel values, so a record describes the chip rather than the user's mixer sliders. Its real subject is the trap it inherited: the v2.3.6 fix had enumerated **one caller** rather than the mechanism, and `rustynes-probe` had **three more** same-timeline restores, so running the Latency Oracle or the RAM Atlas emptied both provenance panels for the session. The test named for the contract compared `nes.snapshot()`, and provenance is deliberately not in the snapshot — it asserted something strictly weaker than its own name and passed throughout. A randomized sweep of the save-state parse boundary found **four** panics in VRC7's OPLL where hand-tracing found one; the all-`0xFF` payload *concealed* one. Both core crates change, so AccuracyCoin 141/141 is **verified** — see `CHANGELOG.md` `[2.3.7]` |
| **v2.3.8 "Parallax"** | The **Divergence Lens** — which pixels differ, not just which frame. A trial reduces each frame to one `u64`: the right shape for *detecting* a difference and the wrong shape for *explaining* one. `divergence::localise` keeps the full output and reports the *shape* — population count, first pixel in raster order, inclusive bounding box — on the **index** framebuffer (the PPU's own per-pixel output before the palette lookup), then hands the located pixel to Pixel Provenance so the answer is a cause rather than a coordinate. Three verdicts, and the third is the point: **`Inconclusive`** never arrives wearing the shape of `Identical`. Cut from its own boundary commit rather than from `main`, so its artifacts contain exactly the Lens — see `CHANGELOG.md` `[2.3.8]` |
| **v2.3.9 "Crucible"** | What the gates actually cover. **The docs-only CI skip had never worked** — `predicate-quantifier` defaults to `some`, so the `code` filter's leading `'**'` matched everything and all seven `!` exclusions were dead from the day they were written; fixed with **two** filter steps because `accuracy` is a list of *alternatives* that becomes unsatisfiable under `every`. **`test-roms` now runs at review time** (path-filtered; 11 of the last 40 merged PRs). **A freeze from one cartridge kept writing into the next**, closed by a ROM-transition sweep under one rule: derived output discarded, user-authored input kept, and only input that actively *writes* neutralised. **The config file is written atomically and durably** (seven properties, five from review). 257 lines of dead code removed; 25 of 29 `#[allow(dead_code)]` attributes suppressed nothing; `undocumented_unsafe_blocks` made a gate. Both core crates change, so AccuracyCoin 141/141 is **verified** — see `CHANGELOG.md` `[2.3.9]` |
-| **v2.4.0 "Concordance"** (merged, never tagged; shipped inside v2.4.1) | Atomic, durable writes on every path that persists user data — the plan named three call sites and there were **four**, the fourth being `save_state.rs`, where a truncated write is a user's game progress, while `per_game.rs` was not in the plan at all because it *looks* correct and held two of seven properties. Review found **four more silent successes**, each an error discarded under a comment explaining the rest of the operation. Plus `Nes::timeline_generation()` (session-local, deliberately not in the save state) and `release_anchor_audit.rs`, pinning **15 release anchors across 10 documents**. |
+| **v2.4.0 "Concordance"** (merged, never tagged; shipped inside v2.4.1) | Atomic, durable writes on every path that persists user data — the plan named three call sites and there were **four**, the fourth being `save_state.rs`, where a truncated write is a user's game progress, while `per_game.rs` was not in the plan at all because it *looks* correct and held two of seven properties. Review found **four more silent successes**, each an error discarded under a comment explaining the rest of the operation. Plus `Nes::timeline_generation()` (session-local, deliberately not in the save state). (`release_anchor_audit.rs`, pinning **15 release anchors across 10 documents**, is not part of v2.4.0: it landed separately in #427, after v2.3.9 and before v2.4.0 merged, and ships in v2.4.1.) |
| **v2.4.1 "Fabric"** | RustyNES as the oracle a new implementation is verified against, opening the v2.4.1 → v2.5.0 "Fabric" line. `crates/rustynes-cosim` exposes the emulator through a narrow C ABI a Verilator testbench links, plus `nes_golden_export`. **Excluding it from the workspace is the load-bearing detail**: cargo unifies features, `irq-timing-trace` selects a *different* per-dot loop in `Bus::tick_one_cpu_cycle`, and the accuracy battery had therefore been validating a scheduler no user runs (+1.2% to +1.9%, below the 3% bar — the percentage was never the argument). Also: the first `run_frame()` after power-on advances **zero** cycles, and two trace-gated core modules had never been linted. Carries v2.4.0. AccuracyCoin 141/141 verified. |
| **v2.4.2 "Cairn"** | The **rung-0 compare surface**. Rolling per-cycle hash checkpoints, measured at **15,263x** smaller than the equivalent CSV (89,343 cycles: 5,372,427 bytes of `irq.csv` against 352 bytes of `ckpt.bin`), so the first mismatch names a 4096-cycle window and only that window is re-run with full capture. **What is hashed is a decision about hardware, not about convenience**: `Observable` is the subset of `CycleRecord`'s 29 fields a device can genuinely produce, the IRQ pair is OR'd before hashing because hardware has one wire-OR’d /IRQ pin, and `pc` is labelled DUT-observable rather than pin-observable. The v2.4.2 acceptance gate is executable and found a defect: a divergence at cycle 0 was reported in a window that did not contain it. The emulation core is untouched. |
| **v2.4.3 "Touchstone"** | The two Fabric-plan risks that had to be settled **before any RTL exists**, both answered by evidence contradicting the plan. **Risk 4, the Quartus subset, is FITTED**: 17.0.2 Build 602 on a 5CSEBA6U23I7 produced a placed-and-routed netlist with **0 errors and 0 synthesis warnings**, and the 2 KiB array inferred as **2 M10K blocks with 29 total registers** — not 16,413 — from the source style alone, no `ramstyle`. The `initial` block produced a real MIF and the `enum` was one-hot encoded. Nine constructs promoted to *fitted*; plain `case`, `priority case` and `$bits` deliberately left *documented* because the kitchen sink does not exercise them. **Risk 1, the `sys/` licence, inverts the hedge**: 57 files, **zero GPL-2.0-only**, and a non-optional GPL-3.0-or-later `hps_io.sv` forces the combined bitstream **up** to GPL-3.0-or-later — already RustyNES's licence, so no relicensing. The emulation core is untouched. |
@@ -121,7 +140,7 @@ The 1.x line was **additive / off-by-default** — every release stayed byte-ide
| **v2.6.3 "Mainspring"** | The DUT runs on one master clock, and four enables that were never enabling. A mainspring is the single wound source that drives a clock's whole train, which is what `nes_top` becomes here: it took its clock enables as INPUTS and the testbench generated the dot phase; it now takes a single 21.477272 MHz master clock and derives `ce`, `ppu_ce` and `ppu_access` itself -- the shape Quartus compiles. It is built in RustyNES's own v2.0.0 "Timebase" shape, **two independent accumulators in master-clock units, never reset to one another**, and that is not stylistic: a modulo-`CPU_DIV` phase counter looks equivalent on NTSC and cannot express PAL at all, where 16 master clocks per CPU cycle and 5 per dot is 3.2 dots per cycle. `ACCESS_MC` and the PPU phase offset are DERIVED from the oracle's `read_split`/`write_split` rather than swept, and **five testbench phase knobs are retired** -- they existed to find this phase, and the answer is now compiled into the core. **It found four enables that were never enabling.** The old testbench tied `ce` high and pulsed the clock once per CPU cycle, so the clock did the gating the enable was supposed to do and any ungated `always_ff` was correct only by accident; under a real master clock each fires twelve times. Two were already known (the PPU register block at v2.5.7, the open-bus decay reload) and **two were not**: the DMC's DMA acknowledge, where the sample pointer advanced by TWELVE per byte and 324,182 of 357,360 cycles diverged, and the frame-counter IRQ set points, where the IRQ line rose eleven master clocks early so the CPU took the interrupt one instruction sooner -- caught by blargg's `08.irq_timing`, a third-party ROM rather than our own trace agreeing with itself. A **compensating** fix was found and REJECTED: delaying the APU's IRQ by one cycle also gave 66 of 66 and is indistinguishable from the real fix by gate result; `cpu6502.sv` already implements the oracle's second-to-last-cycle recognition, correctly gated, so a second delay would have cancelled an APU-side error. Looking for a cause AFTER the fix worked is what separated them. **blargg's `instr_test-v5` battery becomes a standing gate** -- sixteen third-party ROMs, ~2.68 M cycles each, compared per cycle, **16 of 16 exact**, taking the suite from 50 gates to **66 green, 0 failed**. Every rung-1 ROM before these was written inside the project, so the rung could only ask questions someone there thought to ask; these found **three defects the entire self-written corpus had missed**, none of them in the opcodes the battery was run to validate: `RRA` fed its `ADC` stage the carry from BEFORE the instruction (its bus trace was identical on both sides and only the accumulator differed, by one, surfacing nine cycles later), the 8-cycle indirect read-modify-write forms addressed the indexed target during their POINTER fetch cycles, and the PPU I/O-bus latch never decayed -- a 2C02 defect reached from a CPU ROM, three rungs after rung 3 closed. The five `SH`-group stores close the decoder at **256 of 256** opcodes. **The decay constant is where documentation and oracle contradict each other on a quantity a gate depends on.** The wiki says 3-30 ms; RustyNES uses 558.7 ms. Swept against the full 66-gate suite rather than argued: 30 ms fails 9 gates, 50 ms fails 5, 100 ms 3, 200 ms 2, 300 ms 1, and 558.7 ms is the first value failing none. The binding constraint is one measurable property of one ROM -- `10-branches` has a longest gap between group-0 refreshes of 936,697 CPU cycles, or 2,810,091 dots -- and that prediction was TESTED: 2,809,000 dots leaves 52 divergences and 2,811,000 is exact, so the corpus demands >= 523.4 ms. Documentation and corpus are incompatible by a factor of ~17, this rung has no independent oracle to adjudicate, and the constant stays the oracle's, stays labelled **fitted**, and stays a `localparam` so it can move when something can decide. That is Fabric risk 6 -- the oracle can be wrong -- arriving as a measurement rather than a caveat. **Rung 5 reaches an end-to-end AccuracyCoin run**, and the oracle gains `accuracycoin_status`: a status vector decoded against the 146-entry catalog and comparable **entry for entry**, including `Skipped` and `NotRun`, naming every disagreement by test rather than by address. First measurement: **137 of 146 entries agree, 9 differ**, six sharing one failure code -- a pattern a pass count of 137 would have hidden. Producing the vector is this release's deliverable; making the two agree is v2.6.4. Also: an Android dependency refresh (AGP 9.2.1 -> 9.3.2, Compose compiler 2.3.10 -> 2.3.21, `compose-bom` 2026.08.00) with the Gradle 10 deprecations cleared and the AGP/Kotlin interlock measured out of the published POMs rather than assumed; a Rust and Actions refresh; and `markdownlint-cli` v0.39.0 -> v0.49.1, where the pin held since v2.3.9 as a hazard is finally MEASURED -- `MD060/table-column-style` reads this corpus as `compact` and reports 1,936 findings across 122 files, so the style already in use is pinned instead, measuring zero and rewriting no document. **No `rustynes-{cpu,ppu,apu,mappers,core}` changes**, so AccuracyCoin **141/141 (100.00%, RAM decoder)** and nestest 0-diff hold by construction -- and were run anyway. |
| **v2.6.4 "Rubric"** | OAM DMA lands, all nine AccuracyCoin disagreements close, and then the gate that certified them is measured to cover 88 of 146 entries. **`$4014` was a register the console decoded and did nothing with** -- the DUT had never spent the 513 cycles an OAM DMA costs. It lands as a real bus master (halt on a read cycle, optional alignment, 256 read/write pairs) from `nesdev_wiki/DMA.xhtml`, with the documented DMC-get precedence costing OAM its alignment as well as its slot; its halt and alignment were fitted to the oracle first and corrected from the wiki, recorded rather than squashed. The **`SH` group** closes in two steps, the second named by the residual of the first: the AND with the address high byte is RDY-conditional, and the dummy-read cycle is addressing-mode dependent, so `SHA (d),Y`'s `tcyc==3` is a pointer-high fetch. Those took the vector 9 -> 3. A rubric is the authoritative statement of the rules, written by whoever set the test -- which is literally where all three fixes came from. **AccuracyCoin is MIT-licensed and its assembly source is one `curl` away**; this plan's own note that it "is not vendored" is true of this repository and had been read as unavailable. The source explains every assertion, and settled three entries in minutes. **`Open Bus`**: a read of `$4015` does not drive the data bus and its D5 is open bus -- rules the nesdev pages do not state at all, and its stimulus (`LDX #$16 / LDA $40FF,X`) is the exact instruction the trace divergence had been localised to independently. **`Interrupt flag latency`**: branches poll before cycles 2 and 4 and NEVER before 3, so a taken branch that does not cross a page has no poll at its last cycle. **`NMI Overlap BRK`**: an interrupt sequence does not poll (stated in the wiki, missed here), and the hijack window was one CPU cycle narrow at its late edge -- where the comment above the line had argued at length for the wrong version and named this very entry as the test that would catch it. **A `Fail(N)` names test N, one-based**, and decoding it as an index had made a REGRESSION (test 7 -> test 5) read as progress; v2.6.3's reading that six entries "shared one failure code" and therefore one cause is retracted with it. **A fix that closes one gate and opens another is a scope measurement**: the first poll fix moved the poll for EVERY instruction, closed the entry and regressed `apupulse026` and `blargg08`; narrowed to branches alone, nothing regresses. Three AccuracyCoin sub-test ROMs become standing **verdict** gates (69 -> **72 green, 0 failed**), verdict rather than bus by measurement -- their per-cycle surfaces are dominated by the open PPU I/O-latch item at 2,331,867 of 4,467,082 cycles. **7 of 8 RTL mutations CAUGHT**, the eighth classified INERT by byte-comparing 14,294,736 bytes of trace. **Then the coverage finding.** The vector reported identical across all 146 with **58 entries `NotRun` on BOTH sides**: the 600-frame window reaches the CPU catalog and asks nothing about the APU, PPU, sprite-evaluation or PPU-misc suites. 4500 frames executes all 146 (134,012,761 cycles). `accuracycoin_status` now prints coverage and REFUSES when any entry is unrun on both sides (3 of 3 mutations caught). Widening it found a real defect on its first run, at cycle 20,636,325: a halted CPU mid-`LDA $2007` held `ppu_sel` high through a DMC steal, so the sample fetch got the PPU read buffer instead of the cartridge -- the comment above the line stated the intent it violated. **Rung 5 does NOT close**: measured on both sides, the oracle climbs 88 -> 95 -> 117 -> 120 -> 146 while the DUT goes 88 -> 5 -> 5 -> 5, flat for 74 M cycles. The DUT sits in a three-cycle self-loop at `$80DF` = `INC $EC` / `JMP $80DF`, AccuracyCoin's MENU IDLE LOOP, with only the five results the power-on path writes -- so it RESET and returned to the menu rather than hanging inside a test, a reading published from catalog order and retracted after one PC probe. Reported as unavailable rather than as "141 of 146 differ", which is one defect and not 141. **No `rustynes-{cpu,ppu,apu,mappers,core}` changes**, so AccuracyCoin **141/141 (RAM decoder)** and nestest 0-diff hold by construction. |
| **v2.6.5 "Muster"** | 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` -- measured over the 4500-frame golden, 134,012,761 cycles, where the same gate read **5 of 146 executed and 22 differing** at the version's start. A muster is a roll call where every name is called AND answered, which is this release's two-clause acceptance exactly; the second clause is v2.6.4's addition, and without it an identical vector over entries that never executed is a pass, and was one. **Five PPU defects close the last six differing entries, and four were invisible to every gate that existed when the version opened.** The recurring shape is a gate agreeing about a question it was never asked. **The background shift registers' RELOAD and their shift clock need SEPARATE gates**: with one shared gate `BG Serial In` was not merely failing but ARITHMETICALLY UNREACHABLE -- reload dots are absolute, so on a re-enable the next reload is at most seven dots away and the reload takes `hi[14:7]`, discarding every serial-in one, so bit 7 can never hold one on any alignment for any stimulus. Modelling both structures reproduces BOTH measured shifter values, the oracle's `F807` falling out of the split model unfitted and the common gate's repeating `0001 0003 ... 007F 0000` containing the `0003` the DUT reported. **That fix alone left the gate red**, which is why it is recorded separately: **the sprite X counters are NOT gated on rendering**, and AccuracyCoin's `Stale Sprite Shift Registers` test 2 says so outright -- "Rendering was disabled for 18 ppu cycles, but the sprite counters were NOT halted during that time". This core froze them, so a disable/enable pair pushed every sprite right by the width of the window; the probe showed `bopq=1` with sprite zero's counter still reading **18**, the exact width of the test's own off-window. **The ROM that STATES the rule passes either way** -- it expects no hit at X=`$FE`, and a sprite shoved 18 dots further right is also off the end of the line. **The PPUADDR second-write `v <- t` copy is DELAYED**, and `nesdev_wiki/PPU_scrolling.xhtml` says so inside the write sequence itself ("wait 1 to 1.5 dots after the write completes"); this core committed it in the write's own edge. Swept 1 to 4 dots, all of which close `Hybrid Addresses`, against a control at 8 and 12 that fails -- the control is the load-bearing half, since four consecutive passes is also what a build ignoring the override would produce. **The pre-render line CLEARS secondary OAM**: the whole evaluation block INCLUDING the clear was gated on `scanline < 240`, so the pre-render line kept what scanline 239's evaluation had left and the next frame's scanline 0 drew it. **No sprite can ever render on scanline 0**, because OAM Y is stored one less than the display row. A sprite-0 probe over the full battery named it in one run: 24 hits in 134 M cycles, four of them at scanline 0, one per frame, immediately after the single legitimate scanline-239 hit. The fifth, the octal latch holding across the read dot, is verified by exactly ONE gate and was unverifiable until the fourth landed -- the two compose the hybrid address together and neither produces it alone. **A DIAGNOSIS IS RETRACTED.** The residual was read as a two-dot CPU/PPU alignment error, from comparing per-dot record spans across two instruments. Three configurations refute it: at the committed alignment the two consoles execute identical `pc`, `bus_addr` and `bus_access` for **1,695,131 cycles** and issue both PPUADDR writes at the same cycles, while a two-dot power-on shift moves the first divergence back to 593,228 and takes the differing share from **5.13% to 66.80%**, and `LEAD_CPU_CYCLES` 1 -> 2 diverges by scanline 8. The "two dots" was two instruments stamping their records at different points in the cycle -- the v2.5.7 lesson, third occurrence. Also: `rustynes-cosim`'s `state_trace_records_carry_their_cpu_cycle` was gated on a feature no CI step enabled, so it **ran nowhere** -- a regression test the gate could not reach, which is the shape the surrounding CI steps exist to prevent; it runs now, and the test genuinely inapplicable under that feature is gated out with its reason rather than left failing. **The oracle changes on the DEFAULT path** (a `Controller::write_strobe` owed-shift fix), so **AccuracyCoin 141/141 (RAM decoder)** and nestest 0-diff are **VERIFIED, not asserted**. |
-| **v2.6.6 "Chassis"** | The console becomes a MiSTer core. `sys/` is vendored **byte-identical** to `Template_MiSTer@3ea1134c` -- 57 files, 0 content differences, 0 files on one side only, which is acceptance clause 1 measured rather than asserted -- and `rtl/emu.sv`, `rtl/pll.v`, `rtl/palette.sv`, `rtl/video_timing.sv`, `rtl/apu_mixer.sv` and `rtl/audio_dc_block.sv` make the verified console into something Quartus 17.0.2 compiles for a 5CSEBA6U23I7: **0 errors, timing CLOSED**, and the warning count taken from **111 to 3** -- all three inside `sys/` or Quartus's own megafunction, none citing this project's RTL -- worst setup **+0.363 ns**, worst hold **+0.245 ns**, **TNS 0.000 on every clock**, the console's own clock at **+13.514 ns** and 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. **An M10K read is REGISTERED**: `cart_nrom.sv` read its arrays asynchronously under a comment claiming that inferred block RAM "from the source style alone", so 40 KiB of cartridge was **393,216 registers against roughly 166,000 available** and Quartus refused it outright. The project had already written the rule down -- the README has said since v2.4.3 that the core would use "synchronous read with a registered address, no asynchronous read anywhere" -- and nothing before this rung had ever asked Quartus about the cartridge, because v2.4.3 fitted a 2 KiB probe and 2 KiB fits either way. The one-clock latency was **probed rather than argued**: `bus_addr` is stable for all twelve clocks of a CPU cycle and the access lands at the seventh, so the console never sees it; the HARNESS did, and its device cross-checks moved to where the CPU actually samples. **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 -- the mixer is unipolar with silence at zero, and the framework maps unsigned silence to **-32768**, an offset into the DAC and into HDMI audio rather than a quiet channel; `audio_dc_block` is the coupling capacitor real hardware has, cornered at the console's own documented ~90 Hz. And two OSD entries did nothing, "CRT 25%" and "CRT 50%" advertised while `VGA_SL` was tied to zero. **The finding worth keeping: a convention enforced by a glob has no error message.** `sys/sys_top.sdc` groups the core's 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's 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. Reading Fmax and slack TOGETHER is what separates a missing constraint from a slow design. Nine such findings are recorded in the sibling's `docs/rung6-integration.md`, including a Quartus **internal error** on the 256-entry decode table and the three fixes for it that were measured and failed. **Not claimed:** no DE10-Nano and no SuperStation One are attached to this machine, so a booting core, a synced display, audible sound and a working pad are NOT established -- rung 6 did NOT close at v2.6.7 either, because no board was attached there, and it moves to the first release after one exists. **Zero emulation-core changes**, so **AccuracyCoin 141/141 (RAM decoder) and nestest 0-diff hold by construction**, and were re-run. |
+| **v2.6.6 "Chassis"** | The console becomes a MiSTer core. `sys/` is vendored **byte-identical** to `Template_MiSTer@3ea1134c` -- 57 files, 0 content differences, 0 files on one side only, which is acceptance clause 1 measured rather than asserted -- and `rtl/emu.sv`, `rtl/pll.v`, `rtl/palette.sv`, `rtl/video_timing.sv`, `rtl/apu_mixer.sv` and `rtl/audio_dc_block.sv` make the verified console into something Quartus 17.0.2 compiles for a 5CSEBA6U23I7: **0 errors, timing CLOSED**, and the warning count taken from **111 to 3** -- all three inside `sys/` or Quartus's own megafunction, none citing this project's RTL -- worst setup **+0.363 ns**, worst hold **+0.245 ns** (both **withdrawn at v2.6.7**: no clean rebuild reproduces them, and the re-measured pair is +0.108 ns setup / +0.042 ns hold; see the next row), **TNS 0.000 on every clock**, the console's own clock at **+13.514 ns** and 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. **An M10K read is REGISTERED**: `cart_nrom.sv` read its arrays asynchronously under a comment claiming that inferred block RAM "from the source style alone", so 40 KiB of cartridge was **393,216 registers against roughly 166,000 available** and Quartus refused it outright. The project had already written the rule down -- the README has said since v2.4.3 that the core would use "synchronous read with a registered address, no asynchronous read anywhere" -- and nothing before this rung had ever asked Quartus about the cartridge, because v2.4.3 fitted a 2 KiB probe and 2 KiB fits either way. The one-clock latency was **probed rather than argued**: `bus_addr` is stable for all twelve clocks of a CPU cycle and the access lands at the seventh, so the console never sees it; the HARNESS did, and its device cross-checks moved to where the CPU actually samples. **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 -- the mixer is unipolar with silence at zero, and the framework maps unsigned silence to **-32768**, an offset into the DAC and into HDMI audio rather than a quiet channel; `audio_dc_block` is the coupling capacitor real hardware has, cornered at the console's own documented ~90 Hz. And two OSD entries did nothing, "CRT 25%" and "CRT 50%" advertised while `VGA_SL` was tied to zero. **The finding worth keeping: a convention enforced by a glob has no error message.** `sys/sys_top.sdc` groups the core's 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's 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. Reading Fmax and slack TOGETHER is what separates a missing constraint from a slow design. Nine such findings are recorded in the sibling's `docs/rung6-integration.md`, including a Quartus **internal error** on the 256-entry decode table and the three fixes for it that were measured and failed. **Not claimed:** no DE10-Nano and no SuperStation One are attached to this machine, so a booting core, a synced display, audible sound and a working pad are NOT established -- rung 6 did NOT close at v2.6.7 either, because no board was attached there, and it moves to the first release after one exists. **Zero emulation-core changes**, so **AccuracyCoin 141/141 (RAM decoder) and nestest 0-diff hold by construction**, and were re-run. |
| **v2.6.7 "Detent"** | The bitstream becomes a **published, reproducible** release artifact, and a one-cycle disagreement is located 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, because the MiSTer distribution mechanism reads that path out of the REPOSITORY, so an empty `releases/` describes an undistributable core rather than a cautious one. Reproducibility is **measured**: a from-scratch compile and an incremental one produce a byte-identical bitstream. The same measurement **withdrew v2.6.6's published slack figures** -- no corner of a clean rebuild reproduces them, the innocent explanation was checked first and refuted, and the correct pair is **+0.108 ns setup / +0.042 ns hold** at the binding corner. **The release gate had been reading the wrong corner**, and its checker was wrong twice before mutation found both: it extracted ZERO rows and called that "no negative slack", then reported FOURTEEN clocks from a report emptied of data. Caveat C2 **CLOSES**: the first residual was a trace observation point (3 to 11 checkpoint comparisons passing), and the second was real -- its FIRST fix was refuted, blargg dropping 11/11 to 4 of 11 including the ROM written to probe that timing, and **that refutation found the right fix**, since it proved the sequencer's maturation was correctly placed. Separating only the interrupt clear lands it at **write+3**, the documented cycle. Checkpoint streams 11 identical to **52 of 58**, suite 87 to **122**, with **51 checkpoint comparisons**. The gate states its blind spot: no gated golden raises an NMI. The emulation core is unchanged. **Rung 6 does not close** -- no board is attached to this machine. |
| **v2.6.8 "Arrears"** | The gates v2.6.7 fixed and never widened. **A deny list is an assertion about the THING UNDER TEST**, so v2.6.7 changing both the DUT (the `$4017` interrupt-clear split) and the harness (the `Observable` completed at end-of-cycle, `nmi_line` wired to the effective line) re-opened every exclusion -- and nothing re-measured one. **Four of the six denied checkpoint streams were passing**: `irqlat048` (22 checkpoints identical), `ppuvbl023` (175, through cycle 714,737), `ppuvbl024` (291) and `ppuvbl025` (175); `apuconflict039` and `ppuoamcorrupt052` genuinely differ and are restated from current measurements. **Three of them were not run by the suite AT ALL** -- committed golden, ROM and manifest, named nowhere in `regress.sh` except the deny list, which only ever governed the auto-attach, so removing them from it 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 now **all 5,062,680 overlapping cycles match** with the window DERIVED from the manifest rather than written down -- a literal is how this gate went wrong twice. Coverage **19x**. **Caveat C4 CLOSES, by demonstration**: nestest raises `nmi_line` on **3,592** cycles and had no nine-field comparison at all, because it uses `nestest-gate` so the auto-attach could not reach it; wiring it makes a mutation reverting `o.nmi_line` to its pre-v2.6.7 constant **CAUGHT** at checkpoint 28 of 1,237, **while the bus gate passed the identical run**. `ppuspr0` was the last uncovered golden, so the set closes: 59 = 40 loop + 16 auto + nestest + 2 denied, none unreached. Suite **128 passed, 0 failed, 0 skipped** (from 123), nine-field comparisons **51 -> 57**. The widened gate's first catch was **this release's own change** (a window derived from `obs.bin`'s size, 8 short because the stream starts at cycle 8), and a "harness path bug" was **refuted** before a fix was written. The emulation core is untouched, so AccuracyCoin 141/141 holds by construction. **Rung 6 does not close** -- no board, confirmed by checking. |
| **v2.6.9 "Abeyance"** | An exclusion hides improvement as well as regression. Abeyance is a state of temporary suspension in which a right still exists and nobody exercises it -- which is what a deny-list entry is: the coverage is still there, still computable, and nothing looks at it. **Both remaining denied co-simulation streams close, and 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"; 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 -- and taking it from the latch makes the stream identical on **all 357,361 overlapping cycles and all 88 checkpoints**. "By design" is the phrase that 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: **the PLANNED mechanism was refuted by its own mutation pass**, because an allowance by checkpoint index cannot work on a rolling hash -- 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. `obs_diff9.py --allow-cycle` puts the allowance on the per-cycle comparison instead, where an attributed difference costs **one cycle rather than 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. |
@@ -131,7 +150,7 @@ The 1.x line was **additive / off-by-default** — every release stayed byte-ide
| **v2.6.13 "Slack"** | 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, 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 **one figure read off the fetch structure and never measured**; `nes_top`'s `CHR_LAT` sweep asks the console directly and gets **three answers**: a background or sprite fetch tolerates 28 cycles and uses 17, the `$2007` 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 `$2007` port could never fit, so it leaves the shared bus entirely through a `BUFFER_HANDSHAKE` on a port of its own on the arbiter -- affordable because the CPU does not read that buffer until its next `$2007` 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, so `ppu-misc-2007-stress` passed off-die throughout while the two CNROM gates read `$00` where the oracle reads `$01`, `$01`, `$02`. The fix **removes** the address rather than correcting it -- `cart.sv` already publishes the translation for the fetch path, so 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. That single cycle 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 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 -- Quartus said so twice while the lint gate stayed green, and the comment beside them asserted the defect as a virtue. **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 its failures exactly; closed with a stamp-file prerequisite, demonstrated by mutation. The **open row** lands (a hit costs 6 cycles against 10 for a miss, tRAS's 120 us MAXIMUM respected by an early close in idle), and two MiSTer tickets close: `sdram_sz` consumed **validity bit first**, so a power-on `0x0000` cannot read as "no board", and `status_menumask` computed rather than tied off. **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 add-on while rung 7's five mapper families fit at 468 of 553 blocks. The emulation core is unchanged, so **AccuracyCoin 141/141** and nestest 0-diff hold by construction. |
| **v2.6.14 "Docket"** | 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 `to-dos/mister/contribution-checklist.md` decides readiness -- and it had 30 boxes, 16 unticked, and **fourteen of those sixteen saying nothing about why**. An unticked box with no reason cannot be told apart from work outstanding, work blocked elsewhere, and **work already done and never ticked**; the third case occurred **five times**, so the list reported the project as further from submission than it is by a fifth of its own length. **The impossible box** asked that `docs/provenance.md` state "that no NES core was ever opened" -- which that document's own *Do not self-certify* section forbids, so satisfying it required writing the one sentence the provenance rules exist to prevent. **Re-measuring the ticked half** found two more, v2.6.9's lesson in a new document: `RustyNES.sdc` still claimed "exactly one core clock", true of v2.6.3 and false since v2.6.13; and the `.qsf` entry's "all 109 pin assignments" is 145 from two scripts. **The alarming reading of the first was checked before it was written down** -- `set_clock_groups -exclusive` cuts paths BETWEEN groups, all three core-PLL outputs match one glob, and v2.6.13's own -24.769 ns crossing measurement is only observable because those paths ARE analysed. **The naming divergence is measured from `Main_MiSTer/file_io.cpp`** rather than the wiki: `get_display_name` truncates the display name at the literal `_20`, so a version-named bitstream makes every release a SEPARATE core entry, ordered alphabetically -- putting v2.6.9 after v2.6.13. Not fixed: version-naming is a maintainer decision. **The task board had the same defect and one row worse** -- four delivered items never ticked, and an SDRAM row whose precondition "after a board exists" v2.6.13 did not follow, having accepted the controller against a datasheet-derived model; rung 7's own lesson repeating one row below where it is written. **A v2.6.13 claim is retracted**: MMC1 was written up as unimplemented and is decoded at three sites with a gate green since rung 7. The gate asserts a SHAPE rather than a judgement, with five mutations. Bitstream **byte-identical** to v2.6.13's, which is the point -- the only sibling change is a comment. The emulation core is unchanged, so **AccuracyCoin 141/141** and nestest 0-diff hold by construction. |
| **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** -- carried since v2.6.7 as a style item awaiting a decision, and it is not one: `Distribution_MiSTer`'s builder strips a datecode by taking the stem's last nine characters, requires `_` plus exactly eight digits, and SKIPS OUTRIGHT any file that yields none, so an accepted core would appear in the wiki's Cores table and ship nothing with no error raised anywhere. A second parser in the firmware has a different rule again, and `_YYYYMMDD` is the only form satisfying both -- so two names, because there are two audiences and only one is a parser. **Two of the four R1/R2 residuals were never IRQ-timing residuals**: `mmc3_test` and `mmc3_test_2` are the same suite twice, and sub-test 2 of `5-MMC3` carries the IDENTICAL `set_test` string in both while differing by one instruction -- the successor inserts a second `clock_counter` before the first assertion, so blargg WITHDREW the verdict this emulator fails. Adopting it anyway makes one ROM pass and regresses both `scanline_timing` ROMs from sub-test 3 to 2; reverted, with the numbers. **Three gates that did not exist**: `sys/` verbatim rested on one measurement taken eight releases earlier and now pins all 57 files, catching stray ones too; the `.qsf` published TWO seed tables disagreeing about the pinned seed's margin by 0.155 ns; and `bump_release.py`, which dropped a release from the chain in two consecutive releases, turns out to RELOCATE it thirty entries away rather than drop it. **An accuracy gate someone else can run** -- nine rung-1 ROMs export from a PINNED oracle commit and compare in CI, so the evidence stops being a document describing a check a reader cannot run, which is the bar the contributing page sets for AI-assisted code. **`cpu_interrupts_v2` on the DUT**, five of five, the first INDEPENDENT interrupt oracle -- and `5-branch_delays_irq` adjudicates the exact behaviour v2.6.7 changed in the emulator from documentation reasoning alone. **`T-ORACLE-001`'s opening claim is RETRACTED**: this emulator does clock the MMC3 counter on the pre-render line, which `2-details` sub-test 8 -- "Counter should be clocked 241 times in PPU frame" -- has asserted every release. The claim came from a trace that carries no CHR address column and therefore cannot see an A12 rise at all. **One flagged stale claim was not stale**, and saying so is the discipline: the sibling's ledger changelog was correct, and needed only for its silence to be legible. The emulation core is unchanged, so **AccuracyCoin 141/141** and nestest 0-diff hold by construction. |
-| **v2.6.16 "Interlock"** | The arbiter's numbers describe a stimulus, not the console. `tb/sdram_arb_main.cpp` has reported the CHR fetch with seven cycles of margin and the CPU short of its deadline by four since v2.6.13, under a stimulus that issues both requests on the SAME cycle. Measured at the arbiter's own ports on off-die builds of three workloads, the console produces **zero** such coincidences over fifteen million SDRAM cycles and a **minimum separation of two**, because both requesters are locked to one master clock at a fixed alignment. So both figures are wrong about the console in **opposite directions**: the CPU **meets** its deadline, 24 against 24, on all 240,303 requests, at **zero margin**; the fetch reaches **23 against a derived 22**. That exceedance is not a defect, and the control is why — `CHR_LAT` adds four SDRAM cycles per step, and off-die `ppurender` matches the oracle on all 61,440 pixels at +0, +1 and +2 and **fails at +3**, so four passes are evidence rather than a knob that never reached the compiler, and the derived budget understates the real tolerance by at least nine. The slot-scheduled arbiter this version was scoped around is therefore **not built** — the shortfall decomposes to contention alone (PRG 15 against 24 with CHR silent) and the console's own phase already separates the requesters — and is still worth building, because zero margin means one added cycle anywhere breaks the CPU. A prediction that was refuted is kept with its measurement: moving the stimulus onto the real bases, four mebibytes apart, was expected to worsen the worst case and left both figures byte-identical, because refresh precharges every bank. Two new gates, eight mutations, all CAUGHT. Suite **147 on the die, 148 off**. No Rust changed, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. |
+| **v2.6.16 "Interlock"** | The arbiter's numbers describe a stimulus, not the console. `tb/sdram_arb_main.cpp` has reported the CHR fetch with seven cycles of margin and the CPU short of its deadline by four since v2.6.13, under a stimulus that issues both requests on the SAME cycle. Measured at the arbiter's own ports on off-die builds of three workloads, the console produces **zero** such coincidences over fifteen million SDRAM cycles and a **minimum separation of two**, because both requesters are locked to one master clock at a fixed alignment. So both figures are wrong about the console in **opposite directions**: the CPU **meets** its deadline, 24 against 24, on all 240,303 requests, at **zero margin**; the fetch reaches **23 against a derived 22**. That exceedance is not a defect, and the control is why — `CHR_LAT` adds four SDRAM cycles per step, and off-die `ppurender` matches the oracle on all 61,440 pixels at +0, +1 and +2 and **fails at +3**, so three passes followed by a failure at +3 are evidence rather than a knob that never reached the compiler, and the derived budget understates the real tolerance by at least nine. The slot-scheduled arbiter this version was scoped around is therefore **not built** — the shortfall decomposes to contention alone (PRG 15 against 24 with CHR silent) and the console's own phase already separates the requesters — and is still worth building, because zero margin means one added cycle anywhere breaks the CPU. A prediction that was refuted is kept with its measurement: moving the stimulus onto the real bases, four mebibytes apart, was expected to worsen the worst case and left both figures byte-identical, because refresh precharges every bank. Two new gates, eight mutations, all CAUGHT. Suite **147 on the die, 148 off**. No Rust changed, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. |
| **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; pre-v9 `.rns` states no longer load). The third new test 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. Re-deriving that ROM under phi2 PROVED `mask_for_skip_check` is a compensation for the placement (one delay stage instead of two, identical `08 08 09 07`), 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 planned v2.6.18 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. Plan: [`v2.6.18-terminus-plan.md`](to-dos/plans/v2.6.18-terminus-plan.md). |
| **v2.6.18 "Errata"** | The recorded cause was wrong in three ways, and the last AccuracyCoin entry closes -- **144/144, 100.00%, RAM decoder**. `Frozen OAM2 Increment` had a diagnosis pinned in `KNOWN_FAILING` saying a `$2001` enable on dot 256 "takes effect a dot early"; measured per dot, all four writes the ROM names (242, 256, 325, 340) take effect during dot N-1 and are therefore in force from the START of dot N -- exactly where the ROM says, so the core was never early. The failing sub-test was **4**, the false-positive guard, not 2 or 3: `TEST_FrozenOAM2Inc` has no `INC 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` |
| **v2.9.7 "Tandem"** | The desktop's features on the web and on phones, and an A12 fix found by real games -- the eighth release of the v2.9.x line and the fourth of the line to v3.0.0. Release binaries are the `full` build (`release.yml`, the PGO/BOLT steps), and a new CI job builds `full` on macOS and Windows. Web: battery saves in IndexedDB, Vs. DualSystem with both screens. Mobile: FDS (host-supplied BIOS), NSF, and Vs. DualSystem through the bridge, plus a "Cancel opposite directions" switch (Swift uncompiled; run-sheet rows T1-T12). Desktop: cabinet save states (`T-PS-dual-savestate`), the Settings overclock wired (stock timing under movies and netplay), `client.frameskip` closed by design, the user-facing panels translatable (498 keys, 496 Spanish). The PPU reported one A12 pulse per scanline where the console makes eight. `read_vram` now reports every read, MC-ACC (4.3, promoted to Curated), mapper 91 and the J.Y. ASIC count at their documented rates (the Donkey Kong Country 4 map is fixed), and four MMC3-core boards gained MMC3's filter; cost about +1.9% on the nestest workloads. Time Diver (mapper 250) fixed; Uchuu Keibitai localised, open. `--features test-roms` 3,065 passed, 0 failed; AccuracyCoin 144/144, nestest 0-diff. MiSTer bitstreams: v2.9.2's pair byte for byte. Plan: `to-dos/plans/v2.9.7-tandem-plan.md` |
@@ -167,15 +187,17 @@ The 1.x line was **additive / off-by-default** — every release stayed byte-ide
## Versioning guidelines
-- **Bump MINOR** (the middle digit — e.g. `vMAJOR.MINOR.0`) for: new mapper families, new frontend features, new platforms (e.g. mobile), new input devices — anything backwards-compatible that adds capability.
-- **Bump PATCH** (the last digit — e.g. `vMAJOR.MINOR.PATCH`) for: bug fixes, accuracy refinements, dependency bumps, and documentation that does not change behavior.
-- **Bump MAJOR** (`vMAJOR.0.0`) for either of two things. (1) An incompatible public-API break or a save-state-format break that cannot migrate — exactly what **v2.0.0 "Timebase"** did (ADR 0028 bumped the `.rns`/`.rnm` epochs). (2) The first release of a **new deliverable class**, verified to the standard that class requires — the first hardware-verified FPGA core ([ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)), which [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) moved from v3.0.0 to a later v3.x. v3.0.0 itself is MAJOR under (1), for ADR 0042's API and save-state breaks. A new *host* for the same emulator (mobile, libretro) stays MINOR.
+- **Bump MINOR** (the middle digit — e.g. `vMAJOR.MINOR.0`) for: new mapper families, new frontend features, new platforms (e.g. mobile), new input devices — anything that adds capability without breaking the public Rust API. It may carry a documented format break.
+- **Bump PATCH** (the last digit — e.g. `vMAJOR.MINOR.PATCH`) for: bug fixes, accuracy refinements, dependency bumps, and documentation that does not change behavior. It may carry a documented format break when the fix needs one.
+- **Bump MAJOR** (`vMAJOR.0.0`) for either of two things. (1) An incompatible break of the **public Rust API** — the surface defined at the end of this section. (2) The first release of a **new deliverable class**, verified to the standard that class requires — the first hardware-verified FPGA core ([ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)), which [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) moved from v3.0.0 to a later v3.x, numbered after the board session and no later than v4.0.0. v2.0.0 "Timebase" and v3.0.0 "Cornerstone" were MAJOR under the rule in force at the time, which counted save-state breaks too. A new *host* for the same emulator (mobile, libretro) stays MINOR. **v4.0.0** is planned as MAJOR under (1): the remaining public enums become `#[non_exhaustive]` ([`v4.0.0-plan.md`](to-dos/plans/v4.0.0-plan.md)).
### Breaking-change policy
-- Public-API and save-state-format breaks are MAJOR bumps and must be documented in `CHANGELOG.md` with a migration note. A MAJOR bump for a new deliverable class carries no break by itself; any break it also carries still needs its migration note.
-- Save-state cross-version compatibility is best-effort (tagged per-chip sections with a version byte); the on-disk `.rnm` movie format and the public `rustynes-core` API are the stable surfaces.
-- **What "public API" means here** (written down at v2.9.0's review, which found it unwritten). The SemVer surface is the public API of `rustynes-core`, including the chip-crate types it re-exports, together with the `.rns` and `.rnm` formats. Every other crate's public items are internal to this repository and may change in any release: `rustynes-frontend`, `rustynes-hdpack`, `rustynes-script`, `rustynes-ra`, `rustynes-mobile`, `rustynes-libretro` and the rest. That holds because no crate here is published to crates.io, so nothing outside this workspace resolves against them. Their consumers are this repository's own binaries and apps. What the libretro core exposes is the libretro C ABI, which is libretro's contract, not ours. Such changes are still recorded in `CHANGELOG.md` when a user could notice them. This states the existing practice: v2.3.3, a PATCH, changed a frontend `pub fn` signature and deleted a public frontend field, and v2.9.0 changed `HdAudioTrack::pcm` and `ScriptHost::submit`.
+- **Changed 2026-10-07 (maintainer decision D2, [`v3.1-to-v4.0-line-plan.md`](to-dos/plans/v3.1-to-v4.0-line-plan.md)).** Until then this said public-API **and save-state-format** breaks were MAJOR bumps. Practice had already left it: v2.9.5 raised `PPU_SNAPSHOT_VERSION` in a minor, v2.9.8 refused every older state and movie in a minor, v2.9.9 moved `.rnm` to format 4 in a minor, and v3.0.1 raised `EMULATION_EPOCH` in a patch. The rule now matches the practice, by the maintainer's standing preference for the enduring design over compatibility.
+- **Format breaks are allowed in any release** — save states (`.rns` sections), `.rnm` movies, the netplay protocol and `EMULATION_EPOCH` — provided the CHANGELOG entry and the release notes say what breaks and what the user sees (a clear refusal with a reason, never a silent misread; ADR 0028, ADR 0045). A MAJOR's release notes restate every break since the previous MAJOR.
+- **Public Rust API breaks are MAJOR bumps** and need a migration note in `CHANGELOG.md`. A MAJOR bump for a new deliverable class carries no break by itself; any break it also carries still needs its migration note.
+- Save-state cross-version compatibility is best-effort (tagged per-chip sections with a version byte); the public `rustynes-core` API is the stable surface.
+- **What "public API" means here** (written down at v2.9.0's review, which found it unwritten). The SemVer surface is the public API of `rustynes-core`, including the chip-crate types it re-exports. The `.rns` and `.rnm` formats are versioned surfaces whose breaks are documented rather than MAJOR triggers (since 2026-10-07; they were part of the SemVer surface before). Every other crate's public items are internal to this repository and may change in any release: `rustynes-frontend`, `rustynes-hdpack`, `rustynes-script`, `rustynes-ra`, `rustynes-mobile`, `rustynes-libretro` and the rest. That holds because no crate here is published to crates.io, so nothing outside this workspace resolves against them. Their consumers are this repository's own binaries and apps. What the libretro core exposes is the libretro C ABI, which is libretro's contract, not ours. Such changes are still recorded in `CHANGELOG.md` when a user could notice them. This states the existing practice: v2.3.3, a PATCH, changed a frontend `pub fn` signature and deleted a public frontend field, and v2.9.0 changed `HdAudioTrack::pcm` and `ScriptHost::submit`.
## Accuracy milestones (met)
diff --git a/docs/STATUS.md b/docs/STATUS.md
index cd59d845b..5744c484d 100644
--- a/docs/STATUS.md
+++ b/docs/STATUS.md
@@ -1,6 +1,6 @@
# RustyNES — Project Status Matrix
-> **Current release: v3.0.0** (2026-10-06) — **"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"** (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.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
> 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.
@@ -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, 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 3% adoption bar** — a same-session A/B against a ±0.7% noise floor gives −1.3%/−1.5% on rendering-*disabled* content and +0.2%/+0.4% on the rendering-heavy case that dominates real play. Kept behind a flag rather than deleted; compile-time rather than runtime because the cost *is* the per-dot guard. With it off the field, guard and handler are all absent. See `docs/performance.md` §P2. |
| `commercial-roms` | `rustynes-test-harness` | off | 60-ROM regression bisect harness against user-supplied dumps at `tests/roms/external/`. Snapshots committed; ROM dumps gitignored. Enables `cargo test --features test-roms,commercial-roms --test external_real_games`. |
-| `cpu-implied-dummy-reads` *(removed)* | — | **removed -> default** | **Historical (Sprint 2.3, v1.2.0); the flag no longer exists and the behaviour is UNCONDITIONAL.** It gated canonical cycle-2 PC dummy reads for the 23 implied/accumulator/transfer/flag opcodes per the nesdev 6502 cycle reference, default-off pending the DMC get/put scheduler co-fix (ADR 0007), because with the flag on in isolation the `Implied Dummy Reads` AccuracyCoin test did not flip to PASS. Both the flag and the `cfg` on `Cpu::implied_dummy_read` are gone -- every build performs the dummy read, and that entry passes on the current default build. **This row said `off` for releases after the promotion**, which is the worse direction for a stale row to drift in: it described shipped default behaviour as disabled and invited someone to enable a fix already on. Now gated by `feature_flag_audit.rs`, which found it. |
+| `cpu-implied-dummy-reads` *(removed)* | — | **removed → default** | **Historical (Sprint 2.3, v1.2.0); the flag no longer exists and the behaviour is UNCONDITIONAL.** It gated canonical cycle-2 PC dummy reads for the implied/accumulator/transfer/flag opcodes (22 official opcodes plus the six one-byte unofficial NOPs: 28 opcode values across the 23 match arms that call `Cpu::implied_dummy_read`) per the nesdev 6502 cycle reference, default-off pending the DMC get/put scheduler co-fix (ADR 0007), because with the flag on in isolation the `Implied Dummy Reads` AccuracyCoin test did not flip to PASS. Both the flag and the `cfg` on `Cpu::implied_dummy_read` are gone -- every build performs the dummy read, and that entry passes on the current default build. **This row said `off` for releases after the promotion**, which is the worse direction for a stale row to drift in: it described shipped default behaviour as disabled and invited someone to enable a fix already on. Now gated by `feature_flag_audit.rs`, which found it. |
| `dmc-get-put-scheduler` *(removed)* | — | **removed** | **Historical (v2.0.0-era); the flag was deleted.** Engine-lineage Phase 8 Sprint 3 (v1.2.0). Replaces the v1.1.0 phase-agnostic "noop loop + compensating delays" DMC scheduler in `rustynes-core::bus::service_dmc_dma` with Mesen2's canonical get/put cycle alternation model (`NesCpu.cpp:399-447`). Default-off via parallel-implementation pattern (ADR 0007). This parallel experiment reached only **6/10** on the AccuracyCoin DMA cluster (4 failures in the DMC abort path) and was **superseded and removed**: the default master-clock core closes that DMA cluster **10/10** (see the AccuracyCoin 100% breakdown above). |
| `mc-r1-full-cpu` *(removed)* | — | **removed → default** | **Historical (v2.0.0-era); the flag no longer exists.** This was the v2.0 master-clock umbrella (W3-Stage-4 promotion, 2026-06-10) that reached **AccuracyCoin 100.00% (139/139)** on this one flag; it has since been **promoted to the default core and deleted** — RustyNES v1.0.0 ships this behaviour unconditionally (it is the only scheduler). The composition it bundled, for the record: composes the R1 floor substrate (substrate + dmc-idle-halt + unified APU clock + one-clock parity + stack/implied dummy reads + the promoted DMC-abort stack) PLUS the Stage-4 fold: `mc-r1-branch-poll-points` (Interrupt-flag-latency), `mc-ppu-2007-render-buffer` (`$2007` Stress), `mc-r1-dmc-delayed-4015` (the delayed-`$4015` status on the unified single-driver DMA engine at the breakthrough parity — DMC+OAM / Explicit + Implicit Abort / Delta-Mod / Implied-Dummy), and `mc-r1-oam-dma-reg-window` (APU Register Activation). nestest 0-diff; cpu_interrupts_v2 5/5 strict; SH\* 6/6; save-state round-trips hold (the gated state is serialized). Scope: NTSC-only (PAL/Dendy frame-structure tests ignored under the flag); the `audio_tests` corpus is default-build-only (R1 changes DMC audio timing by design). Default-build promotion is the later Phase-7/F program. See `docs/audit/v2.0-stage4-promotion-2026-06-10.md`. |
diff --git a/docs/adr/0032-vs-dualsystem-desktop-presentation.md b/docs/adr/0032-vs-dualsystem-desktop-presentation.md
index 6a0c19124..dbea0d5f0 100644
--- a/docs/adr/0032-vs-dualsystem-desktop-presentation.md
+++ b/docs/adr/0032-vs-dualsystem-desktop-presentation.md
@@ -58,3 +58,23 @@ misread. The slot grid shows a cabinet slot without a thumbnail, because
Run-ahead, rewind, netplay and TAS stay out of dual mode (`T-PS-dual-runahead`,
`T-PS-dual-netplay`), and so do the debugger and HD packs. The overclock is not
applied to a cabinet either (v2.9.7).
+
+## Amendment (2026-10-07): rewind and run-ahead in dual mode
+
+The maintainer decided to lift two of Decision 4's exclusions: **rewind and
+run-ahead** (decision D27 in
+[`v3.1-to-v4.0-line-plan.md`](../../to-dos/plans/v3.1-to-v4.0-line-plan.md),
+scheduled for v3.1.0 as `T-PS-dual-runahead`).
+
+The 2026-09-30 amendment supplies the means. The "RVSD" container already
+snapshots both consoles and the latch that wires them, and restores them
+atomically. Rewind and run-ahead are built on that container rather than on a
+single `Nes`.
+
+The gate:
+
+- a rewind across a cabinet frame restores both framebuffers byte-identically;
+- run-ahead gives the same output as a run without it.
+
+Netplay and TAS (`T-PS-dual-netplay`), the debugger and HD packs stay out of
+dual mode.
diff --git a/docs/adr/0035-rustynes-is-permanently-non-commercial.md b/docs/adr/0035-rustynes-is-permanently-non-commercial.md
index 72af2e359..3c8262edd 100644
--- a/docs/adr/0035-rustynes-is-permanently-non-commercial.md
+++ b/docs/adr/0035-rustynes-is-permanently-non-commercial.md
@@ -68,3 +68,22 @@ Concretely, in v2.2.6 "Almanac":
reversing this ADR — which is the intended bar.
- **Follow-up:** `docs/originality-and-provenance.md` and `NOTICE` disclose the TriCNES
behavioral-calibration caveat (see ADR 0030) as part of the same honesty pass.
+
+## Amendment (2026-10-07): when the free store listings happen
+
+The Decision allowed a free store listing as an unversioned later step, and the
+v2.9.4-to-v3.0.0 line plan placed it "after the v3.x hardware release". The
+maintainer has now placed it (decision D18 in
+[`v3.1-to-v4.0-line-plan.md`](../../to-dos/plans/v3.1-to-v4.0-line-plan.md)):
+**mobile signing and the store listings come at about v3.9, right before
+v4.0.0's final development, test and release activities.**
+
+That covers:
+
+- Android developer verification, which also applies to sideloaded apps and is
+ global from 2027;
+- iOS signing, so that TestFlight uploads run;
+- the Google Play, F-Droid or IzzyOnDroid, and App Store listings.
+
+Nothing in the Decision changes. Every listing is free, with no ads, no
+tracking and no paid unlock, and the `foss`/`play` flavour split stays.
diff --git a/docs/adr/0036-relicense-gplv3-derivative-work.md b/docs/adr/0036-relicense-gplv3-derivative-work.md
index 7b1bfc467..d0d2c2550 100644
--- a/docs/adr/0036-relicense-gplv3-derivative-work.md
+++ b/docs/adr/0036-relicense-gplv3-derivative-work.md
@@ -97,3 +97,20 @@ component is "or-later" and no incorporated component is v3-only.
emulation-core code change, and the release checks verify it: AccuracyCoin passes
141/141 and nestest is 0-diff. `docs/STATUS.md` is authoritative for the pass
counts.
+
+## Amendment (2026-10-06, v3.0.1): the terms of earlier releases
+
+The first Consequences bullet says distributors who relied on the permissive terms
+of prior tagged releases "keep those terms *for those releases*". That asserts the
+earlier `MIT OR Apache-2.0` grant was effective for those releases, which sits
+uneasily with this ADR's own Context: the prior dual license "was not a license the
+project was entitled to offer". Whether those terms held for the GPL-derived code is a
+legal question this ADR does not decide, and the sentence should not have read as if
+it did.
+
+The position of record is the neutral wording `docs/originality-and-provenance.md`
+already uses: source released in prior tagged releases remains under whatever terms
+accompanied it at the time, and that history cannot be retroactively changed, but the
+current tree and every release from v2.2.9 onward is GPL-3.0-or-later. Nothing here
+states whether the earlier permissive terms were valid for the GPL-derived portions.
+The bullet above is left as written, as the record of what was decided on 2026-08-04.
diff --git a/docs/adr/0037-mister-fpga-core-independent-hdl-implementation.md b/docs/adr/0037-mister-fpga-core-independent-hdl-implementation.md
index d6688df48..87ad30aa6 100644
--- a/docs/adr/0037-mister-fpga-core-independent-hdl-implementation.md
+++ b/docs/adr/0037-mister-fpga-core-independent-hdl-implementation.md
@@ -135,3 +135,32 @@ GPL-2.0-**only**, the combined bitstream is undistributable and the RTL must be
GPL-2.0-or-later instead. Tabulating every licence header in `sys/` is an hour of
work and must happen **before any RTL is written** -- relicensing after 10k lines
exist is precisely the failure `docs/originality-and-provenance.md` documents.
+
+## Amendment (2026-10-07): when the board and the oracle disagree, and the Provenance-headered families
+
+Two maintainer decisions taken while the v3.1 to v4.0 line was planned
+([`v3.1-to-v4.0-line-plan.md`](../../to-dos/plans/v3.1-to-v4.0-line-plan.md),
+D13 and D15). Both extend "Accepted risk: the oracle can be wrong" now that a
+board will run the core.
+
+**D13: the board against the oracle.**
+
+- When the SuperStation One contradicts the oracle (palette, video timing,
+ OAM corruption, APU power-on phase, or anything else), the board wins **only
+ where it agrees with documented hardware behaviour**. A board result with no
+ documentary support is recorded, not adopted.
+- When it wins, the RTL and the oracle are fixed **in the same release**, red
+ first, with an `EMULATION_EPOCH` rise (ADR 0045).
+- Each case gets its own dated amendment to this ADR, naming the measurement.
+
+**D15: families whose oracle source carries a `// Provenance:` header.**
+
+- These are N163, FME-7/5B, VRC7 with its OPLL, Bandai FCG, FDS, Vs. System,
+ and any other family the command finds: `grep -rln "^// Provenance:" crates`.
+- Their RTL is written from rungs 1-3 of the escalation ladder: documentation,
+ the Internet, and black-box comparison of outputs.
+- **Rung 4, reading the derived oracle region, needs a dated amendment to this
+ ADR naming the maintainer as the authoriser, one per family, before anything
+ is read.** The derivation is then declared in the sibling: a site comment,
+ its provenance document, and `NOTICE`.
+- No rung-4 authorisation is given by this amendment.
diff --git a/docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md b/docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md
index 22589fced..00f5a39cf 100644
--- a/docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md
+++ b/docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md
@@ -114,3 +114,73 @@ release notes restate every break since v2.x.
Decision 1 (an API major plus a release-candidate core, both bitstreams
labelled not hardware-verified) and Decision 4 (only the libretro sync goes
upstream) are unchanged.
+
+## Amendment (2026-10-07, after v3.0.0): the hardware release's number, and v4.0.0
+
+The maintainer settled the question Decision 2 left open, and defined the next
+MAJOR, while the line after v3.0.0 was planned
+([`v3.1-to-v4.0-line-plan.md`](../../to-dos/plans/v3.1-to-v4.0-line-plan.md),
+decisions D1-D3).
+
+- **D1: the hardware-verification release is numbered after the board session,
+ and no later than v4.0.0.** It is planned as the slot "HW", after v3.1.0 and
+ before any feature RTL. The number is chosen with the session's evidence in
+ hand: the next free minor if the fixes are small, or as the maintainer then
+ decides if the session finds an L-sized re-target (the FPGA device question,
+ HW-A8). ADR 0041's "new deliverable class" trigger stays available to it.
+- **D2: a format break alone no longer makes a MAJOR.** Save-state, movie,
+ netplay-protocol and `EMULATION_EPOCH` breaks may land in any release with
+ notes; MAJOR is a public Rust API break or a new deliverable class.
+ `VERSION-PLAN.md` was rewritten to match. This states the practice of v2.9.5,
+ v2.9.8, v2.9.9 and v3.0.1.
+- **D3: v4.0.0 is the remaining public enums made `#[non_exhaustive]`, plus
+ MiSTer feature parity.** The enum change is the half of v3.0.0's
+ `T-API-EXTENSIBLE` that v3.0.0 did not take. Parity covers save states,
+ cheats, PAL/Dendy, FDS with expansion audio, the Zapper, Four Score, and
+ mapper families covering the incumbent core's licensed-library list.
+
+Decisions 1, 3 and 4 above stand as history. This amendment changes no shipped
+artefact.
+
+## Amendment (2026-10-07, later the same day): the hardware release moves to the end of v3.9.x (D29)
+
+### Context
+
+The amendment above placed the hardware-verification release right after
+v3.1.0 and before any feature RTL, so the board would verify the six-mapper core
+of v3.0.x and every MiSTer feature after it would be built on a verified base.
+On reading the drafted line, the maintainer asked for the opposite: the whole of
+the hardware release, the board session and the mobile device run, toward the
+end of v3.9.x, "so as much as possible has been implemented, integrated, fixed,
+enhanced, improved and optimized" before it runs.
+
+### Decision
+
+**D29.** The hardware release is the last release of v3.9.x, after v3.9.0's
+RTL feature freeze and release-candidate pair, and immediately before v4.0.0.
+It still carries no feature RTL of its own, only board fixes, each a simulation
+gate first where simulation reaches it. Its number is still chosen after the
+session (D1); if its fixes are large it folds into v4.0.0. The mobile device run
+moves with it (Decision 2), and the store listings (ADR 0035's 2026-10-07
+amendment, D18) follow that run.
+
+This supersedes only the ordering clause of the amendment above ("after v3.1.0
+and before any feature RTL"). Its D1 numbering rule, D2 and D3 stand.
+
+### Consequences
+
+- The MiSTer features of v3.2.0 to v3.8.0 land verified in simulation only, and
+ rung 6 stays open under them; the feature phases need a green ladder, not a
+ board.
+- The board verifies the near-parity core, so v4.0.0 ships a hardware-verified
+ parity core, and the submission decision (D10) is taken just before v4.0.0
+ with nearly the whole feature delta in hand.
+- The FPGA device (HW-A8) and SDRAM part (HW-A9) are read last. A wrong part
+ means an L-sized re-target of the parity core rather than the six-mapper core.
+ An optional read-only reading of the chip markings before then is recorded as
+ an open decision, not planned.
+- The off-die build is the headline from v3.3.0 (D4) while its SDRAM
+ constraints stay provisional until HW-O6, at the end.
+- Board-against-oracle corrections (D13) cluster just before v4.0.0, each with
+ an oracle change and usually an `EMULATION_EPOCH` rise.
+- The full risk list is in the line plan's "Risks of D29".
diff --git a/docs/agents/accuracy-oracle.md b/docs/agents/accuracy-oracle.md
index e5d5ba44b..1e2907167 100644
--- a/docs/agents/accuracy-oracle.md
+++ b/docs/agents/accuracy-oracle.md
@@ -11,7 +11,7 @@
> written to be re-checkable, not to be taken on trust.
- **`pre-commit run --all-files` REWRITES vendored/immutable trees — use `--files ` or a single named hook instead.** `trailing-whitespace` / `end-of-file-fixer` / `mixed-line-ending` *modify* files and, before PR #320, had no `exclude` at all: one `--all-files` run silently reformatted **41 files** across the vendored TriCNES C#, vendored rcheevos C, `ref-docs/`, an upstream font licence, and upstream test-ROM READMEs — destroying exactly the byte-identical-to-upstream property those trees exist for. `.markdownlintignore` covered them for markdownlint only. #320 added a shared `exclude` anchor across the three rewriting hooks, scoped deliberately **narrower** than `.markdownlintignore`: only content we did not author. Frozen-but-ours trees (`docs/archive/`, `to-dos/plans/`) stay in scope, since the invariant is "don't rewrite what we didn't write". If it happens anyway, revert **only** the unintended paths (never a blanket `git checkout`, and never including your own edits).
-- **THE VENDORED TriCNES AND AccuracyCoin TREES ARE NOT OURS TO PATCH, and a reviewer will suggest patching them (2026-09-19).** `crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/` is `100thCoin/TriCNES` **byte-identical to upstream** (`94f1b117`; verify by recursive diff against a fresh clone, expect `0 differing files`), and `tricnes-harness-src/` is that same source plus **exactly 88 instrumentation lines** — an invariant worth asserting after any sync, because it is what proves the instrumentation survived and nothing else moved. TriCNES is the **oracle** the core is diffed against; patching emulator *behaviour* into it would mean the thing we call TriCNES is no longer TriCNES, silently, and in exactly the direction that flatters us. CodeRabbit raised a real observation on this tree (`Reset()` assigns `CPU_SYNC` and `DoReset` and does **not** clear `DoNMI`) and the correct answer was to **decline the fix and record it as an upstream candidate**, not to apply it. The route for a genuine defect is an upstream report — which works: `46199ae4` in AccuracyCoin is upstream's fix for a defect reported from here. The same applies to `tests/roms/accuracycoin/` — the ROM is upstream's artifact, and our only local products are `SOURCE_CATALOG.tsv` (regenerated by script, never hand-edited) and the `sub-tests/` ROMs (built by `build_sub_test_rom.py`, which needs `--wine /usr/bin/wine`).
+- **THE VENDORED TriCNES AND AccuracyCoin TREES ARE NOT OURS TO PATCH, and a reviewer will suggest patching them (2026-09-19).** The TriCNES source was vendored until v3.0.1 and now lives outside the repository (`~/reference-oracles/TriCNES` at `94f1b117`, `~/reference-oracles/TriCNES-rustynes-harness`; guardrails section 3a), and the rule moved with it. The upstream copy is `100thCoin/TriCNES` **byte-identical to upstream** (checked file-for-file on removal, 2026-10-07), and the harness is that same source plus **exactly 88 instrumentation lines** — an invariant worth asserting after any sync, because it is what proves the instrumentation survived and nothing else moved. TriCNES is the **oracle** the core is diffed against; patching emulator *behaviour* into it would mean the thing we call TriCNES is no longer TriCNES, silently, and in exactly the direction that flatters us. CodeRabbit raised a real observation on this tree (`Reset()` assigns `CPU_SYNC` and `DoReset` and does **not** clear `DoNMI`) and the correct answer was to **decline the fix and record it as an upstream candidate**, not to apply it. The route for a genuine defect is an upstream report — which works: `46199ae4` in AccuracyCoin is upstream's fix for a defect reported from here. The same applies to `tests/roms/accuracycoin/` — the ROM is upstream's artifact, and our only local products are `SOURCE_CATALOG.tsv` (regenerated by script, never hand-edited) and the `sub-tests/` ROMs (built by `build_sub_test_rom.py`, which needs `--wine /usr/bin/wine`).
- **A MISSING ACCURACYCOIN SUB-TEST ROM CAN BE ONE BYTE AWAY (v2.6.20).** `scripts/accuracycoin-build/build_sub_test_rom.py` injects the suite and test indices as plain `LDY #suite` / `LDX #test` immediates, and they **survive into the assembled binary**. In `advanced-sprite-eval-frozen-oam2-increment.nes` they sit at file offset `0x10AB` (`A0 15`) and `0x10B5` (`A2 02`), so a sibling sub-test is that ROM with one byte changed — no `.asm`, no build. Confirm the index from TWO sources before patching (`BUILD-PROVENANCE.tsv` records the base ROM's suite/test, and `SOURCE_CATALOG.tsv`'s row ORDER within the suite gives the sibling's index), then validate with `validate_sub_test_rom ` before trusting it. For `Misaligned OAM2 Address` — the LAST catalog row, so 134 M cycles and ~35 minutes per battery attempt — this turned the loop into **30 seconds**, which is the only reason two experiments were affordable and the second is what found the real defect. **Wire the instrument before debugging the entry**, and note the corollary: `$0050`-style scratch bytes survive a battery run only because the test is last, so truncating the run to save time gives you whichever test wrote that byte last.
- **A test ROM's own source is the SPECIFICATION, and checking whether it is reachable costs one command.** Three AccuracyCoin entries resisted a full working session of tracing and hypothesis in v2.6.4. AccuracyCoin is **MIT-licensed and its assembly source is one `curl` away**; it carries a prose explanation of every assertion, written by the author who chose the stimulus, and it settled all three in minutes — naming a rule the nesdev pages do not state at all (`$4015` reads are internal to the 2A03, so the data bus is not driven), giving the exact failing stimulus (`LDX #$16 / LDA $40FF,X`, which matched a trace divergence found independently), and distinguishing the neighbouring assertions a broad "fix" would break. The v2.6.4 plan's own note that the source "is not vendored" is true of this repository and had been read as *unavailable*; they are not the same statement. **A test ROM is stimulus, not a reference implementation, so reading it raises no firewall question** — check its licence, then read it before theorising about its verdict. The same holds for blargg's `readme.txt`, which states two APU rules the wiki does not. **And decode its failure codes from the macro, never by inference**: `TEST_Fail` reports `(ErrorCode << 2) | 2` and the runner sets `ErrorCode` to **1** before every routine, so `Fail(N)` names test N one-based — read as a zero-based index it is off by one, and in v2.6.4 that made a **regression** (test 7 -> test 5) read as *progress*, a description that reached a code comment before the macro was read. It also retires v2.6.3's reading that six entries "sharing one failure code" implied one shared cause: the code indexes within one routine, so two entries sharing it share nothing.
diff --git a/docs/agents/ci-and-release.md b/docs/agents/ci-and-release.md
index dcd451ad8..e82c5a75e 100644
--- a/docs/agents/ci-and-release.md
+++ b/docs/agents/ci-and-release.md
@@ -15,7 +15,7 @@
- **CI security hardening (PRs #319 + #320, 2026-07-21, merged `85ee20db` / `a69200ef`).** `persist-credentials: false` on **all 19** `actions/checkout` sites (18 in #319; the last one, `release-auto.yml`, once its tag check stopped needing Git credentials — see the next bullet), because build scripts / proc macros / test binaries / Gradle scripts / MkDocs all execute unreviewed PR code that could read the token out of `.git/config`. Facts worth not re-deriving: `.github/actions/rust-setup` performs **no checkout of its own** (so call-site hardening is complete coverage); `persist-credentials` does **not** affect the `gh` CLI or API calls, only git network ops using the stored credential — which is why `gh release create`, `softprops/action-gh-release`, and `fastlane match` (a *different* repo, own `MATCH_GIT_*` secrets) all look like they need it and don't; and the highest-exposure job is **`web.yml`'s `build`**, not any `ci.yml` job, because `web.yml` declares `pages: write` + `id-token: write` at *workflow* level. There are now **no exceptions**: `release-auto.yml`'s `prepare` was the last holdout (it needed `git ls-remote origin` for the tag check), and that check is now a `gh api` call, so its checkout joined the sweep.
- **The release tag-existence check is FAIL-CLOSED — keep it that way.** `release-auto.yml`'s `decide` step queries `gh api repos/$GITHUB_REPOSITORY/git/matching-refs/tags/`, NOT `git/ref/tags/`: `matching-refs` answers "absent" with HTTP 200 + an empty array, so a genuine miss can never be confused with a lookup failure and no error-body parsing is needed. It matches by **prefix**, so the exact ref is compared in `jq` — this is load-bearing, not defensive: `v2.2` prefix-matches two real tags (`v2.2.0`, `v2.2.1`) while exact-matching none. Under `shell: bash` + `set -euo pipefail` both a `gh` failure and a non-array body abort the job (verified: exit 1 and exit 5 respectively). The old `git ls-remote ... >/dev/null 2>&1` read *any* non-zero exit as "tag absent", so a blip would try to re-release a shipped version. **Never reintroduce a form where a failed lookup is indistinguishable from "absent."** (Note when testing shell behavior locally: this harness's shell is zsh, whose `set -e` semantics for `var="$(cmd)"` differ from bash's — test with `bash -c` or you will get a false result.)
- **`dtolnay/rust-toolchain` is SHA-pinned in `.github/actions/rust-setup` — never put it back on a branch ref.** It is `@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1`; it used to be `@master`, a branch that advances on every upstream commit, so each run silently resolved to whatever HEAD was. That composite feeds 12 of the repo's 19 checkouts, including `release.yml` (`contents: write`, builds the shipped binaries) and `web.yml` (`pages: write` + `id-token: write`), and it is the action that *installs the compiler* — the hardest place to notice a compromise. The trailing `# v1` is the form Dependabot's `github-actions` ecosystem (enabled in `.github/dependabot.yml`) reads to keep the pin current, so don't strip it. The other ~12 actions remain on `@vN` tags by choice: those are not expected to move, and blanket SHA-pinning is a maintenance-policy decision, not a drive-by fix.
-- **There is ONE toolchain, `rust-toolchain.toml`'s `channel`, and no version literal anywhere in `.github/` — don't add one.** `.github/actions/rust-setup` parses the channel out of that file and fails closed if it can't, so a toolchain bump is a one-line edit there. Pass the composite's `toolchain:` input only to install something *deliberately* different from the project pin. **The resolver is table-scoped `awk` on purpose — do NOT "simplify" it back to a one-line `sed`.** Matching the first `channel = "..."` *anywhere* in the file (the first implementation, caught in review on PR #322) resolves `nightly` if any other table carries a `channel` key ahead of `[toolchain]` — silently installing the very toolchain this setup exists to keep out, while the step still reports success. `awk` rather than `tomllib` because the step runs on Windows and macOS runners too and Python ≥3.11 is not a safe assumption there; only double-quoted TOML strings are accepted, and anything else (missing table, single-quoted value, empty file) aborts the job rather than being guessed at. The old `stable` default was misleading rather than wrong: `rust-toolchain.toml` is a directory override that outranks the `rustup default` the action performs, so every job was already compiling on 1.96.0 (rustup logs `overridden by .../rust-toolchain.toml`) — `stable` just downloaded a second toolchain nothing used and made the workflows *read* as though they tested latest stable, which they never did. **Nightly is used in exactly one place, not a gate:** `cargo fuzz` (hard requirement — libFuzzer's sanitizer flags are nightly-only). If you think a CI job needs nightly, it doesn't.
+- **There is ONE toolchain, `rust-toolchain.toml`'s `channel`, and no version literal anywhere in `.github/` — don't add one.** (The libretro buildbot needs the version written once more, as `.gitlab-ci.yml`'s `RUSTUP_TOOLCHAIN`; `libretro-cross` parses it with the same table-scoped, fail-closed `awk` and fails if it differs from the channel. It briefly held 1.96.0 during v3.0.1.) `.github/actions/rust-setup` parses the channel out of that file and fails closed if it can't, so a toolchain bump is a one-line edit there. Pass the composite's `toolchain:` input only to install something *deliberately* different from the project pin. **The resolver is table-scoped `awk` on purpose — do NOT "simplify" it back to a one-line `sed`.** Matching the first `channel = "..."` *anywhere* in the file (the first implementation, caught in review on PR #322) resolves `nightly` if any other table carries a `channel` key ahead of `[toolchain]` — silently installing the very toolchain this setup exists to keep out, while the step still reports success. `awk` rather than `tomllib` because the step runs on Windows and macOS runners too and Python ≥3.11 is not a safe assumption there; only double-quoted TOML strings are accepted, and anything else (missing table, single-quoted value, empty file) aborts the job rather than being guessed at. The old `stable` default was misleading rather than wrong: `rust-toolchain.toml` is a directory override that outranks the `rustup default` the action performs, so every job was already compiling on 1.96.0 (rustup logs `overridden by .../rust-toolchain.toml`) — `stable` just downloaded a second toolchain nothing used and made the workflows *read* as though they tested latest stable, which they never did. **Nightly is used in exactly one place, not a gate:** `cargo fuzz` (hard requirement — libFuzzer's sanitizer flags are nightly-only). If you think a CI job needs nightly, it doesn't.
- **A SKIPPED job satisfies `CI success`, and `CI success` is `main`'s only required check.** The ruleset `Protect (Default)` requires exactly one status context. That job runs `if: always()` and fails on `contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')` — `skipped` is in **neither** list. So a gate that did not run reports a pass for a property it never tested. This is not theoretical: `test-roms` (the accuracy battery) was FULL-run only, so an accuracy regression could not be caught on the PR that caused it — it landed, turned `main` red, and needed a second PR (#396 introduced the vector move, #403 fixed it; **both PRs were correct, the process was not**). **There is no merge queue** — verified, no `merge_group` event appears in the run history — so nothing caught it later either. v2.3.9 A5 adds an `accuracy` paths-filter (chip crates, `rustynes-core`, `rustynes-gamedb`, `rustynes-test-harness`, `tests/`) so the battery runs on PRs that can actually break it; measured first, 11 of the last 40 merged PRs touch those paths, so ~72% still skip it. When judging whether a gate covers something, check three things separately: which contexts are *required*, what the aggregate treats as failure, and whether the job that tests the property actually **ran**.
- **`timeout-minutes` is NOT valid on a job that uses `uses:` — and `actionlint` is how you settle questions like that.** GitHub's workflow-syntax and reuse-workflows pages state it neither way, and a #406 review asserted the restriction was lifted in 2022. It was not: actionlint reports the key unavailable and lists the seven allowed (`name`, `uses`, `with`, `secrets`, `needs`, `if`, `permissions`), so adding one is a **hard syntax error**, not an ignored key. `release-auto.yml`'s `build` therefore cannot carry a timeout; its budget lives on the jobs inside `release.yml`. actionlint is installed and, from v2.3.9, a pinned pre-commit hook. **`.github/actionlint.yaml` has existed since v2.2.3** (it declares the self-hosted `agy` label) — extend it, never `Write` over it; doing so lost its rationale and produced a false "adding the config activated a dormant check" finding that reached a commit body before being retracted.
- **Bound every workflow job, and every network fetch inside one.** PR #400 bounded `ci.yml` and nothing else; v2.3.9 found **six** more unbounded workflows including `release-auto.yml` itself, after `Clippy Security Lints` hung **two hours** in a setup step and blocked the v2.3.7 release PR. Separately, apt provisioning hung **four times across two PRs in one day**, always in a setup/provisioning step and never in a compile or test step. A job timeout bounds the damage but cannot *notice*: a stalled fetch inside a 25-minute budget is indistinguishable from a slow job, and the run reports as `cancelled`, which reads as noise. `.github/scripts/apt-install-retry.sh` adds a per-command `timeout` plus three attempts, and warns on every attempt including ones that succeed — a run needing three and one needing one are identical in the conclusion, and that difference is the early warning.
diff --git a/docs/agents/libretro.md b/docs/agents/libretro.md
index 388c0f08d..36f81077a 100644
--- a/docs/agents/libretro.md
+++ b/docs/agents/libretro.md
@@ -20,6 +20,8 @@
- **Libretro buildbot CI (issue #311) shipped in PR #312 (2026-07-19/20, `b49dd1e0`) — and the upstream half is now DONE; the "stays OPEN by design" instruction this bullet used to lead with is RETIRED, see below.** `.gitlab-ci.yml` + a `[lib] name = "rustynes"` naming-collision fix + `[workspace] default-members` + RA memory-maps + an FDS load-path fix/disk-control + native Game Genie cheats all shipped. TAS and Netplay needed no new libretro-side wiring — RetroArch's own rollback/movie machinery already rides the existing `on_serialize`/`on_unserialize` hooks. **DONE as of 2026-07-21, re-measured 2026-09-20** — this bullet described the upstream half as pending for two months after it landed. Companion PRs `libretro/docs#1164` (merged 2026-07-21) and `libretro/libretro-super#2021` (merged 2026-07-24) are both in, the mirror + buildbot step the libretro team owned is complete, and `rustynes_libretro.so.zip` is on the nightly buildbot for **linux/x86_64, windows/x86_64 and apple/osx/arm64** (checked by fetching the three `latest/` listings, not by asking anyone). `dist/info/rustynes_libretro.info` upstream reads `license = "GPLv3+"`, `display_version = "v2.3.9"`. **Issue #311 is CLOSED and that is now CORRECT** — but read its timeline before citing it, because it closed twice for opposite reasons: `2026-07-19T22:28` by doublegate via a commit-body keyword (premature, reopened 27 minutes later at `22:55`), then `2026-07-21T13:07` **by `hizzlekizzle`, an upstream maintainer, by hand and with no commit id** — which is exactly the condition this bullet demanded. **The keyword rule still stands and the timeline is its evidence:** never put a closing keyword for an unfinished issue in a commit or PR body, because the first close proves GitHub acts on it instantly and the tracker then reports finished work that was not done. What retires an issue is a verified outcome — here, three buildbot listings and a maintainer's own click.
- **The libretro buildbot is a THIRD CI system with its own rules — and the pinned toolchain fights it.** The first real run (pipeline #91899, 2026-07-20) passed 1 of 10 jobs. `rust-toolchain.toml`'s `channel = "1.96.0"` makes rustup install a *fresh* toolchain inside libretro's build image, bypassing the image's pre-provisioned cross targets, so 8 jobs died on `E0463: can't find crate for core`; each job in `.gitlab-ci.yml` now runs `rustup target add ${RUST_TARGET}` (NOT added to `rust-toolchain.toml`'s `targets` — that would cost every contributor and GH Actions job ~8 extra `rust-std` downloads). The Apple jobs must use `!reference` rather than `extends` for that, because GitLab's `extends` REPLACES array keys and would silently drop the templates' `SDKROOT`/`STRIP`/`CC`/`CXX` exports. **tvOS: the upstream template's `cargo +nightly build -Zbuild-std` override is OBSOLETE — don't reinstate it.** It dates from when `aarch64-apple-tvos` was tier 3 with no distributed `rust-std`; the target has since been promoted and rustup ships a complete prebuilt std **including `panic_abort`** (verified on the pinned 1.96.0: `rustup target add aarch64-apple-tvos` gives 26 rlibs and the crate `cargo check`s clean, bindgen included). Our job overrides `script` back to `!reference [.libretro-rust-apple-base, script]`, putting tvOS on the same pinned stable as every other job. That one change dissolved THREE stacked workarounds the `+nightly` path had forced: a nightly-channel reinstall (`+nightly` outranks both `rust-toolchain.toml` and `RUSTUP_TOOLCHAIN`, so the job rode the image's stale 1.94.0-nightly, below our MSRV); `CARGO_PROFILE_RELEASE_PANIC=unwind` (bare `-Zbuild-std` omits `panic_abort`, and `CARGO_UNSTABLE_BUILD_STD` does NOT override the hardcoded crate list — the CLI `-Z` flag wins); and clearing the image's `-C ar` (see the next bullet). (Since v2.8.0 that same variable is set to `unwind` again for EVERY buildbot job, tvOS included, by `.core-defs` — deliberately, for panic containment (L-1.1), not as the old tvOS workaround; `panic_abort` being available does not make the abort profile effective there.) Worth reporting upstream: every Rust core's tvOS job could drop `+nightly` the same way. **A green GitHub Actions run does not imply a green buildbot** — the `libretro-cross` CI job (the buildbot triples a Linux runner can model — at #554, 2026-09-24: 64- and 32-bit MinGW-Windows, Linux aarch64 / i686, armhf, the webOS `armv7-unknown-linux-gnueabi`, and Android/NDK; this parenthesis first said "MinGW-Windows and Android/NDK", which had been stale since the aarch64 and armhf legs landed; the Apple families are deliberately excluded, as bindgen needs a real per-target sysroot and there is no Apple SDK on a Linux runner) is the early-warning gate; before touching anything libretro-related, cross-check `cargo check --release -p rustynes-libretro --target ` locally.
- **The libretro build image injects `-C ar` into EVERY Apple job, and it is a hard error from Rust 1.97 — a bomb armed against the next MSRV bump.** The image (not the `rust-apple.yml` template, which sets no `RUSTFLAGS` at all, and not our `.cargo/config.toml`) adds `-Car=,Clink-arg=-undefined,Clink-arg=dynamic_lookup,-rpath=` to osx-x64 / osx-arm64 / ios-arm64 / tvos-arm64. `-C ar` was a deprecated no-op for years and became a **hard error in 1.97** (bisected locally: 1.93.0-nightly / 1.96.0 / 1.96.1 warn; 1.97.1 and 1.99.0-nightly error). No job trips it today — all four Apple jobs are on the pinned 1.96.0 and merely log the warning. **The day `rust-toolchain.toml` moves to 1.97+, all four fail together** — the warning lives in that file, at the line someone would edit. Discarding the flags is behaviour-preserving, not a gamble: rustc splits `-C` at the FIRST `=`, so the whole comma-joined string is swallowed as the `ar` value and those link args have never reached the linker for *any* core (cargo prints it as one argv token), and two upstream Rust cores have green tvOS jobs on the same image with the same dead token. The override works without knowing where the image sets it because cargo takes rustflags from exactly one source, first match wins: `CARGO_ENCODED_RUSTFLAGS` → `RUSTFLAGS` → `target..rustflags` → `build.rustflags` (verified locally against a global `~/.cargo/config.toml` `build.rustflags`: `RUSTFLAGS=""` removes every injected `-C`, and empty means zero flags, not one empty argument).
+- **v3.0.1 SPLIT THE TOOLCHAIN instead of disarming the `-C ar` bomb.** `rust-toolchain.toml` moved to 1.99.0; `.gitlab-ci.yml` `.core-defs` sets `RUSTUP_TOOLCHAIN: "1.96.0"` (it outranks `rust-toolchain.toml`), and every job's `rustup target add` became `rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}`. The libretro templates set no `RUSTUP_TOOLCHAIN` or rustflags (checked in `rust-apple.yml`, `rust-linux-x64.yml`, `rust-windows-x64.yml`, `rust-android-jni.yml`, `rust-webos.yml`, 2026-10-06). The seven crates the core compiles declare `rust-version = "1.96"`, and GitHub CI's `libretro-cross` reads the version out of `.gitlab-ci.yml` (no literal in `.github/`) and builds on it. **That check has already paid for itself:** clippy 1.99 rewrote `for b in self.ram.iter_mut()` (a `Box<[u8; 2048]>`) to `for b in &mut self.ram`, which 1.96 rejects (`&mut Box<[T; N]>` is not `IntoIterator` there); clippy honoured nothing about the crate's `rust-version`, and only the 1.96 build caught it (`bus.rs`, fixed as `&mut *self.ram`). The documented two-line `RUSTFLAGS` fix in `rust-toolchain.toml` would let the buildbot follow; it was not taken in v3.0.1. **CORRECTED the same day: the buildbot CAN be tested before merge.** libretro's mirror of the GitHub repo runs a pipeline for every pushed BRANCH, not only `main` (`git.libretro.com/api/v4/projects/libretro%2FRustyNES/pipelines?ref=`; the v3.0.0 cycle shows pipelines for `release/v3.0.0` and the review slices). The pushed `release/v3.0.1` got pipeline 119606 within about 20 minutes, and all 15 jobs (Apple included) passed on the 1.96 pin. The job LIST is public; job LOGS need a login (401). A review-slice branch fails every job by construction (each holds only part of a release), so expect red there and do not read it as a defect.
+- **AND v3.0.1 UNDID THE SPLIT, the same day (2026-10-07).** The research for the v3.1 roadmap found that `libretro-infrastructure/libretro-build-rust` removed the `-C` usage from the build image on 2026-09-03 (`841f3619`, "Remove -C usage as no longer supported by rust"; the rebuilt image's own pipeline passed 2026-09-23). A throwaway branch, `test/libretro-rust-1.99`, set only `RUSTUP_TOOLCHAIN: "1.99.0"`; its pipeline 119614 passed all 15 jobs, `osx-x64`, `osx-arm64`, `ios-arm64` and `tvos-arm64` included. So the pin now equals `rust-toolchain.toml` again, the seven crates inherit the workspace `rust-version`, and `libretro-cross` FAILS if the two toolchains differ, so neither can drift alone. Two lessons: the hazard lived in someone else's image, so re-check it there before planning around it; and since the image now separates its `-C link-arg` flags properly, those link arguments reach the linker for the first time. That is harmless on 119614, but it is the first thing to suspect if an Apple link changes behaviour.
- **`rust-libretro 0.3.2` is unmaintained (no commit since 2023-02) and has a MinGW bug we work around.** It casts a keycode with `cfg(target_family = "windows")`, but C enum signedness follows the *ABI*: only **MSVC** gives plain enums `int` — under **MinGW** (`x86_64-pc-windows-gnu`, what the buildbot builds) bindgen emits `c_uint` and the crate fails `E0308`. `.cargo/config.toml`'s `[env] BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu = "--target=x86_64-pc-windows-msvc"` fixes it; the generated-bindings diff is 28 lines, all enum signedness. Don't "clean up" that env var without rebuilding for `x86_64-pc-windows-gnu`.
- **The libretro `.info` RetroArch reads is a DIFFERENT FILE from this repo's, and it went stale for eleven days.** RetroArch downloads `dist/info/rustynes_libretro.info` from `libretro/libretro-super`; `crates/rustynes-libretro/rustynes_libretro.info` is an unrelated copy that nothing syncs and nothing compared. So the v2.2.9 GPL relicense reached `Cargo.toml`, `NOTICE`, `deny.toml`, the SPDX headers and the local `.info` — and **not** the file users actually see, which went on advertising "MIT OR Apache-2.0" at `display_version = v2.2.1`. Both upstream PRs had merged *exactly two weeks before* the relicense, so no sync could have carried it. **Upstream PRs are opened only on MINOR or MAJOR releases** — a `vX.Y.0` where `X` or `Y` changed. Patch releases do NOT trigger an upstream sync; the `.info` `display_version` is allowed to lag through a `v2.4.1`..`v2.4.9` run and is brought current at the next `vX.Y.0`. **Next scheduled upstream sync: v3.0.0, prepared at v2.9.9 and submitted with v3.0.0** (ADR 0043, 2026-09-29, which replaced the 2026-08-23 rule below that it would wait for the MiSTer core to be complete). History, superseded by ADR 0043: the 2026-08-23 decision deferred it past v2.5.0, superseding a 2026-08-20 decision that named v2.5.0; it held that the sync happens at whatever `vX.Y.0` the MiSTer core is fully implemented at, not at the next `vX.Y.0` on the calendar, so v2.5.0 opened no upstream PR and no release before the core was done would. Its reason, which ADR 0043 answered by labelling the v3.0.0 bitstreams not hardware-verified, was that the Fabric line's user-visible claims are about a core that does not exist yet, and an `.info` advertising a version whose headline feature is unfinished is the same class of stale declaration this bullet exists to prevent — just in the other direction. The one override is a **licence change, which syncs immediately** regardless of version — that is what this bullet's incident was about, and it stays on the same footing as a release. `crates/rustynes-test-harness/tests/libretro_info_audit.rs` pins the local file against the workspace manifest so the sync is a *copy*, never a re-derivation; it cannot see upstream, so the sync itself stays a human step. libretro `.info` uses short license tokens, not SPDX, and marks "or later" with a trailing `+` (tallied across all 316 upstream cores: `GPLv2` x100, `GPLv3` x64, `GPLv2+` x19, `GPLv3+` x5) — RustyNES is **`GPLv3+`**; a bare `GPLv3` understates it as GPL-3.0-only. Full detail + the surface table: `docs/libretro/UPSTREAM_SYNC.md`.
- **iOS/iPadOS/tvOS availability is a THIRD repo and a HARDCODED list — being on the buildbot buys nothing there.** **RESOLVED 2026-08-16** by `libretro/RetroArch#19416` (merged `76f60626984a`; verified against `master`, not the PR state — `rustynes` sits at line 268 between `reminiscence` and `sameboy`). The mechanism below is retained because it recurs for any other core and for the sibling forges, and because "in the build list" is not "installable": it ships with the next App Store RetroArch build. iOS cannot download cores (Apple bans fetching executable code), so the App Store build bundles a fixed set chosen by `pkg/apple/update-cores.sh` in `libretro/RetroArch`. That script has two lists: `allcores`, fetched *dynamically* from the buildbot directory (RustyNES is in it automatically), and `appstore_cores`, a hardcoded array (RustyNES is **absent**). The iOS/tvOS build phases run `rm -f ${SRCROOT}//modules/*.dylib` then `./update-cores.sh appstore`, so only the hardcoded list survives. One entry covers iOS + tvOS + macOS App Store. **Alphabetical order is mandatory** — `rustynes` sorts between `reminiscence` and `sameboy`; re-check the neighbours at submission time rather than trusting a line number.
diff --git a/docs/agents/review-bots.md b/docs/agents/review-bots.md
index 5afb418b9..2e8ea5ec8 100644
--- a/docs/agents/review-bots.md
+++ b/docs/agents/review-bots.md
@@ -11,7 +11,7 @@
> written to be re-checkable, not to be taken on trust.
- **CodeRabbit is configured as a 3rd PR review bot** (`.coderabbit.yaml`, added 2026-07-20 in PR #316), alongside gemini-code-assist and copilot-pull-request-reviewer — the reply-and-resolve-every-thread ceremony applies to its threads too. **It does NOT review automatically on this repository — see the next bullet.** This one said "automated" and "applies before any merge" from #316 until v2.3.9, which read as a promise that its findings were arriving; they were not. Configured `profile: assertive` (not the "chill" default) and a `tools{}`/`path_instructions`/custom-checks set audited against this repo's actual file footprint, not guessed. `tone_instructions` has a hard 250-character schema limit that fails validation silently on the CodeRabbit side — after editing `.coderabbit.yaml`, verify with a `@coderabbitai configuration` PR comment and confirm every changed field shows `Source: Repository YAML (base)`.
-- **CodeRabbit does NOT auto-review this repository, and its check never resolves.** Its own comment states the reason: *"This repository does not receive automatic reviews because it has fewer than 10 stars."* The configuration from #316 is loaded and correct (it reports `Path: .coderabbit.yaml`, `Review profile: ASSERTIVE`, `Plan: Pro Plus`) — only the automatic trigger is absent. Two consequences, both of which have been operated wrongly. **First, the ceremony below silently covers two bots, not three.** AGENTS.md credits CodeRabbit with catching a critical fast-forward defect (#358) and a use-after-free in the v2.3.5 libretro tables; none of that arrives unless a review is *requested*. Post `@coderabbitai review` on any PR whose diff warrants it. **Second, waiting for zero pending checks never terminates**: the `CodeRabbit` context sits with a null status and a null conclusion forever, so a healthy PR here reads as ~27 checks with exactly one permanently pending. The merge criterion is `CI success` = SUCCESS with every OTHER check complete — never "nothing pending", and never a bare check count either: a **CONFLICTING** PR also shows a short list, because GitHub cannot build a merge ref so CI never runs at all, and it is otherwise indistinguishable from a healthy PR whose jobs are still queuing. Gate on all three together — `CI success` green, every non-`CodeRabbit` check complete, and `mergeable == MERGEABLE`. **Also note the check is not required and removing it is not the fix**: `main`'s ruleset requires exactly one context, `CI success` (verified via `gh api repos/OWNER/REPO/rules/branches/main`), so the pending `CodeRabbit` context blocks nothing — and suppressing its status reporting would also lose the check on the PRs where a review IS triggered, which is the one time it carries information. And CodeRabbit rate-limits manual triggers per developer, so a batch of `@coderabbitai review` comments can silently produce no reviews at all.
+- **CodeRabbit does NOT auto-review this repository, and its check stays pending until a review is requested.** Its own comment states the reason: *"This repository does not receive automatic reviews because it has fewer than 10 stars."* The configuration from #316 is loaded and correct (it reports `Path: .coderabbit.yaml`, `Review profile: ASSERTIVE`, and `Plan: Advanced` on #583 and #589 as of 2026-10-06; it read `Plan: Pro Plus` when this was first written) — only the automatic trigger is absent. Two consequences, both of which have been operated wrongly. **First, the ceremony below silently covers two bots, not three.** AGENTS.md credits CodeRabbit with catching a critical fast-forward defect (#358) and a use-after-free in the v2.3.5 libretro tables; none of that arrives unless a review is *requested*. Post `@coderabbitai review` on any PR whose diff warrants it. **Second, waiting for zero pending checks never terminates on a PR nobody has asked CodeRabbit to review**: the `CodeRabbit` context sits pending until a review is requested (on #586 and #589 it reported SUCCESS once one was), so a healthy PR here reads as ~27 checks with exactly one pending. The merge criterion is `CI success` = SUCCESS with every OTHER check complete — never "nothing pending", and never a bare check count either: a **CONFLICTING** PR also shows a short list, because GitHub cannot build a merge ref so CI never runs at all, and it is otherwise indistinguishable from a healthy PR whose jobs are still queuing. Gate on all three together — `CI success` green, every non-`CodeRabbit` check complete, and `mergeable == MERGEABLE`. **Also note the check is not required and removing it is not the fix**: `main`'s ruleset requires exactly one context, `CI success` (verified via `gh api repos/OWNER/REPO/rules/branches/main`), so the pending `CodeRabbit` context blocks nothing — and suppressing its status reporting would also lose the check on the PRs where a review IS triggered, which is the one time it carries information. And CodeRabbit rate-limits manual triggers per developer, so a batch of `@coderabbitai review` comments can silently produce no reviews at all.
- **The bot-comment ceremony must read the review BODIES, not just the resolvable threads.** CodeRabbit posts "Outside diff range" and other suppressed findings **inside the review body**, where they are invisible to a resolve-every-thread sweep — and Copilot does the same. This has now cost the project three times: issue #360 (an untested attestation path) reached `main` unaddressed; two findings of the same class on #357 were genuine defects, **one critical** (two threads producing frames during fast-forward under threaded display-sync, fixed in #358); and a **use-after-free** in the v2.3.5 libretro controller tables was caught only because the review body was read. A green "all threads resolved" is not evidence the review was addressed. Fetch the bodies explicitly — `gh pr view --json reviews --jq '.reviews[].body'` — and triage every finding in them before merging.
- **TWO KNOBS CAN BE ONE DECISION, AND A REVIEW THAT CORRECTS PROSE MAY CORRECT THE CODE IN THE WRONG DIRECTION (v2.6.20).** v2.6.19 narrowed the OAM2 increment window from 256 to 258 AND deleted the dot-257 freeze latch. Both changes came from review, both were self-consistent, and **both were wrong** — because they are not two changes. The 33rd increment candidate is suppressed by `!oam2_overflowed`, so 256 and 258 perform the same 32 increments and differ only in PHASE; and the latch deletion's stated justification rested explicitly on the 258 window ("with the wrap corrected to dot 320, reading the live flag cannot disturb a fetch that has already concluded"). So there are two coherent packages — 258-without-latch and 256-with-latch — and only the second is the hardware's. v2.6.19 passed both OAM2 entries with the first, **by cancellation**, and the cancellation was invisible because **nothing read the counter**: `oam2_fetch_addr` was write-only for a whole release. The moment `$2004`'s post-fetch rest value reads it, restoring 256 alone breaks sprite rendering by **6 of 61,440 pixels** in two gates, because `oam2_overflowed` is also consumed by the four sprite-fetch sites and the wrap moves from dot 320 to dot 318, INSIDE the fetch. **Before adopting a review's fix, check what its justification depends on** — here the sentence named its own premise, and the premise was the defect. And v2.6.19's comment predicted the blind spot in its own words: "mostly invisible ... nothing downstream can tell 318 from 320." A difference called invisible is a statement about the instruments that exist, not about the hardware.
diff --git a/docs/agents/tooling-traps.md b/docs/agents/tooling-traps.md
index 373a892d4..cd6a2c781 100644
--- a/docs/agents/tooling-traps.md
+++ b/docs/agents/tooling-traps.md
@@ -10,10 +10,10 @@
> Every bullet is a measured finding with its evidence attached; they are
> written to be re-checkable, not to be taken on trust.
-- **AND `--files` IS THE QUIET FAILURE MODE OF THE SAME TOOL — `pre-commit run --files ` SILENTLY DROPS A PATH THAT DOES NOT EXIST (2026-09-19).** It prints `markdownlint.....(no files to check)Skipped`, which reads as a pass, while the hook never ran. Isolated at the argv level with `subprocess.run` and an explicit list, so no shell is involved: `'docs/STATUS.md'` -> **Passed**, `'docs/STATUS.md '` (one trailing space) -> **Skipped**. The bad path comes from the idiom that looks careful — `FILES=$(git diff --name-only | grep '\.md$' | tr '\n' ' ')` — where the `tr` appends a trailing space; without it the same pipeline passes. So the bullet above is right about the hazard it names and is the *source* of this one. **The safe form is neither: stage the changes and run the bare hook** (`git add -A && pre-commit run `), which takes its file set from the index and cannot be handed a malformed path. Treat `no files to check` / `Skipped` as a **failure to verify**, never a clean result — the same shape as a test filter matching nothing, and as `grep -c` returning 0 for a pattern that cannot match.
+- **AND `--files` IS THE QUIET FAILURE MODE OF THE SAME TOOL — `pre-commit run --files ` SILENTLY DROPS A PATH THAT DOES NOT EXIST (2026-09-19).** It prints `markdownlint.....(no files to check)Skipped`, which reads as a pass, while the hook never ran. Isolated at the argv level with `subprocess.run` and an explicit list, so no shell is involved: `'docs/STATUS.md'` -> **Passed**, `'docs/STATUS.md '` (one trailing space) -> **Skipped**. The bad path comes from the idiom that looks careful — `FILES=$(git diff --name-only | grep '\.md$' | tr '\n' ' ')` — where the `tr` appends a trailing space; without it the same pipeline passes. The trailing space survives only when the list reaches argv intact — in zsh (this harness's shell, which does not word-split an unquoted `$FILES`) or when the variable is quoted (`"$FILES"`); bash or sh splitting an unquoted `$FILES` drops it (qualified at v3.0.1). So the bullet above is right about the hazard it names and is the *source* of this one. **The safe form is neither, and it is NOT `git add -A`** (which this bullet recommended until v3.0.1: it stages every modified and untracked file in the tree, so the next commit takes unrelated work with it). Either **stage only the intended paths and run the bare hook** (`git add -- && pre-commit run `), which takes its file set from the index and cannot be handed a malformed path, or pass a **NUL-delimited** list that no shell splits: `git diff -z --name-only -- '*.md' | xargs -0 pre-commit run --files`. Treat `no files to check` / `Skipped` as a **failure to verify**, never a clean result — the same shape as a test filter matching nothing, and as `grep -c` returning 0 for a pattern that cannot match.
- **DO NOT EDIT A SHELL SCRIPT WHILE BASH IS EXECUTING IT (v2.6.7).** bash reads a script incrementally from a byte offset, so editing `tb/regress.sh` mid-run made the running instance resume mid-token and die with `syntax error near unexpected token )` at a line that parses perfectly — `bash -n` on the same file is clean. The whole ~50-minute run was wasted and, worse, every result before the error came from a file that was changing underneath it, so it is mixed-provenance evidence rather than merely incomplete. **Run long suites from a frozen copy** — but freeze it **IN PLACE**, not elsewhere. `f=$(mktemp --suffix=-frozen.sh tb/.ladder-XXXXXX)`, then `cp tb/regress.sh "$f" || exit 1`, then `( trap 'rm -f "$f"' EXIT; bash "$f" )`. **This bullet has now been corrected five times across three review rounds, and each correction was a measurement.** A fixed name lets two runs overwrite the copy the other is executing. `;` after `cp` runs a STALE copy from a previous run when the copy fails, which is worse than not running. An `EXIT` trap set at an interactive prompt replaces whatever the shell had, so it belongs in a subshell. **`$$` does not make the name unique**: it is the SHELL's pid, so two runs backgrounded from one interactive shell collide — measured, both subshells saw `$$ = 3300706` while their `BASHPID` differed. And `mktemp` is NOT ruled out by the ignore pattern, which is what an earlier revision here claimed: `tb/*-frozen.sh` anchors on the end, and `--suffix` puts the random part BEFORE it — `mktemp --suffix=-frozen.sh tb/.ladder-XXXXXX` yields `tb/.ladder-M5hUHb-frozen.sh`, verified gitignored, unique within a shell, and created atomically. (`--suffix` is GNU coreutils; on a BSD `mktemp` fall back to `$BASHPID`, not `$$`.) `tb/scratch/` is ignored but unusable, since `dirname/..` from it resolves to `tb/` rather than the repository root. Measured 2026-09-20: `regress.sh` locates the repository from its own path (`_REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"`), so a copy under the scratchpad resolved `_REPO_ROOT` to the scratchpad and **all 74 gates failed rc=2** — a result that reads exactly like a mass regression and was nearly reported as one. The advice as it stood named a scratch path and was wrong for any self-locating script; check what `$0` buys a script before relocating it. Same family as the mutation harness overwriting live edits, but the failure surfaces at the END rather than the start.
- **`cargo test` prints `error:` when a TEST FAILS, not only when the build breaks.** A mutation classifier keyed on `^error` reported BUILD-FAILED for three mutations that were all CAUGHT — the inverse of the usual trap, discarding evidence rather than manufacturing it. Classify from the runner's own vocabulary in order: `could not compile`/`error[E` -> BUILD-FAILED, `test result: FAILED` -> CAUGHT, `test result: ok` -> NOT CAUGHT, anything else -> investigate. And **run the baseline through the same classifier**: a baseline that does not land on the "tests pass" branch means the classifier is broken before any mutant has run.
-- **Never pass a prose body as a CLI argument in this harness — `git commit -m`, `gh pr create --body`, `gh pr comment --body`, `gh release create --notes`.** The shell is zsh, so **backticks are command substitution** and `` is an input redirect. A v2.4.0 message documenting three `gh` invocations lost all three to substitution and emitted `no such file or directory: owner` from a literal `repos///...` — the commit succeeded with a mangled body reading "recorded, because it is reusable: RETURNS THE PULL REQUEST". This project's house style puts command examples in commit bodies routinely, so `-m` is structurally the wrong tool: write the message to a file and use `git commit -F`. After amending, grep the message for each phrase that was supposed to survive. **The rule is about the SHELL, not about git, so it covers `gh` too** — hit again on 2026-09-18 with a `gh pr create --body` whose text contained a backtick-quoted filename, which published a body reading "Post-release. only" plus a `command not found: AGENTS.md` on stderr that is easy to miss beside the PR URL the same call prints on success. Read the body from a file every time — `-F` is the short flag on all three, and the long names DIFFER: `gh pr create --body-file`, `gh pr comment --body-file`, `gh release create --notes-file` (there is no `--body-file` on `release create`; verified from `--help`). **`gh api -F` is a DIFFERENT flag** — a typed field, which is why the `-F body=@file` form appears in the PR-ceremony rule — which the v2.6.23 split moved into [`review-bots.md`](review-bots.md), so "elsewhere in this file" (as this sentence read until review on #534) now names the wrong file. Then read back what was actually published (`gh pr view N --json body`) rather than assuming the text you wrote is the text that landed.
+- **Never pass a prose body as a CLI argument in this harness — `git commit -m`, `gh pr create --body`, `gh pr comment --body`, `gh release create --notes`.** The shell is zsh, so **backticks are command substitution** and `` is an input redirect. A v2.4.0 message documenting three `gh` invocations lost all three to substitution and emitted `no such file or directory: owner` from a literal `repos///...` — the commit succeeded with a mangled body reading "recorded, because it is reusable: RETURNS THE PULL REQUEST". This project's house style puts command examples in commit bodies routinely, so `-m` is structurally the wrong tool: write the message to a file and use `git commit -F`. After amending, grep the message for each phrase that was supposed to survive. **The rule is about the SHELL, not about git, so it covers `gh` too** — hit again on 2026-09-18 with a `gh pr create --body` whose text contained a backtick-quoted filename, which published a body reading "Post-release. only" plus a `command not found: AGENTS.md` on stderr that is easy to miss beside the PR URL the same call prints on success. Read the body from a file every time — `-F` is the short flag on all three, and the long names DIFFER: `gh pr create --body-file`, `gh pr comment --body-file`, `gh release create --notes-file` (there is no `--body-file` on `release create`; verified from `--help`). **`gh api -F` is a DIFFERENT flag** — a typed field, and the rule for it lives here: post a reply body with `gh api ... -F body=@reply.md`, which reads the field from a file, and **never `-F body=-`**. (Until v3.0.1 this sentence pointed at the PR-ceremony rule in [`review-bots.md`](review-bots.md), which does not carry the form.) Then read back what was actually published (`gh pr view N --json body`) rather than assuming the text you wrote is the text that landed.
- **Never byte-slice in a panic or format path.** `&text[at..(at + 24).min(text.len())]` panics when the offset lands inside a multi-byte character, and these documents are full of em-dashes and arrows — so the audit would crash *while formatting the diagnostic*, replacing the message explaining the real failure with a char-boundary error about the reporting code. **A diagnostic that can crash the diagnosis is worse than none**, because the failure it exists to explain becomes harder to read than if the excerpt were omitted. Use a char-boundary walk that KEEPS the offset: `s[at..].chars().take(n).collect::()` once `at` is known to be a boundary, or derive it with `char_indices()`. A bare `s.chars().take(n)` is NOT the fix — it returns the string's prefix and silently drops the failing region the excerpt exists to show, which is how a diagnostic becomes confidently wrong instead of merely absent. Caught in review on #534.
- **`make` COLLAPSES EVERY RECIPE FAILURE TO EXIT 2, so a harness cannot discriminate failure modes through it (2026-09-20).** Measured directly: a recipe exiting `1`, `2` or `139` all give `make` exit **2**. This matters because the diff tools here draw a careful three-way distinction — `fb_diff.py`'s own header says "Exit codes, three of them and never two: 0 identical / 1 differ / **2 the comparison could NOT be made**" — and every gate is invoked through `make`, which destroys it one layer up. A `run_xfail` that graded on the exit code was therefore **structurally incapable** of telling "the documented divergence" from "the gate never reached its comparison", which is the one thing an expected-failure gate must distinguish. The fix is to grade on **output evidence** — a required regex the gate's output must carry — because output is the one thing `make` does not rewrite. Generalises: before keying any logic on a subprocess's exit code, check what the layers between you and it do to that code.
@@ -31,3 +31,4 @@
- **`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.
+- **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/ai-emulator-provenance-guardrails.md b/docs/ai-emulator-provenance-guardrails.md
index 9d1518a48..b66904ab0 100644
--- a/docs/ai-emulator-provenance-guardrails.md
+++ b/docs/ai-emulator-provenance-guardrails.md
@@ -119,6 +119,26 @@ sandbox mounts, etc.), express the firewall there. A rule the runtime enforces b
agent is merely asked to follow — because the failure mode is precisely an agent that *doesn't*
follow the asked rule.
+### 3a. The one named exception: TriCNES (maintainer decision, 2026-10-07)
+
+TriCNES (Chris "100th_Coin" Siebert, the AccuracyCoin author; MIT, verified against
+`100thCoin/TriCNES` on 2026-10-07) is the single reference whose **source** may be consulted,
+and only on these terms:
+
+- **For AccuracyCoin work only**, when a test its author has already passed in TriCNES is being
+ troubleshot, and only after documentation, the Internet and black-box comparison (rungs 1-3 of
+ the AGENTS.md escalation ladder) have not settled it.
+- **From outside the repository.** Since v3.0.1 the upstream clone lives at
+ `~/reference-oracles/TriCNES` (commit `94f1b117`) and the instrumented harness at
+ `~/reference-oracles/TriCNES-rustynes-harness`. Rules 1 and 2 above still hold: nothing of it
+ goes back into the working tree.
+- **Anything written after consulting it is a port, and is attributed as one:** a `// Provenance:`
+ header, a section 1 row in `docs/originality-and-provenance.md`, and `NOTICE`, in the same
+ change. MIT requires the copyright notice; this project requires the rest.
+- **It is not a precedent.** It rests on TriCNES being permissive and written by the author of the
+ test suite. No GPL reference and no third-party HDL gains anything from it, and for the MiSTer
+ sibling the AGENTS.md per-region rung-4 declaration still applies.
+
---
## 4. Attribution: four surfaces, always consistent
diff --git a/docs/apu-2a03.md b/docs/apu-2a03.md
index e49a9025a..29aa4561f 100644
--- a/docs/apu-2a03.md
+++ b/docs/apu-2a03.md
@@ -452,7 +452,7 @@ AccuracyCoin stays 144/144 and nestest 0-diff with the change.
## Expansion-chip audio
-Six on-cart expansion sound chips are synthesized and summed into the external-audio mix via the `Mapper::mix_audio(&mut self) -> i16` hook (default 0). Each synth core lives in the owning mapper crate, **not** the 2A03 APU crate, because they are cartridge hardware:
+Six on-cart expansion sound chips are synthesized and summed into the external-audio mix via the `Mapper::mix_audio(&mut self) -> i32` hook (default 0; `i16` until v2.2.3). Each synth core lives in the owning mapper crate, **not** the 2A03 APU crate, because they are cartridge hardware:
| Chip | Mapper(s) | Synth core | Clock cadence |
|------------|------------------|-------------------------------------------------------|--------------------------------|
diff --git a/docs/build-and-tooling.md b/docs/build-and-tooling.md
index ee0dc317a..12df0751b 100644
--- a/docs/build-and-tooling.md
+++ b/docs/build-and-tooling.md
@@ -5,8 +5,8 @@
## Toolchain
- **Rust edition**: 2024.
-- **MSRV (minimum supported Rust version)**: 1.96.0. Pinned via `rust-toolchain.toml`. (Bumped from 1.86 in v1.3.0 "Bedrock" to unblock the edition-2024 + egui 0.34.3 / wgpu 29 / rfd 0.17.2 dependency tier.)
-- **Channel**: the pinned `1.96.0` stable release — *not* a floating `stable`. `rust-toolchain.toml` is the single source of truth: every GitHub Actions job resolves its toolchain from that file (`.github/actions/rust-setup` parses the `channel` and fails closed if it cannot), the libretro buildbot builds all ten of its jobs on it, and local builds pick it up automatically as a directory override. There is no `toolchain:` version literal anywhere in `.github/`, so bumping the pin is a one-line edit here — but read the `-C ar` warning in `rust-toolchain.toml` before bumping to 1.97 or newer.
+- **MSRV (minimum supported Rust version)**: 1.99, the pinned toolchain, for every crate except the seven the libretro core builds (`rustynes-{cpu,ppu,apu,mappers,core,gamedb,libretro}`), which declare 1.96 because the libretro buildbot stays on 1.96.0 (v3.0.1; see `.gitlab-ci.yml`). (History: 1.86 until v1.3.0 "Bedrock", which moved to 1.96 for the edition-2024 + egui 0.34.3 / wgpu 29 / rfd 0.17.2 dependency tier; 1.96 until v3.0.1.)
+- **Channel**: the pinned `1.99.0` stable release — *not* a floating `stable`. `rust-toolchain.toml` is the single source of truth: every GitHub Actions job resolves its toolchain from that file (`.github/actions/rust-setup` parses the `channel` and fails closed if it cannot), and local builds pick it up automatically as a directory override. **One exception:** the libretro buildbot runs 1.96.0, set by `RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml` (which outranks the file), because its build image passes `-C ar`, a hard error from Rust 1.97. CI's `libretro-cross` job reads that value from `.gitlab-ci.yml` and builds on it, so the 1.96 floor is checked on every PR. There is no `toolchain:` version literal anywhere in `.github/`, so bumping the pin is a one-line edit here — but read the `-C ar` warning in `rust-toolchain.toml` before bumping to 1.97 or newer.
- **Nightly** is used for exactly one thing, outside CI and not a gate: `cargo fuzz`, which requires it for the sanitizer flags it threads through `rustc` (`cargo +nightly fuzz run ` — see `fuzz/README.md`). No build, test, lint, docs, release, or packaging path uses nightly.
- **Targets supported**: `x86_64-unknown-linux-gnu`, `aarch64-apple-darwin`, `x86_64-pc-windows-msvc`. Tier 2: `aarch64-unknown-linux-gnu`. Cross-compile targets declared in `rust-toolchain.toml` (auto-installed): `thumbv7em-none-eabihf` (the `no_std` chip-stack gate) and `wasm32-unknown-unknown` (browser). Android arm64/arm/x86_64 via `cargo ndk` (see `docs/android.md`). The `x86_64-apple-darwin` release target was retired (ADR 0009).
@@ -14,7 +14,7 @@
```text
Cargo.toml # workspace manifest
-rust-toolchain.toml # pin 1.96.0 stable + thumbv7em + wasm32 targets
+rust-toolchain.toml # pin 1.99.0 stable + thumbv7em + wasm32 targets
crates/
├── rustynes-core/ # public re-exports + Nes facade + scheduler + save state
├── rustynes-cpu/ # 2A03 CPU
@@ -148,7 +148,7 @@ cargo build -p rustynes-core --target thumbv7em-none-eabihf --no-default-feature
- `actions/rust-setup/action.yml` — shared composite action (toolchain +
Linux wgpu/winit/cpal deps + cargo cache) used by all three workflows, so
the setup steps + the apt package list live in exactly one place.
-- `workflows/ci.yml` — lint (fmt + clippy + rustdoc on the pinned 1.96
+- `workflows/ci.yml` — lint (fmt + clippy + rustdoc on the pinned 1.99
toolchain, so the gate matches local) + the cross-platform test matrix +
test-roms + no_std + wasm32 clippy + the frame-time bench gate. Runs the
feature-combo clippy gates (`scripting`, `hd-pack`, `retroachievements`, and
diff --git a/docs/cpu-6502.md b/docs/cpu-6502.md
index b46704c3e..81390830f 100644
--- a/docs/cpu-6502.md
+++ b/docs/cpu-6502.md
@@ -168,8 +168,19 @@ Implementation: `Cpu` carries a `skip_irq_sample` flag. The branch
dispatch arms set it before the operand fetch (the opcode fetch in
`step()` has already performed the canonical poll on cycle 1); for
the rest of the instruction, `idle_tick` no longer updates
-`irq_first_tick`. NMI sampling is unaffected — the quirk is IRQ-only.
-The flag is reset at the top of every `step()`.
+`irq_first_tick`. The flag is reset at the top of every `step()`.
+
+**A taken branch also defers NMI dispatch (since v2.6.7).** nesdev's
+`CPU_interrupts` says *interrupts* are not polled before the third cycle of a
+taken branch, not only IRQs. `handle_interrupts` therefore freezes the NMI
+dispatch copy `mc_prev_need_nmi` while both `skip_irq_sample` and its
+one-cycle delay `skip_irq_sample_q` are set. The NMI edge latch
+(`mc_need_nmi`) keeps running every cycle — it is hardware's always-on edge
+detector — so an /NMI edge inside a taken branch is never lost, only
+dispatched after the branch. `skip_irq_sample_q` is genuine emulation state
+and is serialized: it is the field CPU snapshot version 4
+(`CPU_SNAPSHOT_VERSION`) appended. (Until v3.0.1 this section said the quirk
+was IRQ-only, which stopped being true at v2.6.7.)
Covered by the `cpu_interrupts_v2/5-branch_delays_irq` ROM, which passes
strictly. A mock-bus unit pin, `branch_taken_no_cross_delays_irq_one_instruction`
diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md
index 318dbe930..4eba091f9 100644
--- a/docs/dev/BUILD.md
+++ b/docs/dev/BUILD.md
@@ -21,9 +21,10 @@
### Required
-- **Rust** 1.96.0 (pinned in `rust-toolchain.toml`; the channel auto-installs).
- Edition 2024. MSRV 1.96 unblocks the edition-2024 + egui 0.34.3 / wgpu 29 /
- rfd 0.17.2 dependency tier (bumped from 1.86 in v1.3.0 "Bedrock").
+- **Rust** 1.99.0 (pinned in `rust-toolchain.toml`; the channel auto-installs). The libretro core's crates must also build on 1.96.0, which the libretro buildbot uses: `RUSTUP_TOOLCHAIN=1.96.0 cargo check --release -p rustynes-libretro`.
+ Edition 2024. (The pin was 1.86 until v1.3.0 "Bedrock", which moved to 1.96
+ for the edition-2024 + egui 0.34.3 / wgpu 29 / rfd 0.17.2 dependency tier,
+ and 1.96 until v3.0.1.)
- **Cargo** (included with Rust).
### System libraries
@@ -44,14 +45,14 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
**Or visit**:
-The pinned toolchain (1.96.0) and the cross-compile targets
+The pinned toolchain (1.99.0) and the cross-compile targets
(`thumbv7em-none-eabihf` and `wasm32-unknown-unknown`) are all declared in
`rust-toolchain.toml`, so `rustup` installs them automatically on first build.
### Verify Installation
```bash
-rustc --version # Should report 1.96.0 (the pinned channel)
+rustc --version # Should report 1.99.0 (the pinned channel)
cargo --version
```
diff --git a/docs/dev/STYLE_GUIDE.md b/docs/dev/STYLE_GUIDE.md
index 7ffc833bf..68b9ce739 100644
--- a/docs/dev/STYLE_GUIDE.md
+++ b/docs/dev/STYLE_GUIDE.md
@@ -16,7 +16,7 @@ Coding conventions and best practices for contributing to RustyNES.
```toml
[package]
edition = "2024"
-rust-version = "1.96" # Minimum Supported Rust Version (pinned in rust-toolchain.toml)
+rust-version.workspace = true # 1.99; the seven libretro-path crates declare "1.96"
```
### Formatting
diff --git a/docs/expansion-audio.md b/docs/expansion-audio.md
index bb7368a57..1bd113962 100644
--- a/docs/expansion-audio.md
+++ b/docs/expansion-audio.md
@@ -5,7 +5,7 @@
Several NES cartridge boards carry their **own** sound hardware that mixes into
the console's external-audio input alongside the 2A03. RustyNES synthesizes six
such expansion chips and sums each into the mix through the
-`Mapper::mix_audio(&mut self) -> i16` hook (default `0` for boards with no audio
+`Mapper::mix_audio(&mut self) -> i32` hook (default `0` for boards with no audio
hardware). Each synth core lives in the **owning mapper crate**, not the 2A03
APU crate, because it is cartridge hardware.
diff --git a/docs/frontend.md b/docs/frontend.md
index 5ebcb860c..46119a8aa 100644
--- a/docs/frontend.md
+++ b/docs/frontend.md
@@ -2156,19 +2156,27 @@ All additive + frontend-only; the core stays byte-identical.
subtitle track at the region's frame rate (NTSC's 60.0988 fps stays
drift-free), for muxing into an A/V dump (`movie_srt::markers_to_srt`).
-A detach / pop-out affordance for tool windows shipped in **v2.2.9 "Studio II"** —
-the shared `detachable_window` helper (`debugger/mod.rs`) pops any of 18 tool
-panels out via `ctx.show_viewport_immediate`, with a Reattach affordance and its
-prior first-open geometry (a `WindowCfg`); native-only (wasm keeps the docked
-`egui::Window`). **Honest scope:** the frontend is currently a single-viewport
-`egui_winit` integration (one `take_egui_input` / `handle_platform_output` for the
-main window, no `viewport_output` handling, `embed_viewports` left at its default
-`true`), so `show_viewport_immediate` renders the panel **embedded in the main
-window** rather than a separate OS window. True OS-window detach — the Windows-10
-trapped-window fix — requires wiring multi-viewport into the render loop
-(`set_embed_viewports(false)`, per-`ViewportId` winit windows + egui states +
-wgpu surfaces, and routing their events); the affordance and geometry plumbing are
-in place for when that lands. Tracked as follow-up.
+**Detached tool windows (v2.3.0 "Datum II").** Every tool panel can be detached
+into its **own real OS window** (`crates/rustynes-frontend/src/detached.rs`) —
+the fix for the Windows-10 "every tool window is trapped inside the main window"
+report. RustyNES does not use egui's native multi-viewport path: each detached
+window owns its own `egui::Context`, `egui_winit::State`, `egui_wgpu::Renderer`
+and `wgpu::Surface`, shares the main `Gfx`'s one instance / adapter / device /
+queue, and is rendered on its own `RedrawRequested`, where the panel's borrows
+(`&mut Nes`, panel state) are re-acquired by re-running the panel dispatch under a
+thread-local render-target filter (`DebuggerOverlay::render_detached_body`).
+Nothing is stashed across frames, so no `unsafe` and no lifetime erasure are
+needed; the module's preamble gives the full reasoning. Native-only: wasm is
+single-canvas and keeps every tool panel docked as an `egui::Window`.
+
+*History:* the detach affordance first shipped in **v2.2.9 "Studio II"** as the
+shared `detachable_window` helper (`debugger/mod.rs`), which popped any of 18 tool
+panels out via `ctx.show_viewport_immediate` with a Reattach affordance and its
+prior first-open geometry (a `WindowCfg`). Because the frontend was then a
+single-viewport `egui_winit` integration (`embed_viewports` left at its default
+`true`), that rendered the panel **embedded in the main window** rather than in a
+separate OS window; v2.3.0's `detached.rs` replaced that embedding (the
+`detachable_window` helper itself remains in `debugger/mod.rs`).
**Deferred (noted for a follow-up):** Virtual Pad (clickable on-screen
controller → `SharedInput`), input Macros feeding the piano-roll pattern-paint,
diff --git a/docs/mappers.md b/docs/mappers.md
index 4f881e9ea..55eafc8d8 100644
--- a/docs/mappers.md
+++ b/docs/mappers.md
@@ -716,7 +716,7 @@ the page gives only Disch's notes, or masks marked "probably".
|------|-----|--------------|------|-----|-------|
| 12 | 0 | Gouder SL-5020B (`INES_Mapper_012`) | Curated | MMC3A (the alternate / NEC behaviour) | `$4100` mask `$E100`: CHR A18 per pattern table (bit 0 for PPU A12=0, bit 4 for A12=1), outside the ASIC so unaffected by `$8000` bit 7. The *Dragon Ball Z 5* language bit read at the same address returns 0 on D0: the page says every known copy is hard-wired to Chinese but not which level that is, so this is an assumption. Submapper 1 (the Magic Card 4M extraction) is a different device and is not supported. |
| 37 | — | SMB + Tetris + NWC (`INES_Mapper_037`) | Curated | MMC3 | The 74HC161 at `$6000-$7FFF`, written only while the MMC3's `$A001` allows a PRG-RAM write; PRG A16 = Q0·Q1 + Q2·M16, A17 = CHR A17 = Q2 (the page's NAND equations). Write-only (open bus). The CIC reset clears it (`Mapper::reset`). |
-| 45 | — | GA23C (`INES_Mapper_045`) | Curated | MMC3 | Four outer registers written in turn at `$6000` (mask `$F001`): CHR-OR, PRG-OR, CHR-AND + high bits, PRG-AND (inverted) + lock. `$6001` resets and unlocks, as does a soft reset. `$5000-$5FFF` reads the menu DIP switch on D0. WRAM only when a NES 2.0 header declares it. The page gives no power-on or reset value for the outer registers. Power-on, soft reset and `$6001` set `[$00, $00, $0F, $00]`, so CHR-AND passes every MMC3 CHR bit (T-GA23C-POWERON, maintainer 2026-10-05). The evidence: two *Famicom Yarou* menus (54 and Vol.5) draw with CHR banks 0-7 before their first outer-register write, which needs CHR-AND at `$A` or more. *Famicom Yarou Vol.1* (CHR-RAM) still shows noise: its menu uploads all 8 KiB of pattern data with every CHR register at 0, as if the RAM were unbanked (T-GA23C-CHRRAM, open). |
+| 45 | — | GA23C (`INES_Mapper_045`) | Curated | MMC3 | Four outer registers written in turn at `$6000` (mask `$F001`): CHR-OR, PRG-OR, CHR-AND + high bits, PRG-AND (inverted) + lock. `$6001` resets and unlocks, as does a soft reset. `$5000-$5FFF` reads the menu DIP switch on D0. WRAM only when a NES 2.0 header declares it. The page gives no power-on or reset value for the outer registers. Power-on, soft reset and `$6001` set `[$00, $00, $0F, $00]`, so CHR-AND passes every MMC3 CHR bit (T-GA23C-POWERON, maintainer 2026-10-05). The evidence: two *Famicom Yarou* menus (54 and Vol.5) draw with CHR banks 0-7 before their first outer-register write, which needs CHR-AND at `$A` or more. CHR-RAM is unbanked: addressed straight from PPU A10-A12, past the MMC3 banks and the outer CHR registers (T-GA23C-CHRRAM, fixed v3.0.1). The page is silent on CHR-RAM; mapper 372, the GA23C with a ROM/RAM switch, documents its RAM as unbanked, and *Famicom Yarou Vol.1* uploads all 8 KiB with every CHR register at 0 and then draws with R0-R5 = 0, 2, 4, 5, 6, 7. |
| 47 | — | Spike V'Ball + NWC (`INES_Mapper_047`) | BestEffort | MMC3 | One block bit in the PRG-RAM window, gated like mapper 37. The page is Disch's notes only. The Kasheng *2-in-1 (Mortal Kombat 6, Samurai Spirits)* is often labelled 47 (512 KiB CHR, `$6000` written with `$C0`/`$60` while WRAM is disabled); it is NES 2.0 mapper 291 (`NES_2_0_Mapper_291`), which this project does not implement. |
| 74 | — | Waixing 43-393 (`INES_Mapper_074`) | Curated | MMC3 | CHR banks 8 and 9 are 2 KiB of CHR-RAM. 8 KiB work RAM. A dump that writes `$A001` with bit 5 set (`$EC`/`$ED`), `$5FF3`, or MMC3 registers 8-11 is a Waixing FS005 re-release, mapper 176 submapper 2 (`INES_Mapper_176`), not this board. |
| 83 | 0/1/2 | Cony / Yoko (`INES_Mapper_083`) | Curated | 16-bit M2, up or down | Three PRG modes, `$6000` ROM (subs 0/1) or 32 KiB banked WRAM (sub 2), 1 KiB / 2 KiB / outer-banked CHR. On iNES the submapper follows the page's CHR-size heuristic. The DIP and scratch-RAM masks are "probably" on the page and are decoded inside `$5000-$5FFF` only. |
diff --git a/docs/mister.md b/docs/mister.md
index f2252898e..e8816070e 100644
--- a/docs/mister.md
+++ b/docs/mister.md
@@ -31,9 +31,12 @@ reviewer can **check** about it.
The contributing page states the bar for AI-assisted code in one sentence --
*"Fully AI generated code should meet a minimum reasonable bar for readability
and include some evidence of quality and accuracy testing."* This programme's
-evidence is 148 co-simulation gates with a mutation record apiece -- 142 at the
-start of v2.6.15, plus the five it added and the one v2.6.16 added. 147 of them
-run on the die; the 148th measures the off-die memory system and reports N/A
+evidence is 148 co-simulation gates -- 142 at the start of v2.6.15, plus the five
+it added and the one v2.6.16 added. Every gate but five carries a mutation
+record; the exceptions are the five blargg `cpu_interrupts_v2` verdict gates
+v2.6.15 added, which are independent-oracle verdicts with no mutation recorded
+against them (no rung document and neither `tb/mutate.sh` nor
+`tb/mutate_apu.sh` names them; noted at v3.0.1). 147 of them run on the die; the 148th measures the off-die memory system and reports N/A
there, because a gate on the SDRAM path cannot exist in a build with no SDRAM. And
`tb/regress.sh` says in its own header that it *"is NOT a CI gate and cannot
be"*, because it needs the oracle's goldens and a cargo build of a crate in
@@ -251,8 +254,11 @@ gate can adjudicate. It is allowed and named, not resolved.
repositories. This reverses v2.6.6, which produced a bitstream and deliberately
did not publish it.
-**Why the reversal is right.** The MiSTer distribution mechanism reads
-`releases/RustyNES_MiSTer-vX.Y.Z.rbf` out of the *repository*, so an empty
+**Why the reversal is right.** The MiSTer distribution mechanism reads the
+`.rbf` out of the *repository*'s `releases/` — since v2.6.15 as
+`releases/RustyNES_YYYYMMDD.rbf`, the only form both MiSTer parsers accept, with
+the version-named `RustyNES_MiSTer-vX.Y.Z.rbf` attached to the GitHub releases
+for people (the sibling's `docs/bitstream-release.md`) — so an empty
`releases/` does not describe a cautious core — it describes an undistributable
one, withheld from exactly the people who own the boards this project does not.
And a claim nobody made is not the same as a claim marked unverified: only the
@@ -600,10 +606,12 @@ written from public hardware documentation**, in a sibling repository
private), with **RustyNES as its verification oracle**. This document specifies
the boundary between the two - the one part that lives in this repository.
-The sibling repository holds the harness at rung 0 and **no RTL**, which is the
-ladder's design rather than a gap: the testbench must be shown able to recognise
-agreement before anything is compared. Two of its files matter to readers of
-this document, because they are the other half of what is specified here.
+When this section was written (2026-08-20, first shipped in v2.4.2), the sibling repository held only the
+rung-0 harness and **no RTL**, which was the ladder's design rather than a gap:
+the testbench had to be shown able to recognise agreement before anything was
+compared. It has carried RTL since v2.4.4 and now holds a complete core (see the
+release sections above). Two of its harness files matter to readers of this
+document, because they are the other half of what is specified here.
`tb/checkpoint.h` reimplements this repository's checkpoint encoding in C++, and
`tb/checkpoint_selftest.cpp` asserts it against **the same hardcoded vector**
@@ -617,9 +625,12 @@ first thing to run after touching either.
Its licence audit also settled a question this side had left open. ADR 0037
recorded that a GPL-2.0-**only** file anywhere in the MiSTer framework's `sys/`
-would force the RTL to GPL-2.0-or-later. All 57 files were read: **there is no
-such file**, and four are GPL-3.0-or-later - including `hps_io.sv`, which no core
-functions without. GPL-2.0-or-later combines upward and GPL-3.0-or-later does not
+would force the RTL to GPL-2.0-or-later. All 57 files of an upstream clone were
+read at v2.4.3: **there is no such file**, and four are GPL-3.0-or-later -
+including `hps_io.sv`, which no core functions without. v2.6.6 re-verified this
+against the tree actually vendored (`Template_MiSTer@3ea1134c`): **40** HDL files,
+the same four GPL-3.0-or-later, nine GPL-2.0-or-later, **zero** GPL-2.0-only (the
+sibling's `docs/licence-audit-2026-08-20.md`, "Re-verified at v2.6.6"). GPL-2.0-or-later combines upward and GPL-3.0-or-later does not
reduce, so the combined bitstream must be **GPL-3.0-or-later**, which is already
this project's licence. The hedge is inverted by the evidence rather than
confirmed by it.
diff --git a/docs/originality-and-provenance.md b/docs/originality-and-provenance.md
index 5754d0971..a77973595 100644
--- a/docs/originality-and-provenance.md
+++ b/docs/originality-and-provenance.md
@@ -70,12 +70,13 @@ LGPL-2.1-or-later ones may be incorporated into a GPL-3.0-or-later work.
| `crates/rustynes-apu/src/blip.rs` | blip_buf (Blargg) | band-limited synthesis (`blip_buf`) | LGPL-2.1-or-later |
| `crates/rustynes-apu/src/opll.rs` | emu2413 (upstream MIT; Mesen2 vendors it) | `emu2413.{h,cpp}` | MIT |
| `crates/rustynes-frontend/src/ntsc_bisqwit.rs` | Bisqwit; Mesen2 | Bisqwit `nes_ntsc`-style composite model as implemented by Mesen2's `BisqwitNtscFilter`; **numeric tables ported verbatim** | GPL-3.0-or-later (Mesen2) |
-| `crates/rustynes-gfx-shaders/src/crt_stack.rs`, `src/lib.rs` | CRT-Royale, crt-guest-advanced, Sony Megatron | single-pass WGSL reimplementations of those shaders (see §6) | GPL-2.0-or-later / permissive |
+| `crates/rustynes-gfx-shaders/src/crt_stack.rs` | CRT-Royale, crt-guest-advanced, Sony Megatron | single-pass WGSL reimplementations of those shaders (see §6); `src/lib.rs` re-exports them | GPL-2.0-or-later / permissive |
+| `crates/rustynes-gfx-shaders/src/lib.rs` | Bisqwit; Mesen2 | `BISQWIT_WGSL` (`src/bisqwit.wgsl`), a generated verbatim copy of `ntsc_bisqwit.rs`'s pass, tables ported from Bisqwit's C via Mesen2's `BisqwitNtscFilter` (recorded v3.0.1, T-NTSC-PROVENANCE: its doc comment had called it independent) | GPL-3.0-or-later (Mesen2) |
| `crates/rustynes-core/src/vs_dualsystem.rs` | Mesen2 | `NesConsole::RunFrame` / `RunVsSubConsole`, `VsControlManager` (reset seed, coin routing), `UpdateMainSubBit` (recorded v2.9.9, NC-17) | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/m019_namco163.rs` | Mesen2 | `NesSoundMixer::GetOutputVolume`: the N163 `* 20` output weight (recorded v2.9.9, NC-17) | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/m016_bandai_fcg.rs` | Mesen2 | `Eeprom24C01` / `Eeprom24C02`, `Core/NES/Mappers/Bandai/` | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/m035_jy_asic.rs` | Mesen2 | `JyCompany` register decode, `InvertPrgBits` | GPL-3.0-or-later |
-| `crates/rustynes-mappers/src/m069_sunsoft_fme7.rs` | Mesen2 / Nestopia | Sunsoft 5B audio + FME-7 | GPL-3.0-or-later / GPL-2.0-or-later |
+| `crates/rustynes-mappers/src/m069_sunsoft_fme7.rs` | Mesen2 / Nestopia | Sunsoft 5B audio + FME-7; the 5B level target from Mesen2's `NesSoundMixer::GetOutputVolume` (`* 15` weight) over `Sunsoft5bAudio::_volumeLut`, as this file's pre-v2.2.5 "derived from Mesen2" comment recorded (recorded here v3.0.1); no Nestopia file is recorded | GPL-3.0-or-later / GPL-2.0-or-later |
| `crates/rustynes-mappers/src/m085_vrc7.rs` | Mesen2 | `Vrc7Audio.h`: the `$9010`/`$9030` register-write path and the `$E000` silence flag (classified v2.7.1, core audit §6.2) | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/m099_vs_system.rs` | Mesen2 | `VsSystem.h`: DualSystem sub-console `chrOuter` / `prgOuter` banking (classified v2.7.1, core audit §6.2) | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/m176_bmc_fk23c.rs` | Mesen2 | `Waixing/Fk23C.h`, `Mmc3Variants/MMC3_Coolboy.h` | GPL-3.0-or-later |
@@ -85,7 +86,7 @@ LGPL-2.1-or-later ones may be incorporated into a GPL-3.0-or-later work.
| `crates/rustynes-mappers/src/m513_sachen_9602.rs` | Mesen2 | `Sachen/Sachen9602.h`, `Txc/TxcChip.h` | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/mmc3_clones.rs` | Mesen2 | `Waixing/Mapper253.h`, `Sachen/Sachen8259.h`, `InvertPrgBits`, MMC3 variants | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/multicart_discrete.rs` | Mesen2 | `Ntdec/Mapper221.h`, `Txc/Bmc11160.h` | GPL-3.0-or-later |
-| `crates/rustynes-mappers/src/ntdec.rs` | Mesen2 | NTDEC boards, `Txc/Bmc11160.h` | GPL-3.0-or-later |
+| `crates/rustynes-mappers/src/ntdec.rs` | Mesen2 | NTDEC boards, `Txc/Bmc11160.h`; also `Ntdec/NtdecTc112.h` (mapper 193) and `Unlicensed/Mapper204.h` (mapper 204), named by this file's own pre-v2.2.5 "Ported from" comments (recorded v3.0.1) | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/sachen_discrete.rs` | Mesen2 | `Sachen/Sachen8259.h`, `Txc/TxcChip.h` | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/kaiser.rs` | Mesen2 | Kaiser boards, `Waixing/Mapper253.h` | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/fds.rs` | puNES | `fds.c` per-CRC drive-timing table | GPL-2.0-or-later |
diff --git a/docs/performance.md b/docs/performance.md
index 7763e4c38..95913f23b 100644
--- a/docs/performance.md
+++ b/docs/performance.md
@@ -3233,6 +3233,16 @@ the column existed passes but is reported as *"validity UNKNOWN, not verified"*
it cannot be proven valid, and saying "window was on screen" of a log that never
measured it would be a small version of exactly the error F14 is about.
+A fourth state since v3.0.1. Until then, `0` with the column present was reported
+as *"capture VALID — window was on screen throughout"* even when the run had **no
+presentation clock** — and the section above says zero cannot carry that claim,
+because the counter is written through a `map_or(0, ...)` on the clock. The
+checker now reads the header's `measured_refresh_hz`, which is set only from that
+clock's answer: when it is `none`, a zero count is reported as *"validity
+UNVERIFIED (no presentation clock)"*. The header is written when logging starts,
+so a clock that answered later is also reported unverified — an understatement,
+never an overclaim. This changes the report line only; pass and fail are as before.
+
Every pacing conclusion in this document that predates the column therefore
carries an unverifiable assumption: that the window was actually on screen. The
sixteen scanout-bearing captures almost certainly were — they *have* `presented`
@@ -3357,12 +3367,18 @@ ring — and nothing else uses them. Free win, apparently.
| bench | full | slim | saving |
| --- | ---: | ---: | ---: |
| `nes_restore_quiet_flowing_palette` | 122.8 µs | 115.9 µs | **6.9 µs** |
-| `nes_restore_quiet_mmc3` | 123.7 µs | 116.2 µs | **7.4 µs** |
+| `nes_restore_quiet_mmc3` | 123.7 µs | 116.2 µs | **7.5 µs** |
Against the 2.802 ms `nes_runahead_budget` increment that is **0.25%** — an
order of magnitude under the project's >3% bar. **Rejected before
implementation.**
+*Corrected at v3.0.1:* the `nes_restore_quiet_mmc3` saving was first printed
+as 7.4 µs, but the rounded columns subtract to 7.5 µs and the unrounded
+criterion means were not recorded (commit `697495c3` gives the same rounded
+figures), so the table now shows the difference of what it prints. The 0.25%
+conclusion is unchanged either way.
+
> The first version of this table read 8.4 µs, from a **confounded** probe: it
> booted a fresh `Nes` and ran one frame, while every other bench here uses
> `warmed_nes` (60 frames, rendering enabled, OAM and palette populated). That
@@ -3374,7 +3390,8 @@ implementation.**
It came from the project's own (correct) statement that the framebuffer is **94%
of the snapshot BYTES**, and that was carried silently into a claim about
-**TIME**. 245,760 bytes is ~12-25 µs of memcpy at ordinary bandwidth, so it could
+**TIME**. 245,760 bytes is ~12-25 µs of memcpy at an ordinary ~10-20 GB/s
+(245,760 B / 20 GB/s = 12.3 µs; / 10 GB/s = 24.6 µs), so it could
never have been 94% of a 122 µs restore. The measured share is ~7%. The estimate
was off by 13x.
@@ -3423,6 +3440,13 @@ screen and confirmed valid by F16's gate, 18 post-warmup rows):
| `tick_iv` — between tick *sends* | 16.289 ms | **24.578 ms** | 28.637 ms |
| `produced` — resulting interval | 16.269 ms | **24.635 ms** | — |
+> Correction (v3.0.1): `tick_iv` is differenced on the receiver, between the
+> send stamps of successive *delivered* ticks. A tick dropped on the full depth-1
+> channel is never seen, so the next interval spans two sends. It equals the
+> interval between sends only while `tick_dropped` is 0. Drops were measured at
+> 0 (`run_ahead` 2) and 0-1 (`run_ahead` 0) per 45 s capture under "Suspect A",
+> so the conclusion below is not affected in practice; the row label is.
+
#### The result
**The cross-thread hop is 33-50 microseconds.** It is not a contributor, and the
@@ -3695,7 +3719,7 @@ square. Five paired rounds for the two live arms:
| reset — rejected | 4.00 s | 2.02% | **1.00 (3 of 3)** |
**5/5 paired rounds favour `predict` on both convergence and cadence, exact
-one-sided sign p = 0.0312** — at the floor for n = 5, which is why five rounds
+one-sided sign p = 1/32 = 0.03125** — at the floor for n = 5, which is why five rounds
were run rather than three (three floors at 0.125 and could not have reached
significance whatever it showed).
diff --git a/docs/provenance-failure-postmortem.md b/docs/provenance-failure-postmortem.md
index 32d8060a8..098145309 100644
--- a/docs/provenance-failure-postmortem.md
+++ b/docs/provenance-failure-postmortem.md
@@ -25,7 +25,8 @@ FCEUX). The AI that wrote them **labeled them honestly at the time** ("Faithful
1. **The port itself** (May 2026, in the predecessor project `RustyNES_v2` core-work): the reference
emulators' full GPL **source** was cloned in the workspace and set as the "accuracy bar," with
enforced guardrails forbidding reading or reproducing it not followed. The LLM decided to match
- Mesen2 exactly, with Mesen2's source right there, it did the obvious thing and partially-ported.
+ Mesen2 exactly, with Mesen2's source right there, it did the obvious thing and partially-ported
+ (the model's reasoning is inferred from the result, not quoted from a transcript; see §5).
2. **The laundering** (v2.2.5 "Colophon," 2026-08-03, in this public project): when the licensing
implication surfaced, the honest "port of" comments were **reworded** into "oracle
cross-checks," `NOTICE` was rewritten to assert "No GPL-licensed emulator source is
@@ -50,7 +51,7 @@ that engine by transplant on 2026-06-13.
|---|---|---|---|
| **2026-05-10** | RustyNES_v2 | Project "bootstrapped **from a deep-research workflow**." The Mesen2/higan/ares "accuracy bar" framing and the reference-emulator source tree (`ref-proj/`) entered here. Phases 1–2 (6502, nestest pass, first mappers, PPU) landed the same day. | `3ec2230 chore: bootstrap RustyNES v2 from deep-research workflow`; `4d3cf47`, `b386595`, `69e9373` |
| **~2026-05-10 → 05-25** | RustyNES_v2 | The cycle-accurate chip core built in phases. With the GPL **source** on disk and an accuracy-matching goal, code was **ported** from it and labeled as such: CPU SH\*/unstable stores from Mesen2 `NesCpu.h`; PPU sprite-eval/OAM from Mesen2 `NesPpu.cpp:1015-1141`; mappers from Mesen2; JV001/FDS from puNES; UNIF from FCEUX. | `9e00032 fix(cpu): SH* unstable stores` (2026-05-23); `941d448 fix(ppu): Phase 3b — OAM-corruption row tracking` (2026-05-23) |
-| **2026-06-13** | RustyNES → | The "**v2.8.0 engine stack**" was **transplanted** into the public repo as the `rustynes-*` crates. The honest "port of" comments came along verbatim. The "oracle / do NOT port" framing was written into the docs **for the first time** on this same day — *after* the porting was already done. | `dba2e75c feat(synthesis): Phase A — transplant v2.8.0 engine stack as rustynes-*`; `4e1844f7 docs(synthesis): Phase C` (first "do NOT port" text) |
+| **2026-06-13** | RustyNES → | The "**v2.8.0 engine stack**" (the private `RustyNES_v2` project's internal v2.8.0 — engine lineage, not RustyNES's own v2.8.0 release) was **transplanted** into the public repo as the `rustynes-*` crates. The honest "port of" comments came along verbatim. The "oracle / do NOT port" framing was written into the docs **for the first time** on this same day — *after* the porting was already done. | `dba2e75c feat(synthesis): Phase A — transplant v2.8.0 engine stack as rustynes-*`; `4e1844f7 docs(synthesis): Phase C` (first "do NOT port" text) |
| **2026-06-19 →** | RustyNES | The public-era sessions and maintainer guidance repeatedly asserted the code used the emulators "**as oracle**" only and "**NEVER lift**" — a framing that directly contradicted the "port of Mesen2" comments sitting in the same tree. The tension was left unresolved for weeks. | Public session logs, maintainer instructed: "as oracle" ×165, "NEVER lift" ×58, "reference only" ×41, "do not copy" ×36 |
| **2026-08-03** | RustyNES | **v2.2.5 "Colophon."** Prompted by NESdev scrutiny of the project's AI-assisted origins, the honest "port of X" comments were **reworded** to "oracle cross-checks," `NOTICE` was rewritten to claim "No GPL-licensed emulator source is incorporated," and the MIT/Apache license was kept. The evidence was scrubbed rather than acted on - the LLM should not have done this. | `0265b3bd release: v2.2.5 "Colophon"` |
| **2026-08-04** | RustyNES | NESdev reviewer (**Fiskbit**) publicly identified that the code — bugs, constants, variable names, code ordering, and file/function/line comments — goes well beyond oracle use, and that scrubbing the comments looked like concealment. **Correct.** v2.2.9 relicenses to GPL-3.0-or-later, restores honest attribution, and writes this post-mortem. | `ec26e229 license: relicense to GPL-3.0-or-later …`; this document |
diff --git a/docs/scheduler.md b/docs/scheduler.md
index 6e050657d..f3d520c95 100644
--- a/docs/scheduler.md
+++ b/docs/scheduler.md
@@ -157,11 +157,14 @@ This guarantees that save/load round-trips and a re-played input sequence produc
> These are the original **design-phase aspirations**, not gates. The frame-cost
> figure was not met and is knowingly accepted — the implemented cycle-accurate
-> core measures **~3.9 ms** (nestest) / **~2.5 ms** (flowing palette), ~23% of the
-> 16.639 ms NTSC budget. See `docs/performance.md` §Targets for the measured
-> numbers and why the main optimization levers were measured and rejected.
-
-- Frame cost (single-thread, headless core — no frontend, no present): ≤ 2 ms on a 2018-era laptop x86_64 (Skylake-era) — **aspirational; ~3.8 ms measured and accepted** (`nes_run_frame_nestest`, which renders; the render-light `flowing_palette` workload measures ~2.6 ms).
+> core measures **~3.95 ms** (nestest) / **~2.65 ms** (flowing palette) on the
+> shipped fast dot path, ~23% of the 16.639 ms NTSC budget (the v2.7.0 core,
+> 2026-09-23; v2.9.7's A12 change added about 1.9% on nestest). See
+> `docs/performance.md` §"Current figures" for the measured numbers, the
+> exact-path pair, and why the main optimization levers were measured and
+> rejected.
+
+- Frame cost (single-thread, headless core — no frontend, no present): ≤ 2 ms on a 2018-era laptop x86_64 (Skylake-era) — **aspirational; ~3.95 ms measured and accepted** (`nes_run_frame_nestest_fast`, the shipped fast dot path, which renders; the render-light `flowing_palette` workload measures ~2.65 ms).
- Frame cost including wgpu present + cpal callback: ≤ 5 ms (well under the 16.67 ms budget for 60 fps NTSC).
- Audio callback: lock-free SPSC ring buffer; never block the audio thread.
diff --git a/docs/testing-strategy.md b/docs/testing-strategy.md
index 98ef4c038..ba4316ea8 100644
--- a/docs/testing-strategy.md
+++ b/docs/testing-strategy.md
@@ -209,7 +209,7 @@ fails if any job it aggregates failed):
feature set the project enumerates (including `retroachievements`, `full` and
both wasm32 builds), rustdoc `-D warnings`.
- **Tests:** `cargo test --workspace` on Linux (plus macOS and Windows on full
- runs), on the pinned toolchain (`rust-toolchain.toml`, 1.96). Since v2.9.4 the
+ runs), on the pinned toolchain (`rust-toolchain.toml`, 1.99). Since v2.9.4 the
Linux leg also runs the tests behind non-default features: the frontend with
`full`, `rustynes-core` and `rustynes-ppu` with `debug-hooks,hd-pack`,
`rustynes-apu` with `debug-hooks`, `rustynes-script` with
diff --git a/docs/tooling/oracle-tooling-setup.md b/docs/tooling/oracle-tooling-setup.md
index bae396e45..38d686cb0 100644
--- a/docs/tooling/oracle-tooling-setup.md
+++ b/docs/tooling/oracle-tooling-setup.md
@@ -10,14 +10,14 @@
> allowed paths** (a sibling directory the tool sandbox does not expose). The removed-clone paths that
> appear below are historical and no longer resolve.
>
-> **TriCNES is the deliberate exception, and it is not a firewall violation.** TriCNES is **MIT**, so
-> its full upstream source is *intentionally vendored in-repo* at
-> `crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/` (with its `LICENSE`, attributed in
-> `NOTICE` + `docs/originality-and-provenance.md` §1) as a genuinely-incorporated permissive component —
-> which is exactly what makes the cross-diff harness self-contained. The committed golden vectors under
-> `crates/rustynes-test-harness/golden/` (plus the AccuracyCoin sub-test ROMs) remain the preferred
-> path because they need no live emulator at all; the vendored MIT TriCNES source is the permissible
-> in-repo fallback. Neither requires the copyleft references to be in reach.
+> **TriCNES is the deliberate exception, and it is not a firewall violation.** TriCNES is **MIT**
+> and written by the AccuracyCoin author; its ported models are attributed in `NOTICE` +
+> `docs/originality-and-provenance.md` section 1. Its source was vendored in-repo until v3.0.1 and
+> now lives **outside** the repository (maintainer decision, 2026-10-07): `~/reference-oracles/TriCNES`
+> (upstream at `94f1b117`) and `~/reference-oracles/TriCNES-rustynes-harness`. It may be consulted
+> for AccuracyCoin troubleshooting on the terms in `docs/ai-emulator-provenance-guardrails.md`
+> section 3a. The committed golden vectors under `crates/rustynes-test-harness/golden/` (plus the
+> AccuracyCoin sub-test ROMs) remain the preferred path because they need no live emulator at all.
The v2.0 accuracy push (toward a full pass) cross-diffs RustyNES's per-cycle bus stream against two
reference emulators. `/tmp` is wiped on reboot (CachyOS) — this is the recipe to regenerate.
@@ -75,16 +75,16 @@ stood here. A grep target survives a re-vendor; a line number does not.
### 2a. In-repo buildable instrumented harness (the per-cycle cross-diff oracle)
The cross-diff oracle used for the DMA-tail / Program-M work is a **trimmed, instrumented TriCNES
-built from source**, vendored self-contained in this repo (TriCNES is MIT — Chris Siebert 2025):
-
-- `crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/` — the buildable harness:
- `Emulator.cs` (instrumented with the per-cycle window logger), `Program.cs`, `6502Documentation.cs`,
- `mappers/` (all 10 `Mapper_*.cs` — **required to build**; salvaged 2026-06-08, the earlier salvage
- had omitted them so the harness did not build), `tricnes-harness.csproj`. Build with
- `dotnet build -c Release` (needs the .NET 10 SDK); rebuild into `/tmp/tricnes-harness` if you prefer
- an out-of-tree build dir.
-- `crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/` — the **complete** upstream TriCNES
- source (`.cs`/`.csproj`/`.resx` + `LICENSE`, no build artifacts; ~1 MB), for reference / re-trimming.
+built from source** (TriCNES is MIT — Chris Siebert 2025). It was vendored in this repo until
+v3.0.1 and now lives outside it:
+
+- `~/reference-oracles/TriCNES-rustynes-harness/` — the buildable harness: `Emulator.cs`
+ (instrumented with the per-cycle window logger; 88 lines against upstream), `Program.cs`,
+ `6502Documentation.cs`, `mappers/` (all 11 `Mapper_*.cs`, **required to build**),
+ `tricnes-harness.csproj`, `LICENSE`. Build with `dotnet build -c Release` (.NET 10 SDK). On a
+ machine without it, restore the tree from this repository's history before the v3.0.1 removal.
+- `~/reference-oracles/TriCNES/` — a clone of `github.com/100thCoin/TriCNES` at `94f1b117`, for
+ reference and re-trimming.
- Cross-diff driver: `scripts/tricnes_xdiff.py` (+ the ad-hoc helpers under `scripts/diag/`, salvaged
session diagnostics — see `scripts/diag/README.md`). RustyNES side: `scan_dma_abort`, `trace_dma_4015`.
- Individual AccuracyCoin sub-test ROMs (MIT, distinct builds) under
@@ -96,9 +96,9 @@ built from source**, vendored self-contained in this repo (TriCNES is MIT — Ch
> gitignored). The out-of-tree rule is for the **copyleft** references: if a **Mesen2** build (or
> puNES / FCEUX / GeraNES) is genuinely needed to *regenerate* an oracle trace, keep it **out of tree,
> outside the agent's allowed paths** — build and run it there, capture only its **output**, and diff.
-> **TriCNES is MIT and is the deliberate exception:** its harness + full source are vendored in-repo
-> (§2a) under their own permissive license, so a TriCNES trace can be regenerated from the in-tree
-> `tricnes-harness-src` with no out-of-tree source at all. The committed golden vectors above make the
+> **TriCNES is MIT and is the deliberate exception:** a TriCNES trace is regenerated from the
+> harness at `~/reference-oracles/TriCNES-rustynes-harness` (§2a), out of the repository since
+> v3.0.1. The committed golden vectors above make the
> cross-diff oracle self-contained without *any* live emulator, which is the preferred path.
## 3. PPU sub-dot oracles (Phase 6)
diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md
index 99657fdef..a9b929270 100644
--- a/docs/user-guide/getting-started.md
+++ b/docs/user-guide/getting-started.md
@@ -34,7 +34,7 @@ file with no install step.
### Option 2: build from source
-Building from source requires Rust 1.96 (pinned via `rust-toolchain.toml`)
+Building from source requires Rust 1.99 (pinned via `rust-toolchain.toml`)
and a working system-library set for the windowing / audio stack.
**Linux build deps (Debian / Ubuntu):**
diff --git a/screenshots/external/mapper-045-GA23C/Famicom Yarou Vol.1 7-in-1 (Unl).png b/screenshots/external/mapper-045-GA23C/Famicom Yarou Vol.1 7-in-1 (Unl).png
index 9fdb27207af364cbca3524c4235f4bf1a5a4bde6..8f89fdecd6e3e4d1ac081d6c7c0a8fba96a8d234 100644
GIT binary patch
literal 3110
zcmds4X;c#08a<#QPKl*f=9s0HW3TIQGtf-QSt||AIV%+^EUgSuv`o#RZm(OKl~`UH
z9Y{npMI2tCS)yrB4xnC#3Wv-D424HO->>)ct@Zw#b=La!+TULL+uvH}Tn!G~r=@P7
z4gk>d@%B6b07P*G-{3ICIbZy;8vu=2AJ07@Y4>JF+|HOcZhRf*^D5pYwiPFV5uuj+
z%pS{6y?r@>7S5MXkFS%!T5_N&ExD1sYp@Q8OxR1v58kS*>{->*tzKG>J_!okw4Ch4
z`1$!Du!T7@0)W&EfH-AfS_nY44#;$X0t6a>b~td~00D6G|M#LD(b;{(O5w4fu7}@+&0X08rx6;oq4Nw>^lEKs!qRARrq9L$72Qb}z0DNu~TEK-GTqKQ-C)
z!phH;elpP%WEwR*l(S6sboba=zVE
z+$@y~GScTzda=sDA0ZlVOzuR^U5XpHH1^Aok`F5GAI%L^B+%+R{p?pLETmTvMa`DC
zyaOu>oFbcyTmUSx-LbzRG)&0!J$V4Y@`=fq2@?&%e)xp3Ns%LnrP6teesR4xZ0j-i
zR_gF%do&!B(*^@vaZ@XllN~rJ3}_C@Hl0%HSsJ9^Os1_)xQ#B3!ma$-;
z@m7uav*9zb2nq+W!3*pw&2{S>mP-46@x0%cR*cd>lZQ_FrDc}N7vyiw$C5|OKcgPSLBM3iiq=}O@({!LW*9%ni_IwvT2If*W3IVoHgJ-It(`nXRqx0w(Hw1tYDO+m^1=DVef1|bM+}zlG?if-Ef2}W!N;KM=2pb%KRi4s*v7)8q1e50gFi2rs%%!K)R
zwGWNPNZ#|}vV*VTu^!r9+HRr^f|l!0U-K-<@9SYTZSl{>l7E*bRNL0s%bLdVS+59`
zZJG982NwM|Il(gRD}_7`hnaq1@cxqM_ftpR;V0IMrxxO$POtvG_0X;zH!+aNEK0t$
zO)XNpT!9!iZWf%j(M#KI=sO3!!b;L{2#tIieYR#Kggcb5>bXdE8kS1xyluZzxGF0g
ziOQ#nX3OJf(D;CEO^xh(xBH0#c3(S4!~8EttcNg9ZgXcsohHpC~u5QyvfqKR~GT+zsiYRSeF{3bD-W
z9Nb_CJ~4gkP&i)TouJ-AAr-_ZPjE*`v6iN#MACCX1i}t9ImEmi78}ed>oRA5PTFXA
za)-{tDAqPa5`K@NuOHEUT%`@eO7gaW=h#7CDjCTVPBtVl(}^kx*!=_Y$Y<0u*E!ty
zTk_Bu-?nGl+`pC*0>4h91&M70=`{=Im){?3L^0SeghqokEcy2xYlB@>qq+BhqsDPR
zuaAoXG(M_(*oTi>*g1^ZA(hZY!_UYvoH~%Br*hJ5EdwPqW=?@Z(a{Wp;KLCbk~D}>>~k)vSaJ|p5KMR3)k93VEX;s
zBDUAkc_USfdAPGxT=pVcN|gRnRzh8SO3mWY^gVeclXU`QGdoI`Q@%R-F$21HcN3%`
zds}1B6iHC(M(XivGY#bBMRLZ9oMatnBPTL6X$rE!GwksvS4hVUg88Zl{c!B9-RC{(
zdir>F^$Nr9aFsrfdGSE|x-Bz4>8Do*nq>H8`?pn2HAdJ7yx>N*>*l}N9R18Ent*h`
zMh_D(A7N0N%ex;Dh9{pL`dK(}0)<+6CFZeCyzUla^H&&yB+F*e$pflZ1+TW%9HqfIe(e|rBDAX87
zmCmJFdQFtnhk2XTaYky>eFf`+ba@{f{e`)czOGj<_FbDms8!2H75vFl2|+{bk?P&+
zG1Uw>rqr*v6iwx$glA&PAH0{w1o6#Ir~|hdk?PZsqo<pVslJ0@RlL1{u28vnDRyG8XQ)e^_2q8X;hc%aO{oK?I8iBI-YHrf3i~4?
z#A&ngoaOeFAP7SAkho2+SiwuGmJ~aQ8eTC=-?=g7G0>i{A7rTMt@7w@H5SFi_ti0G
zx=YUOaTdAx8|`4G!~}(XU~L%vIN#biTd^{RLjf~$?gcnNxs;NUc>eAzANkzAx>UEG
zkNUVl#`__2z7a+O=J4`Y1hoR?oCvH0SD9GaDMo9a1$UC?oJ?Pd}lA
z__t#u)Z=}>W|aIHzzeq4g&b|MV*Kb$NHj_A+?E-UxWJ5$+|rVaB@8xn5;Cy8irgId
z)H9@0c+y@dmu1Bb;q31^P-BOeeOhK(7&uITyIpd
zSQm7Xzr@$kYm8nIq*VDXaHtYula9*pX>f6V*s7R;s#>1?VS!y8*8C6JZ~k_AeT8QA
z3YQH9-P2s??J~@JU2r-js?s>0b0!xm~v$0}v|*6tVbO4cYXf4t@TNxBvBD)nc&+x(GN|jsw#-}nNNo#^v??06zNr!M=N?eI6|qY
zW`*^Yiwj=HF=4$Cm$!ky2X%=&gEg2NDy`KR$T*)qxof5H!#Uxs(i%4?ya4+&c9{9|
txMGx}x>5iPI}{)=Q}AD2*=4D#CXJ~UOJSF<*!=ni_;>|+R(r%=`ZwlGiTwZo
literal 3816
zcmc&%YgALm7M?>8P$a-AQwplC6uCsC}3R#F8OA1H_xt4J__ASC4A3oQt?
zsKrVQq6Oq3Qhb4u1fc{f4=I>bpaz#qL2>#TLwWMUNJ
zw@=P}9_a5d<-I8Y0Ed9}YqtP^z#&lD*}(Vcx7AMpz?TQCT^*d5JItv_|4``iW65@5
z!xZA7aNJ48obY*afpcc78Jk{q{e1YWEA}5Q&v5wUSl9)tKB>(q=f{D$KZPB&spQF)
zq{setu%g&oe4?Vj@7`}0(=k?KNdv=q|t0v6QT%FYpXCtbG
z{%jk^q;u|1SK0WguCqQ!Z_h+%NEbsOo(58}o$KsCC+SM^a9>jEwWBhc{IZ+WHFGT%9I
zS%VJ)D4CqoM%yfNbQN8#gR@7u&K!MWBhs6lJ;QB&Uk-uV
z{NK^Mwm7E+24cDOU$Pf^kpzGl)<0wRn!uY?n+c*xj^p7lQg<>4<<8{joBz=RFSbyS
zHa{(9PdNUh4m_0P>g)oCI9aLjNIh$3+>q4W_MN*^yKKjhg>Lhy(6s=%zOzWM#glB!
z&CyvOqC>9%OyW9$U|G{E~fho!z5CRL)
z-r{oeCDCRK*{|MFCyiPC#V_^-Vc&+^6psChS8NGW)|Kw>Gm#>er@ds0#oWEwITXoQ
z(~8z*^DB9)PkX#6Mt$@|253MHzL$^W9{7YKIx=RQT_Gl=#7^Z0oauTa-Bf;(_g+6P
zr>KoG$tYJ!jJ21Pl)4CoK0hHq6&{LBWBu|?8+elJK&$wce{Uu$dfe09HmtXDbjiZc
zpJdIglPfu=&1$9D)zrV4Z9~oMEB%ECiM5|6c^qdDn7dH=>r;u03;i=CD4R<~>+4>+Uv|T9l
zpu5%>b#k_;r^cw+C};DIkN-e9pv>Btn8>8E}K0H-}YLwNUMp-pc!JZIOsuD~O
z>bPt}ou~SZu~W=877E2&Aw}J0&mMivy`p~oa6k%6#6}XQ?bMFny9dij5XozIQ@(gq
z%&?Jx%Slb{+D7fb-%!fG!Jks@W9Nrhon=U*r#AfupbPHBG*dt@I*M5!Az1wK?3!kOzmEQ*Pr6(GtGGXl&su6WJ*UmljN6MT_|lH1x6*r{Q}~@UD>wP5
zS?x=<=wRq0R8-o%`L
zGaJy;)n2F^?*qSgl?Nl#fVrDEdw3ahrktpqA2fo=9EXSf^ze5m0~w*^w=!dCd3P3H7R4|
zUaB7+kjQfVFa+m??ZnlT=lrs38}ZaS+jZse&a;pvq_;!kkV+ILlARAjj%Wn~2SoBw
zZtFKAg|vC9(i24NEFojhF;-^6WA2_w43Lj5PCBltN=`Qnmbu${VMHi^GfC#eG{fK}
zH(M$mBMR`SdFvC?xQGKRf>RHVuG%o(4z1mO!`%>vlHfTPa07F!$XK_*`0a|9G`fQ&
z3obPoL_vF|rsENTFYQUD@$?IcOZlh+cIv4
za!cxo0#z;L;WIfwtKlyuSw$t&*i&R+Ei()@i0B+hK`K6vZYKi`Pm#O!tC|&&@zY|&
z!}Jkezc2eMDVuB@oXkGUZJ|(n<*ZYy-AYaHRf6&H$d!Dpix-e)aTt(C>CjD6V7%HJ^Ha-gBAJ_#a&~__*mhP
z>E|%R`0B&rjc-z&RLU0q`D<5?r43i!mK+(Mm>Nbx4-V@WkP=J1!Ra>o^{jZCJiRVR
z1wTfrU&@W||8_E;KA!sP6jI_sk0P0Atd}I=PjE=PM)T#bKv%lfuuJUuDP_HC8uYJ8
zEsJNzoK8Qqnr|GfDO2^VJo}g*Z?b%>C?gz@UxlaMw2Bnix0nMxpRKaZW7y>FuQv?+&
z7WjeZ`kO-w{IG3z(Oa-!f0Ccx-s$9NwMXH_Q&VFB2woJ?tkK#*QEpM
z_$dV=GqQ0--nP%8UFL0T3E0mixLxW&3smYI5yKzIpE;$0)ObIvsUN6c;7SbjGB6%@
z$s24Xu`d#-3#wTA6~mPcT|!SXbkJTKyo&T6lk;t#t+!;;59>jr7LhuV45h+bGH}0y
zHsqM+qGB@_?(Q}wXn4C2dY{e>I`QKk_E)NiP~gKMQU_7vk^7!-
zYn>*m3^L5bh@y-kkhmK(eP!ZFXKPXhp(*x7wPk;zA|BaS4vj))
z>dPNB6f)L)aRgV`Z@onvf40T;f+5A+rJ?v?tF8}?HH>$<`qBa)m}7wY>W^mcrpn^1izahh^V
zocSAE<7b2DoqK_J!PmGPt73FCI4(Z(M+Gdn86tFKrfPZ`*r19)!3Qvkj|PW`>EbLR
z)n3>eIE8YyJ%;tXBz#yJxY-l3{QJ+LEC9i_
q7zdEIZvcP=XYl{$f#CU5Gg7oT<6Hw}D8=$eHsI61wYS%Vo%k2?`+O+?
diff --git a/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md b/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md
index cac156971..55a94df67 100644
--- a/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md
+++ b/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md
@@ -19,6 +19,19 @@
> **and cited**, and absence means it was never catalogued rather than that it was
> finished.
>
+> **Re-targeted 2026-10-07 for the v3.1 → v4.0 line.**
+> [`plans/v3.1-to-v4.0-line-plan.md`](plans/v3.1-to-v4.0-line-plan.md) now
+> schedules what was still open, under maintainer decisions D1-D28. Each entry
+> that changed carries a dated `(2026-10-07 ...)` annotation naming the release
+> slot and the ticket. Two entries were closed against the tree, not the prose:
+>
+> - R1 (§6b) shipped as `T-MMC3-BG-A12` in v3.0.0;
+> - §7's six header/size mismatches were adjudicated, as refusals, in v2.9.6.
+>
+> The rest of the 2026-10-06 backlog survey's stale-entry list (the ROADMAP,
+> `docs/compatibility.md`, `docs/mappers.md` and others) is v3.1.0's records
+> item.
+>
> **Reconciled against `main` at v2.9.3 (2026-09-29) for v2.9.4.** Every open
> box and `[~]` below was re-checked against the tree, not against the prose;
> each change carries a dated `(2026-09-29 ...)` annotation naming the file and
@@ -147,6 +160,15 @@
> apps still ship as GitHub sideload + TestFlight; app stores and F-Droid are now
> targeted **after the v3.x hardware release** per the
> [line plan](plans/v2.9.4-to-v3.0.0-line-plan.md) "After v3.0.0 — External".)*
+>
+> *(2026-10-07: placed by the maintainer (D18, and ADR 0035's amendment of that
+> date). Mobile signing and the free store listings come at about **v3.9.x**,
+> right before v4.0.0's final activities. That covers Android developer
+> verification, which is global from 2027 and covers sideloads too, and iOS
+> signing so that TestFlight uploads run. D29, later the same day, put the v3.x
+> hardware release itself at the end of v3.9.x, so "after the v3.x hardware
+> release" above now means the last v3.9.x: signing is set up at v3.9.0, and
+> the listings follow the hardware release's device run.)*
---
@@ -286,6 +308,11 @@ remains are the optional/SQLite tails and any beta.5 polish.
unverifiable in CI. Source: `docs/netplay-webrtc.md` §4. Target:
**maintainer-manual**. *(2026-09-29: still open; target **after v3.0.0**
with the hosted signalling, as above.)*
+ *(2026-10-07: both `[M]` entries above are scheduled in **v3.4.0** as
+ `T-HOSTED-NETPLAY`. The hosting is Cloudflare (maintainer decision D19): a
+ Worker with Durable Objects for the lobby, and Cloudflare TURN with
+ short-lived credentials. Native 3-4 player netplay over `mesh_net.rs`
+ (`T-NETPLAY-MESH-NATIVE`) lands in the same release.)*
- `[x]` **Spectator netplay (H8)** — *(shipped; v1.8.9 reconcile: complete in
`crates/rustynes-netplay/src/spectator.rs` — `SpectatorSession`,
receive-only, determinism-safe, `MAX_SPECTATOR_FRAME_LOOKAHEAD` DoS bound, unit-tested;
@@ -340,6 +367,15 @@ the v1.7.0 **H1/H2** workstream + a maintainer-manual deploy/verify.
code change. Source: [ROADMAP](ROADMAP.md) "Beyond v2.0.0". Target:
**maintainer-manual**. *(2026-09-29: still open; the line plan lists
"RetroAchievements allowlisting" under **after v3.0.0 — External**.)*
+ *(2026-10-07: the three entries above are scheduled in **v3.4.0**: the proxy
+ on Cloudflare as `T-RA-PROXY` (D19), and RA hardcore compliance as
+ `T-RA-HARDCORE` (D25).
+ - RA's current requirement page asks for a privacy policy, offline unlock
+ queueing, and hardcore blocks on Lua and TAS playback as well as the usual
+ ones.
+ - It also specifies a User-Agent format that `RustyNES/ rcheevos/`
+ may not match.
+ - `docs/ra-integration-request.md` still says v1.8.8 and MIT/Apache.)*
---
@@ -439,7 +475,13 @@ take this on. See [v2.0.0 plan](plans/v2.0.0-master-clock-plan.md) and
### 6b. The residuals it unlocks (R1–R5)
-- `[ ]` **R1 — `mmc3_test_2/4` #3 (1-CPU-cycle "IRQ sooner" bracket)** — the
+- `[x]` **R1 — `mmc3_test_2/4` #3 (1-CPU-cycle "IRQ sooner" bracket)** —
+ *(CLOSED v3.0.0 "Cornerstone" as `T-MMC3-BG-A12`, `to-dos/ROADMAP.md`: both
+ 4-scanline ROMs pass all 13 sub-tests.
+ `crates/rustynes-test-harness/tests/mmc3.rs:119-120`
+ `mmc3_test_2_4_scanline_timing_strict` and `:201-202`
+ `mmc3_test_v1_4_scanline_timing_strict` are plain `#[test]`s, with no
+ `#[ignore]`. Verified 2026-10-07. The annotations below are history.)* the
CPU `T_last-1` IRQ-sample M2 sub-cycle phase; the integer 3-dots-per-cycle
timebase **cannot represent** it. **The 17-rollback graveyard / hard target with
a bounded-effort escape hatch** (fall back to by-design `#[ignore]` rather than
@@ -516,6 +558,10 @@ take this on. See [v2.0.0 plan](plans/v2.0.0-master-clock-plan.md) and
`docs/nesdev-hardware-emulation-checklist.md` "residual stale-shifter cases
tracked". Falls in the **v2.9.5** accuracy release by theme, but is not a named
item there; the attempt-only-if-a-game-demands-it condition stands.)*
+ *(2026-10-07: the maintainer lifted that condition (D20): the item is to be
+ attempted without a motivating game, in **v3.3.0** as `T-SPRITE0-STALE`. It is
+ kept only if AccuracyCoin stays all-pass; otherwise it is recorded as refuted.
+ [Line plan](plans/v3.1-to-v4.0-line-plan.md).)*
### 6c. Other v2.0-axis items
@@ -538,6 +584,11 @@ take this on. See [v2.0.0 plan](plans/v2.0.0-master-clock-plan.md) and
`Nes::set_extra_scanlines`, held at 0 under a movie or netplay; see
`docs/frontend.md`. The CPU-multiplier overclock and the sprite-limit
toggle stay open.)*
+ *(2026-10-07: both are scheduled for **v3.1.0** (maintainer decision D22):
+ `T-CPU-OVERCLOCK` and `T-SPRITE-LIMIT`. Both go in `HardwareOptions`, so
+ 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.)*
- `[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
@@ -582,7 +633,10 @@ take this on. See [v2.0.0 plan](plans/v2.0.0-master-clock-plan.md) and
### 6d. By-design non-targets (recorded for completeness — do NOT implement)
- `mmc3_test_2/6` (NEC rev B) — RustyNES defaults to Sharp rev A; mutually
- exclusive (R6).
+ exclusive (R6). *(2026-10-07: the default stays rev A. An opt-in per-game or
+ config override that runs the ROM under NEC is scheduled for **v3.1.0** as
+ `T-MMC3-NEC-OVERRIDE` (D20). The NEC variant has been selectable by NES 2.0
+ submapper since v2.9.6.)*
- `cpu_reset` full-protocol ×2 (interactive) — needs an externally-timed reset the
headless handler can't supply (R7).
@@ -612,6 +666,12 @@ are ROM-availability/coverage and a detection follow-up — none affect the orac
continuation stays partial; the next batch — 37/47, 12, 44/45/49,
74/191/192/194/195, 105, 115/121, 163, 228, 83/91/153 over the existing MMC3
and FCG/MMC1 cores — is **v2.9.6** in the line plan.)*
+ *(2026-10-07: v2.9.6 shipped 17 families (174 → 191). The next batch is
+ ranked by real titles (maintainer decision D26): 8 (FFE), 158, 207, 116,
+ 114/182/215, 197, 165, 172/173, 230, 235, 252, in **v3.2.0**
+ (`T-MAPPER-BREADTH-V3`). The long tail continues in **v3.8.0**. Each family is
+ first checked against the maintainer's library in `~/Dropbox/ROMs/` (D21).
+ [Line plan](plans/v3.1-to-v4.0-line-plan.md).)*
- `[~]` **Zero-library mappers (no freely-available ROM)** — families 28, 29, 31,
39, 81, 174, 179 have no freely-available dump, so they have no committed
screenshots (register-decode unit-tested only). Source: the standing
@@ -624,6 +684,12 @@ are ROM-availability/coverage and a detection follow-up — none affect the orac
`screenshots/besteffort/mapper-031-INL-NSF/`. Still none for **29, 39, 81,
174, 179** (`tests/roms/external/mapper-029-RET-CUFROM/` exists but is empty).
Target: **v2.9.6** tier promotions, where a legal homebrew ROM exists.)*
+ *(2026-10-07: v3.2.0's dump-corpus sweep (`T-DUMP-CORPUS`, D21) searches
+ `~/Dropbox/ROMs/` for 29, 39, 81, 104, 174, 179, 238, 261, 194, 195 and the 16
+ high-id boards. A local dump now counts as evidence for promotion when the
+ committed record names the title, the hash, the frames checked and a
+ screenshot (D23, `T-CURATED-EVIDENCE`). The dumps themselves are never
+ committed.)*
- `[x]` **`m176` Waixing FS005 detection follow-up** — three `.WXN` Chinese dumps
are misdetected as m30 (UNROM-512). Not an m30 bug. Source: the blank-boot-fixes
memory note. Files: `crates/rustynes-mappers`, frontend `game_db`.
@@ -803,6 +869,14 @@ are ROM-availability/coverage and a detection follow-up — none affect the orac
*(2026-09-29: still open. `Chu Liu Xiang` still renders no tiles
(`docs/mappers.md` "remains open"), and the six header/size mismatches are
still unadjudicated. Target: **v2.9.6**, the mapper release.)*
+ *(2026-10-07: the header/size mismatches **are** adjudicated, by v2.9.6:
+ rejected. `UNSUPPORTED` in
+ `crates/rustynes-test-harness/tests/external_coverage.rs:141-182` refuses
+ each by name with a typed reason: three CPROM, m58 *Study and Game*, two m146
+ *Lucky 777*, and a non-iNES Vs. hack. *Chu Liu Xiang* is still blank and sits
+ in the KNOWN_BLANK list. It is scheduled for **v3.2.0** as `T-KNOWN-BLANK`,
+ with a CPU trace first and the mirroring-register power-on hypothesis to
+ test.)*
- `[x]` **`m301` / `m348` UNIF board-map entries** — *(done; verified against the
tree 2026-08-14, v2.3.4 Workstream D: `unif.rs:246` maps board `"8157"` -> 301
and `unif.rs:270` maps `"830118C"` -> 348, both covered by the board-map tests
@@ -828,6 +902,12 @@ are ROM-availability/coverage and a detection follow-up — none affect the orac
[v1.0.0 synthesis](plans/v1.0.0-synthesis-plan.md); [ROADMAP](ROADMAP.md).
Target: **long-tail / no fixed version**. *(2026-09-29: unchanged — 174
families; **v2.9.6** adds the next batch, the rest stays long-tail.)*
+ *(2026-10-07: 191 families at v3.0.0. Breadth now follows real titles (D26):
+ **v3.2.0**, then **v3.8.0** for the long tail. "100% TASVideos" stays a
+ direction, not a gate. The 2026-10-06 ecosystem survey found no fixed
+ TASVideos NES compatibility set to measure against: its NESAccuracyTests page
+ is a results table, and which emulators it currently accepts was not
+ verified.)*
---
@@ -869,6 +949,19 @@ bridge, Vs. DualSystem on mobile and the SOCD switch (**v2.9.7**), the device
run and the iOS first compile (**after v3.0.0**, line plan "Mobile"), and the
app stores (**after the v3.x hardware release**).)*
+*(2026-10-07: FDS/NSF loading, Vs. DualSystem and the SOCD switch shipped on
+the bridge at v2.9.7; their device rows are T1-T12 of the run sheet. In the
+[v3.1 → v4.0 line plan](plans/v3.1-to-v4.0-line-plan.md):
+
+- the device run belongs to the hardware release (ADR 0043 Decision 2), which
+ D29 placed at the end of v3.9.x, so the stores ("after the v3.x hardware
+ release", above) come at the end of v3.9.x too;
+- the mobile extras are **v3.7.0** (D24): iOS box art, widget and external
+ display, and the EQ and cheat database on both apps;
+- the zero-copy framebuffer (MOB-06) follows UniFFI 0.33;
+- Android API 37 is **v3.6.0**;
+- signing and the store listings are **v3.9.x** (D18).)*
+
- `[x]` **v1.8.0 "Android" MVP** — *(shipped v1.8.0, commit `e83f8d35`:
`crates/rustynes-mobile` (UniFFI bridge) + `crates/rustynes-android` (JNI/NDK
host) + the Compose app under `android/`; ADR 0024.)* a hybrid + focused-MVP Android frontend.
@@ -907,7 +1000,9 @@ app stores (**after the v3.x hardware release**).)*
(CPU registers, disassembly, RAM hex, step-frame), v1.9.9 "Workshop", commit
`70a971bf`. Android has no debugger surface; the egui spike in
`crates/rustynes-android/src/lib.rs` was replaced. Remaining: an Android
- surface, if wanted. Target: **unscheduled**.)* kept only as an optional sideload
+ surface, if wanted. Target: **unscheduled**.)* *(2026-10-07: wanted (D24). The
+ Android debugger is scheduled with the zero-copy bridge (MOB-06), in **v3.7.0**
+ or the first release after UniFFI 0.33 releases `&mut [u8]`.)* kept only as an optional sideload
power-user overlay (Android) / a future hybrid embed (iOS), not a first-class
mobile surface. Source: [v1.8.0](plans/v1.8.0-android-plan.md),
[v1.9.0](plans/v1.9.0-ios-plan.md). Target: **v1.8.x / v1.9.x**.
@@ -1025,6 +1120,10 @@ broken-boot fix re-blesses its own snapshots, which next applies in **v2.9.6**.)
`gh api repos/doublegate/RustyNES/rulesets` on 2026-09-29). So the remaining
work is one repository setting, still a maintainer decision. Target:
**unscheduled**.)*
+ *(2026-10-07: offered to the maintainer with the v3.1 → v4.0 planning
+ decisions, and not chosen (D25 took the AccuracyCoin re-sync, the v1.8.x
+ checklist and RA hardcore approval, and left the merge queue out). It stays
+ open and unscheduled; turning it on is still one repository setting.)*
- `[x]` **`cargo-nextest` adoption** — *(REJECTED, WONT-FIX in v1.8.9-beta.1,
commit `a4b494cd`. The NOTE on the `test` job in `.github/workflows/ci.yml`
records why: nextest runs `cargo metadata --all-features` internally, which
diff --git a/to-dos/ROADMAP.md b/to-dos/ROADMAP.md
index 6e8007400..560260fa0 100644
--- a/to-dos/ROADMAP.md
+++ b/to-dos/ROADMAP.md
@@ -57,14 +57,23 @@ v2.8.0 → v0.9.7; the synthesis itself = **v1.0.0**.
## Status
-- **Current release:** **RustyNES 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.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.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;
+ - **v4.0.0**: the remaining public enums `#[non_exhaustive]`, plus MiSTer feature parity.
+
+ Format breaks no longer need a MAJOR (D2; `VERSION-PLAN.md`). The tickets the line minted are under "Tickets for the v3.1 → v4.0 line" below.
- **Re-plan (maintainer, 2026-09-29):** the mobile device runs (`docs/mobile-v2.9.3-run-sheet.md`) and the SuperStation One board session, with the fixes each produces, move after v3.0.0 to a v3.x hardware-verification release. [ADR 0043](../docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) makes **v3.0.0 the API major with a release-candidate core** (not hardware-verified), superseding ADR 0041's definition. The line from v2.9.4 is [`plans/v2.9.4-to-v3.0.0-line-plan.md`](plans/v2.9.4-to-v3.0.0-line-plan.md): records and CI, accuracy, mappers, targeted platform features, performance and the SDRAM arbiter, the release candidate, then v3.0.0.
- **Programme after v2.6.23 — the line to v3.0.0, the SuperStation One core** ([ADR 0041](../docs/adr/0041-hardware-release-is-v3.0.0.md), 2026-09-22). *(Superseded on 2026-09-29 by ADR 0043 and the re-plan bullet above: v3.0.0 is now the API major with a release-candidate core, and the board session and the hardware-verified core move to a later v3.x. What follows is the plan as it stood; the v2.7.x–v2.9.3 releases it describes have shipped.)* The first hardware-verified FPGA core is a new deliverable class, so it ships as a MAJOR version, and the board session moves from v2.7.0 to **v2.9.2** so that it measures the bitstream the audit lines produce rather than one they are about to replace. Three minor lines come first, each acting on AI-written audits in [`docs/audits/`](../docs/audits/README.md), every finding triaged against the code, pinned by a test that fails first, and gated: **v2.7.x** the core and frontend audits ([plan](plans/v2.7.x-core-frontend-audit-plan.md)); **v2.8.x** the libretro and RTL audits, and the off-die SDRAM build ([plan](plans/v2.8.x-libretro-rtl-audit-plan.md)); **v2.9.x** the re-audit, final seed sweeps, the release-candidate `.rbf` pair and the bring-up on the board ([plan](plans/v2.9.x-final-audit-and-hardware-plan.md)); **v3.0.0** the on-die `.rbf` as the headline, with the off-die `.rbf` as a labelled secondary ([plan](plans/v3.x-hardware-verification-plan.md), renamed 2026-10-05 from `v3.0.0-superstation-core-plan.md`). Features the older plans deferred "to v2.8+" are now deferred **past v3.0.0**, so each version number means one thing.
- **Shipped, inside v2.4.1 — v2.4.0 "Concordance".** It merged to `main` and was never tagged, because the workspace version never sat at 2.4.0 on any commit; v2.4.1 carries it. There is deliberately no `v2.4.0` tag. Its scope was: A concordance is an index of where every term actually occurs, and the release is scoped as one: reconcile what the project says about itself with what is true outside it. Four items, each traceable to a recorded deferral rather than newly invented — **(A)** the **owed upstream libretro sync** (`libretro-super` + `libretro/docs`), the one carried obligation with an outside deadline; **(B)** a core-side **timeline generation counter** replacing the last-seen-`cycle()` heuristic for stale telemetry (it covers a restore to a *later* state, which the heuristic cannot), deliberately **not** serialized, so it must land with its consumers and be AccuracyCoin-**verified**; **(C)** a **shared atomic-write helper**, lifting v2.3.9's seven properties out of `config.rs` and giving the Windows tail a real implementation rather than a portable spine; and **(D)** `skip_serializing_if` on `hd_packs` / `shader_presets`, which carry the same false byte-identity claim v2.3.9 corrected in prose only. Explicitly out of scope, and recorded as decisions rather than oversights: the remaining RAM Atlas exports (a cheat is a **write**, so it needs a locked-session predicate the watch export correctly does without), RAM Atlas per-game persistence (a restored verdict without its evidence is a claim that cannot be checked — this panel's whole argument in reverse), APU workstreams **D2 and D4** (unmeasured on purpose; their prior is a null, not an unknown), a CHANGELOG gate (**measured and rejected** — 62% false positives against the project's own history), and any store launch. See [`plans/v2.4.0-concordance-plan.md`](plans/v2.4.0-concordance-plan.md).
- **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), and v3.0.0 "Cornerstone", the current release and 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. 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", 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.
- **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`.
@@ -1205,7 +1214,18 @@ So two dumps from the same series assume CHR-AND passes at least 3 bits
(`$A` or more) before any write. That rules out the all-zero power-on value,
but does not pin a value. The maintainer chose `$F`.
-## T-GA23C-CHRRAM — *Famicom Yarou Vol.1*'s CHR-RAM is uploaded unbanked (found v3.0.0)
+## T-GA23C-CHRRAM — *Famicom Yarou Vol.1*'s CHR-RAM is uploaded unbanked (found v3.0.0, FIXED v3.0.1)
+
+**Fixed in v3.0.1, from a document.** NESdev's mapper 372 page describes that
+board as "INES Mapper 045 but with one bit of outer bank register #2 working
+as a CHR-ROM/RAM switch" and documents the RAM side as "CHR-RAM (1,
+unbanked)". That is the GA23C family's own CHR-RAM wiring, and it is what the
+trace below requires. A mapper 45 board with CHR-RAM now addresses it
+straight from PPU A10-A12 (`mmc3_boards.rs`, `chr_target`). Pinned by
+`m45_chr_ram_is_unbanked`, which replays the traced upload-then-bank sequence
+and fails without the fix. Only *Vol.1* moved among the five local mapper 45
+dumps (the other four have CHR-ROM); its menu now draws, and
+`EMULATION_EPOCH` rose to 2 with it. The original analysis follows.
*Famicom Yarou Vol.1 7-in-1* (mapper 45, 256 KiB PRG, CHR-RAM) shows noise,
both before and after T-GA23C-POWERON. Its committed screenshot was noise in
@@ -1220,7 +1240,38 @@ v2.9.8 too, and that review accepted it as running. Traced on 2026-10-05:
The program behaves as if this cart's CHR-RAM is addressed straight from PPU
A10-A12, bypassing the MMC3's CHR banks. The mapper 45 page does not mention
CHR-RAM at all. Open: fix only from a document or a hardware measurement, as
-for T-GA23C-POWERON.
+for T-GA23C-POWERON. (The document turned out to be the mapper 372 page.)
+
+## T-NTSC-PROVENANCE — is the Bisqwit-style NTSC pass derived from Bisqwit's published code? (raised v3.0.1, CLOSED v3.0.1: recorded as derived)
+
+**Closed in v3.0.1 (maintainer, 2026-10-07: treat it as derived).** The answer
+was already in the tree: `BISQWIT_WGSL` is generated verbatim from
+`rustynes-frontend`'s `ntsc_bisqwit.rs`, whose header and section 1 row have
+long recorded its tables as ported from Bisqwit's C via Mesen2's
+`BisqwitNtscFilter`. The "independent" sentence sat on a copy of that same
+code. `lib.rs` now carries a `// Provenance:` header and its own section 1
+row, `NOTICE` names the generated file, and `provenance_record_audit.rs` lost
+its one exception (`ROW_WITHOUT_HEADER` is empty; deleting the new header
+fails the audit). The record below is kept as it was raised.
+
+`crates/rustynes-gfx-shaders/src/lib.rs` (around line 311) documents the
+Bisqwit-style composite NTSC post-pass as "an independent implementation of
+the NES composite signal model documented at the NESdev wiki ('NTSC video');
+no third-party emulator code is incorporated". Two problems, found while
+restoring the softened Sunsoft 5B comment in v3.0.1:
+
+- The sentence is a self-certification, which
+ `docs/ai-emulator-provenance-guardrails.md` forbids as a finished claim.
+- The NESdev "NTSC video" page carries Bisqwit's own published code, and the
+ pass is named after him, so whether the WGSL derives from that code is a
+ real question. If it does, it needs the same treatment as every other
+ derived region (a site note, a `// Provenance:` header, a section 1 row in
+ `docs/originality-and-provenance.md`, `NOTICE`), and the code's licence on
+ that page decides compatibility.
+
+Not changed in v3.0.1: deciding it needs a careful comparison of the pass
+against the published code (the wiki page is public documentation, so reading
+it is permitted) and a maintainer decision, not an edit to the sentence.
## T-SPECTATOR-HISTORY — the spectator's input history grows without bound (found v2.9.9, FIXED v3.0.0)
@@ -1378,6 +1429,50 @@ and fixed from the mapper's NESdev page, or recorded in
`docs/accuracy-ledger.md` with the cause if it proves to be the game or the
dump.
+## Tickets for the v3.1 → v4.0 line (minted 2026-10-07)
+
+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`,
+`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.
+
+| 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-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 |
+| `T-DUMP-CORPUS` | Every implemented family with a dump in `~/Dropbox/ROMs/` gets a recorded boot; nothing committed but snapshots (D21) | MAP-02 | v3.2.0 |
+| `T-PHI2-WRITE` | Reopen PPU register write placement against the v2.6.17 conditions (D20) | ACC-03 | v3.3.0 |
+| `T-SPRITE0-STALE` | The sprite-0 stale shifter and the internal/external bus split (D20) | ACC-04 | v3.3.0 |
+| `T-HOSTED-NETPLAY` | A Cloudflare Worker lobby (Durable Objects) and Cloudflare TURN (D19) | NET-04, NET-02, NET-03 | v3.4.0 |
+| `T-RA-PROXY` | The browser RA proxy, on the same account (D19) | RA-01, RA-02 | v3.4.0 |
+| `T-RA-HARDCORE` | RetroAchievements hardcore compliance, then the application (D25) | RA-03 | v3.4.0 |
+| `T-NETPLAY-MESH-NATIVE` | Native 3-4 player netplay over `mesh_net.rs`, then mobile | NET-01, MOB-09 | v3.4.0 |
+| `T-MOVIE-INPUT-STREAM` | The Zapper, microphone, Vs. coins and FDS events in `.rnm` | CR-03 | v3.5.0 |
+| `T-LIBRETRO-OPTIONS-V2` | Core Options v2 and float-audio negotiation, behind capability checks | ecosystem 3 | v3.6.0 |
+| `T-LIBRETRO-CONSOLES` | Switch, PS Vita and 3DS investigated for the libretro core (D28) | LR-04 | v3.6.0 |
+| `T-ANDROID-API37` | `ACCESS_LOCAL_NETWORK` for LAN netplay, lifecycle-safe background audio, `targetSdk` 37 | ecosystem 5 | v3.6.0 |
+| `T-MOBILE-EXTRAS` | iOS box art, widget and external display; EQ and cheat DB on both apps; the live Kotlin TODOs (D24) | MOB-07, MOB-08 | v3.7.0 |
+| `T-MOB-06-ZEROCOPY` | The zero-copy framebuffer, once UniFFI 0.33 releases `&mut [u8]` (D24) | MOB-04 | v3.7.0 or the first release after UniFFI 0.33 |
+| `T-MOBILE-DISTRIBUTION` | Android developer verification, iOS signing, and the free store listings (D18) | MOB-02, MOB-03 | v3.9.x |
+| `T-API-ENUMS` | The remaining public enums `#[non_exhaustive]`, the v4.0.0 API break (D3) | the backlog's MAJOR section | v3.9.x trial, v4.0.0 |
+
## Open questions blocking planning
-None block Phase 1. Open questions in the docs (esp. `architecture.md`, `mappers.md`) will be revisited at the start of the phase that needs them resolved.
+None block the v3.1.0 start. The open maintainer decisions are tabled under
+"Still open" in [`plans/v3.1-to-v4.0-line-plan.md`](plans/v3.1-to-v4.0-line-plan.md),
+each asked at the release that needs it. *(The sentence this replaced, "None
+block Phase 1", dated from the original phase plan.)*
diff --git a/to-dos/libretro/IMPLEMENTATION_PLAN.md b/to-dos/libretro/IMPLEMENTATION_PLAN.md
index ba254a63b..13e7ed6ea 100644
--- a/to-dos/libretro/IMPLEMENTATION_PLAN.md
+++ b/to-dos/libretro/IMPLEMENTATION_PLAN.md
@@ -1,5 +1,11 @@
# RustyNES Libretro Core Implementation Plan
+> **Historical (2026-10-07).** Every phase below shipped, at v1.10.0 "Arcade"
+> and in the releases after it; `TASKS.md` has every box ticked. The forward
+> libretro work, v3.0.1 to v4.0.0, is under "The v3.x line" in
+> [`SPRINT_PLAN.md`](SPRINT_PLAN.md) and in
+> [`v3.1-to-v4.0-line-plan.md`](../plans/v3.1-to-v4.0-line-plan.md).
+
This document outlines the exact execution sequence to build out the `rustynes-libretro` core integration, strictly adhering to the architectural constraints established in `to-dos/libretro/SPRINT_PLAN.md` and `docs/libretro/*`.
## Proposed Changes
diff --git a/to-dos/libretro/SPRINT_PLAN.md b/to-dos/libretro/SPRINT_PLAN.md
index a85a19de0..4cace1d9c 100644
--- a/to-dos/libretro/SPRINT_PLAN.md
+++ b/to-dos/libretro/SPRINT_PLAN.md
@@ -1,5 +1,25 @@
# RustyNES Libretro Core Exhaustive Implementation & Integration Sprint Plan
+## The v3.x line (current, 2026-10-07)
+
+Everything from "Phase 1" down shipped (v1.10.0 "Arcade" onward) and is kept as
+the record of how the core was built. The forward work is below. The decisions
+it cites (D5, D28) were taken by the maintainer on 2026-10-07 and are tabled in
+[`v3.1-to-v4.0-line-plan.md`](../plans/v3.1-to-v4.0-line-plan.md).
+
+| Sprint | Release | Deliverable | Gate | Status |
+| --- | --- | --- | --- | --- |
+| L1 | v3.0.1 | **The Rust 1.96 pin** (D5). libretro's build image stopped passing `-C ar` on 2026-09-03 (`libretro-build-rust` `841f3619`; the rebuilt image passed 2026-09-23). Push a short-lived branch with `RUSTUP_TOOLCHAIN` removed and run its pipeline on 1.99. The mirror builds every pushed branch | All 15 jobs green, Apple included: drop the pin, the seven crates' `rust-version = "1.96"`, and the `libretro-cross` 1.96 leg. Any red job: keep the pin and record the job and error in `docs/agents/libretro.md`. Watch the Apple link lines either way, because the image's `-C link-arg` flags now reach the linker | **Done in v3.0.1.** Branch `test/libretro-rust-1.99` (only `RUSTUP_TOOLCHAIN: "1.99.0"`): pipeline 119614, 15/15 green. The pin now equals `rust-toolchain.toml`, the seven crates inherit the workspace `rust-version`, and `libretro-cross` fails on a mismatch instead of building a 1.96 leg |
+| L2 | v3.1.0 | `libretro/docs#1215` (the v3.0.0 page) answered and merged; `docs/libretro/UPSTREAM_SYNC.md` brought up to date (it still says no PR is open) | The upstream state recorded | — |
+| L3 | v3.6.0 | Core Options v2 (`SET_CORE_OPTIONS_V2`, v0 fallback), float-audio negotiation (`GET_AUDIO_SAMPLE_BATCH_FLOAT`, behind a capability check), LR-03, the Vs. DIP option | `abi_tests.rs` covers each path; every existing option keeps its key | — |
+| L4 | v3.6.0 | **Console targets** (D28): Switch, PS Vita and 3DS, investigated for a Rust libretro core. `libnx` was dropped at v2.9.8; re-check why rather than assume | A recorded go or no-go per target; any target that goes ahead gets a green buildbot job | — |
+| L5 | each release | Re-sync the forks and the upstream `.info` when the version, mapper count or capabilities change | `libretro_info` audit green; the upstream PR opened after the tag | — |
+
+Out of scope, as before: turbo and SOCD in the core (RetroArch provides both),
+HDR output, and the VFS v5 API.
+
+---
+
This document is the absolute source of truth for the Antigravity IDE (and any other developers or autonomous agents) when implementing the `rustynes-libretro` core integration for RetroArch. It expands upon the original architectural blueprint, defining strict code-level constraints, integration mechanisms, mathematics, and file structures.
This must be followed sequentially.
diff --git a/to-dos/mister/IMPLEMENTATION_PLAN.md b/to-dos/mister/IMPLEMENTATION_PLAN.md
index 0c2cb4d25..67204181a 100644
--- a/to-dos/mister/IMPLEMENTATION_PLAN.md
+++ b/to-dos/mister/IMPLEMENTATION_PLAN.md
@@ -1,4 +1,83 @@
-# RustyNES MiSTer core — implementation plan, v2.5.1 → v2.7.0
+# RustyNES MiSTer core — implementation plan
+
+## v3.1 → v4.0: feature parity, then the hardware release (current, 2026-10-07)
+
+The execution view of the MiSTer half of
+[`v3.1-to-v4.0-line-plan.md`](../plans/v3.1-to-v4.0-line-plan.md). The
+decisions it cites (D1-D29) were taken by the maintainer on 2026-10-07 and are
+tabled there. **D29, later that day, moved Phase H from right after v3.1.0 to
+the end of v3.9.x**, after every feature phase, so the board verifies the most
+complete core; the phase table below is in the new order. The narrative for the hardware release is
+[`v3.x-hardware-verification-plan.md`](../plans/v3.x-hardware-verification-plan.md).
+
+### Where the core actually is (after v3.0.1)
+
+
+
+| Component | State |
+|---|---|
+| Rungs 1-5, 7 (banking) | Closed. The CPU, PPU, APU, the six mappers and the full system are cycle-exact against the oracle on every gate. Ladder **199 passed / 0 failed / 1 expected failure** on-die and **200 / 0 / 1** off-die at v3.0.0; v3.0.1 adds `mapper4mmc3oddskip080` and the dot-0 A12 fix (ledger 3.49) |
+| Rung 6 (hardware) | **Open.** The SuperStation One is in hand. **No hardware has run any bitstream** |
+| Mappers | 0 NROM, 1 MMC1 (up to 256 KiB PRG; SUROM/SXROM refused), 2 UxROM, 3 CNROM, 4 MMC3 (rev A; NES 2.0 board-variant submappers refused), 7 AxROM (`rtl/ines_header.sv`) |
+| Cartridge size | on-die 256 K PRG / 128 K CHR; off-die 512 K / 256 K |
+| Saves, OSD, input | Battery saves through the HPS (v2.6.21, not yet seen on hardware); aspect, scandoubler, scale, crop and palette options (v2.9.8, not yet seen); two pads |
+| Region, audio | NTSC only; the 2A03 only, with no band-limiting and no expansion audio |
+| Absent | Save states, cheats, FDS, NSF, Vs. System, the Zapper, Four Score, paddle, keyboard, PAL |
+| Fit | On-die 22,922 / 41,910 ALMs (55%), **468 / 553 M10K (85%)**, 33 / 112 DSP; off-die 24,570 ALMs, 84 M10K (15%) |
+| CI | Nine rung-1 gates against a pinned oracle, plus the module gates. The full ladder runs by hand from a frozen worktree, and from v3.1.0 on a self-hosted runner (D16) |
+
+### The phases
+
+| 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) |
+| **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 |
+| **F4** (region and media) | v3.6.0-v3.8.0 | PAL/Dendy with VMODE (v3.6.0); FDS (v3.7.0); NSF, Vs. System, band-limited audio (v3.8.0) | D15 |
+| **Freeze** | v3.9.0 | the RTL feature freeze, the parity re-measure, the release-candidate pair the board session runs on | D29 |
+| **H** (hardware) | the last v3.9.x (D29), numbered after the session, no later than v4.0.0 | HW-0, Strands A-F on the SuperStation One on the frozen pair, HW-O6, fixes as gates, the re-sweep, the anchors flipped. No feature RTL | D1, D11, D13, D29 |
+| **Parity** | v4.0.0 | save states, cheats, PAL/Dendy, FDS with expansion audio, the Zapper, Four Score, the licensed-library mapper list, the re-measured incumbent | D3 |
+| **After** | v4.x | the SuperStation One distribution channel, then an openFPGA (Analogue Pocket) port | D17 |
+
+Open RTL items slot into the start of any release: RTL-1, RTL-5 and RTL-10 at
+v3.1.0. RTL-2 and RTL-4 are investigations. RTL-3 (OAM corruption) and RTL-6
+(APU power-on phase) wait on the board, which since D29 means the end of v3.9.x.
+
+**Risk of D29, for this half:** features F1-F4 land verified in simulation
+only, and HW-A8/HW-A9 (the FPGA device and SDRAM part) are read last. Write each
+feature's `bringup-log.md` rows as it lands, so the session's checklist is ready
+rather than assembled at the end. The full list:
+[Risks of D29](../plans/v3.1-to-v4.0-line-plan.md#risks-of-d29).
+
+### Scope, re-decided 2026-10-07
+
+- **Mappers: ranked by real titles** (D26). This replaces the 2026-08-23 "top
+ six" scope, under which `TASKS.md` recorded the remaining families as out of
+ scope.
+- **Hardware: the SuperStation One** (D11). The DE10-Nano is optional.
+- **Provenance-headered families** use rungs 1-3. Rung 4 needs an ADR 0037
+ amendment naming the maintainer, per family (D15). Those families are N163,
+ FME-7/5B, VRC7, Bandai FCG, FDS and Vs., and anything else the command finds:
+ `grep -rln "^// Provenance:" crates`.
+- **Not planned:** run-ahead, rollback netplay, HD packs, HD audio, TAS, Lua,
+ the debugger.
+
+### Standing rule 1, restated
+
+"A rung may not start until the one below is green" now means green on a
+recorded ladder run. That is the frozen worktree until the self-hosted runner
+exists, and both afterwards. CI's nine rung-1 gates are a subset and never
+stand in for the ladder. Rules 2-7 below are unchanged.
+
+**Rung 6 is the exception, by D29.** Rung 6 (hardware) stays open while the
+feature phases above it proceed: they need a green ladder, not a board. It
+closes at the hardware release, at the end of v3.9.x.
+
+---
+
+## History: the v2.5.1 → v3.0.0 plan
> **Re-targeted twice.** [ADR 0041](../../docs/adr/0041-hardware-release-is-v3.0.0.md)
> (2026-09-22) made the hardware-verified core and the contribution package v3.0.0;
@@ -18,7 +97,7 @@ what is next, and what each release owes.
until ADR 0043), suitable for contributing per
`ref-docs/2026-08-23-mister-core-contribution-requirements.md`.
-## Where the core actually is
+## Where the core was (as of v2.6.x; superseded by the table at the top)
+# v3.0.1 "Mortar" — the open items, the toolchain, and every unanswered review
+
+The first patch on v3.0.0 "Cornerstone"; the codename is "Mortar" (maintainer,
+2026-10-06). The maintainer's scope (2026-10-06):
+
+1. the **palette-offset A/B** (`8f449691` → `33ea0572`), which needs a quiet host;
+2. **T-GA23C-CHRRAM** (*Famicom Yarou Vol.1*'s CHR-RAM);
+3. the **odd-frame A12 stimulus** for the MiSTer core (`to-dos/mister/TASKS.md`);
+4. **every dependency and toolchain bump** in both repositories, Dependabot's
+ PR #584 included, except the parts that must stay on Rust 1.96;
+5. **every bot review comment** on every PR in both repositories, back to #1,
+ that never got a reply: adjudicated against the current code, answered,
+ and resolved, with the still-valid ones fixed.
+
+## Gates (how each is known done)
+
+| # | item | gate |
+| --- | --- | --- |
+| 1 | palette A/B | two independent `ab_check.sh` runs of `33ea0572` against `8f449691`, each started on a quiet host (one- and five-minute load under 1.5), order-bias control within ±2% or the run is void; the verdict recorded in `docs/performance.md` either way |
+| 2 | T-GA23C-CHRRAM | a red-first test of the traced upload sequence; Vol.1 boots to its menu; the other mapper 45 dumps unchanged; `EMULATION_EPOCH` raised (ADR 0045's trigger) |
+| 3 | odd-frame stimulus | a generated ROM whose writes REACH the exception (counted from the oracle's trace, not assumed), the DUT exact against the oracle, and the exception's mutant CAUGHT |
+| 4 | dependencies | every gate CI runs, on the new toolchain, plus a build of each changed ecosystem (Android, web, docs, Docker); the libretro path built on 1.96 |
+| 5 | bot sweep | every unanswered thread, review-body finding and agy review has a verdict with evidence; still-valid ones fixed; every reply posted and every open thread resolved |
+
+## Outcome
+
+| # | outcome | evidence |
+| --- | --- | --- |
+| 1 | pending a quiet host | the runner waits on load and a `BUSY` handshake with every heavy job; on 2026-10-07 another session's QEMU kept the host above the 1.5 load threshold after the release gates. The result lands in this table, the CHANGELOG and `docs/performance.md` if it runs before the merge, or carries to v3.1.0 otherwise |
+| 2 | done | `c986411f`: unbanked per NES 2.0 mapper 372's page ("CHR-RAM (1, unbanked)"); Vol.1 draws its "7 IN 1" menu; epoch 1 → 2 |
+| 3 | done, and it found a DUT defect | sibling `684af85`: `mapper4mmc3oddskip080` reaches the exception on 3 of 100 frames; the rule's live `rendering` gate was wrong (ledger 3.49), fixed to "was dot 1 rendering"; the exception's mutant CAUGHT on the new `IRQ_RISE` surface (`tb/irq_rise_diff.py`) |
+| 4 | done | `4aae8c13` (Rust 1.99, libretro buildbot first held on 1.96 via `RUSTUP_TOOLCHAIN`, then moved to 1.99 once test pipeline 119614 passed 15/15 (D5), every crate and action, Android, web, Docker, pre-commit); sibling `e77050d` (runner pinned, Verilator 5.020 → 5.032 under review) |
+| 5 | done | 290 ledger rows → 473 verdicts (140 fixed, 133 declined, 80 still valid, 55 refuted, 36 obsolete, 28 no findings, 1 open); the 80 fixed by four worktree agents and cherry-picked; 279 replies posted (153 thread replies, 77 resolves, 126 PR comments); afterwards 0 unresolved and 0 unanswered threads in both repositories |
+
+## What the sweep is NOT
+
+Bot summaries with no findings (CodeRabbit walkthroughs, Gemini quota notices
+and change summaries, Dependabot) are informational and get no reply. A
+finding answered by a later comment of ours is not re-answered.
diff --git a/to-dos/plans/v3.1-to-v4.0-line-plan.md b/to-dos/plans/v3.1-to-v4.0-line-plan.md
new file mode 100644
index 000000000..834f45d2e
--- /dev/null
+++ b/to-dos/plans/v3.1-to-v4.0-line-plan.md
@@ -0,0 +1,515 @@
+
+# v3.1.0 → v4.0.0: the hardware release, MiSTer parity, and the last API major
+
+This is the line after v3.0.0 "Cornerstone" and the v3.0.1 "Mortar" patch. It
+was drafted on 2026-10-07 from three read-only surveys of both repositories and
+the outside ecosystem, about 110 oracle items and 60 sibling items, and shaped
+by 28 maintainer decisions taken the same day (D1-D28, the table at the end),
+plus D29, which moved the hardware release to the end of the line.
+It continues
+[`v2.9.4-to-v3.0.0-line-plan.md`](v2.9.4-to-v3.0.0-line-plan.md), whose
+"After v3.0.0" list it schedules.
+
+Each release gets its own plan file. Each file states its gate before work
+starts, and its outcome table is filled in as the release lands. This file is
+the line's index. Version numbers here are slots, not promises. A release that
+grows splits into patches, and the order of the oracle themes can change
+without a new decision. Two things are fixed: the hardware release is the
+last thing before v4.0.0, after every feature has landed (D29), and v4.0.0 is
+the API major (D3).
+
+**D29 (maintainer, 2026-10-07, later the same day) reversed this line's first
+ordering.** The first draft put the hardware release right after v3.1.0 and
+before any feature RTL. The maintainer moved the whole of it, the board session
+and the mobile device run, to the end of v3.9.x, "so as much as possible has
+been implemented, integrated, fixed, enhanced, improved and optimized" before
+the board sees it. The cost is recorded under [Risks of D29](#risks-of-d29).
+
+**Where v3.0.1 leaves things** (verified 2026-10-06/07; `docs/STATUS.md` is the
+authority):
+
+- The emulator is at 191 mapper families (51 Core, 109 Curated, 31 BestEffort).
+ AccuracyCoin is 144/144 against the catalogue as re-synced on 2026-09-19;
+ upstream has since added two tests (`9fc47e06`, 2026-10-05). nestest is
+ 0-diff.
+- The MiSTer core has six mapper families (0, 1, 2, 3, 4, 7). Its ladder is
+ 199/0/1 on-die and 200/0/1 off-die at v3.0.0; v3.0.1 adds
+ `mapper4mmc3oddskip080`. On-die it uses 85% of M10K.
+- **No hardware has run any bitstream.**
+
+## Rules every release follows
+
+Unchanged from the v2.7 to v3.0 lines, except where a decision is cited.
+
+- **Triage → pin red → fix → gate**, through the `next-version` skill. A fix
+ counts only if a test failed first and reverting the fix is caught.
+- **Provenance firewall.** Reference emulators and third-party NES HDL are
+ black boxes. Run `grep -n "Provenance:"` before reading oracle source for RTL
+ work. Work up the ladder: documentation, then the Internet, then black-box
+ comparison, and derived source last.
+ - **Rung 4 on a Provenance-headered family needs an ADR 0037 amendment naming
+ the maintainer, one per family** (D15). The affected families are N163,
+ FME-7/5B, VRC7, Bandai FCG, FDS and Vs. The default is rungs 1-3.
+ - **TriCNES is the one consultable reference source** (D7). It is MIT and by
+ AccuracyCoin's author. It may be read, with attribution, to troubleshoot
+ AccuracyCoin tests its author has already solved. It lives outside the
+ repository, and the guardrails record the authorisation. Every other
+ reference emulator stays a black box.
+- **SemVer, as decided (D2).**
+ - Format breaks are allowed in any release, provided the release notes and
+ the CHANGELOG say so: save states, `.rnm` movies, the netplay protocol and
+ `EMULATION_EPOCH`.
+ - MAJOR is for a public Rust API break (`rustynes-core` and what it
+ re-exports) or a new deliverable class.
+ - `VERSION-PLAN.md` was rewritten to say so on 2026-10-07.
+- **The epoch rises with any change to a frame, a sample or a bus cycle**
+ (ADR 0045). A re-blessed golden or a moved snapshot is the trigger.
+- **The test-ROM corpus includes the maintainer's library** (D21).
+ - Dumps from `~/Dropbox/ROMs/` (nested) are copied into the gitignored
+ `tests/roms/external/` as each release needs them.
+ - They verify new work, and anything existing that was never verified on a
+ real dump.
+ - Never commit them. Commit only snapshots, screenshots and hashes.
+- **A local dump can promote a mapper tier** (D23). The committed record must
+ name the title, the dump hash, the frames checked and a committed screenshot.
+- **Off by default, still.** A new option that changes emulation is off by
+ default, and is carried in `HardwareOptions` so movies and netplay record it
+ (ADR 0044).
+- **Bitstreams.** Any release that changes RTL sweeps seeds 1-8 for both builds
+ on one build date. Two clean compiles of each must be byte-identical. A
+ release with unchanged RTL ships the previous pair and says so.
+- **Never claim hardware before the hardware release.** Its PR flips the
+ anchors, found by grep at the time.
+- **Ecosystem watch, every minor** (`T-ECOSYSTEM-WATCH`):
+ - the wgpu/egui pair (a breaking wgpu every three months; v31 is due about
+ now, an inference);
+ - the AccuracyCoin upstream diff;
+ - rcheevos;
+ - the libretro build image's commit log;
+ - the Android and iOS policy calendar;
+ - the Rust pin, moved in a minor about every second stable.
+ - Rust 1.100 ships 2026-11-12 and 1.101 2026-12-24. Algebraic float
+ operations (Rust 1.98) stay out of the chip crates, because they are not
+ deterministic.
+- **Gates.**
+ - fmt;
+ - clippy for every feature combination, including `retroachievements` and
+ both wasm builds;
+ - rustdoc `-D warnings`;
+ - the no_std build;
+ - the full `--features test-roms` suite and the commercial suites;
+ - the release audits;
+ - for the sibling, one frozen-worktree ladder run per build.
+
+## The releases
+
+| Slot | Oracle theme | Sibling theme (MiSTer phase) | Plan |
+| --- | --- | --- | --- |
+| v3.0.1 | (in progress) the open items, the toolchain, the review sweep, and the libretro 1.96 pin, tested and dropped (D5) | odd-frame A12 fix | [`v3.0.1-mortar-plan.md`](v3.0.1-mortar-plan.md) |
+| **v3.1.0** | AccuracyCoin re-sync, the overclock and sprite-limit options, emphasis, the NEC option, rewind and run-ahead in dual mode, records | small RTL items, the self-hosted runner, submission docs (Phase S) | [`v3.1.0-plan.md`](v3.1.0-plan.md) |
+| v3.2.0 | mapper breadth by real titles, the dump corpus, KNOWN_BLANK triage, tier promotions | options and cheap mappers (Phase F1) | [`v3.2.0-plan.md`](v3.2.0-plan.md) |
+| v3.3.0 | phi2 write placement and the sprite-0 stale shifter, the graphics stack | the memory platform: arbiter, DDR3, save states, rewind, headline switch (Phase F2) | [`v3.3.0-plan.md`](v3.3.0-plan.md) |
+| v3.4.0 | hosted netplay and RetroAchievements: Cloudflare, the RA proxy, hardcore compliance, native 3-4 players | the first big boards: MMC2/4, FME-7/5B, VRC2/4, the Zapper (Phase F3a) | [`v3.4.0-plan.md`](v3.4.0-plan.md) |
+| v3.5.0 | creator tools: the full movie input stream, Lua sockets and shared memory, desktop polish | MMC5, N163, VRC6/7, Bandai FCG, Famicom peripherals (Phase F3b) | [`v3.5.0-plan.md`](v3.5.0-plan.md) |
+| v3.6.0 | libretro: Core Options v2, float audio, the DIP option, console targets; Android API 37 | PAL and Dendy (Phase F4a) | [`v3.6.0-plan.md`](v3.6.0-plan.md) |
+| v3.7.0 | the mobile extras | FDS with its audio (Phase F4b) | [`v3.7.0-plan.md`](v3.7.0-plan.md) |
+| v3.8.0 | the remaining accuracy residuals, Vs. cabinets, the long-tail mappers | NSF, Vs. System, band-limited audio (Phase F4c) | [`v3.8.0-plan.md`](v3.8.0-plan.md) |
+| v3.9.0 | signing set up (D18); the v4.0 trial and re-audit | **the RTL feature freeze**, the parity re-measure, the RC pair | [`v3.9.0-plan.md`](v3.9.0-plan.md) |
+| **HW** (the last v3.9.x, D1, D29) | the mobile device run, then the store listings (D18) | the board session, Strands A-F (Phase H), on the frozen RC pair | [`v3.x-hardware-verification-plan.md`](v3.x-hardware-verification-plan.md) |
+| **v4.0.0** | the public enums `#[non_exhaustive]` (the API major) | MiSTer feature parity | [`v4.0.0-plan.md`](v4.0.0-plan.md) |
+
+**The hardware release has no number yet (D1), and it comes last (D29).**
+
+- **Position.** It is the last release of the v3.9.x line, after v3.9.0's RTL
+ feature freeze and release-candidate pair, and immediately before v4.0.0. Its
+ number is chosen after the session: the next v3.9.x patch if its fixes are
+ small. If they are large, it folds into v4.0.0, which then carries both.
+- **Feature RTL does not wait for it.** The sibling's feature work (Phases F1
+ to F4) lands in v3.2.0 to v3.8.0, verified in simulation only, as every
+ release since the core began has been.
+- **It still carries no feature RTL of its own.** The session runs on v3.9.0's
+ frozen release-candidate pair. A fix the board finds is RTL, so it reopens the
+ seed sweep at that day's build date; keeping features out of the release keeps
+ each lost margin attributable to one fix.
+- **Oracle work runs to the end.** Only the sibling freezes at v3.9.0.
+
+### Risks of D29
+
+Moving the board to the end trades early evidence for a more complete core under
+test. What that costs, written down so it is weighed rather than discovered:
+
+- **About eight releases of RTL, unverified on silicon.** Every feature from
+ v3.2.0 to v3.8.0 is proven against the oracle in simulation and nowhere else.
+ A systematic error that only the board shows (clocking, reset, the HDMI path)
+ would be found under a much larger core.
+- **A wrong device or memory part is found late.** If the SuperStation One's
+ FPGA is not the 5CSEBA6U23I7 the build targets (HW-A8), or its SDRAM is not
+ the part the constraints assume (HW-A9), the re-target is L-sized, and it
+ lands on the parity core rather than on six mapper families.
+- **The headline build stays provisional the longest.** Off-die becomes the
+ headline at v3.3.0 (D4), but its SDRAM constraints stay provisional until
+ HW-O6.
+- **Late board-against-oracle fixes cost the oracle too.** Under D13 each one
+ changes the oracle in the same release, red first, usually with an
+ `EMULATION_EPOCH` rise; found at the end, they cluster right before v4.0.0.
+- **Mitigation:**
+ - a simulation gate for every feature, mutation-proven, as now;
+ - a seed sweep in every release that changes RTL;
+ - v3.9.0's freeze, so the board sees one build;
+ - an optional, read-only look at the board's chip markings before then (still
+ open, below).
+
+### v3.1.0: options, emphasis, and the re-sync
+
+Oracle-heavy. The sibling side is small: RTL items, the runner, the
+submission documents, and preparation for Phase F1, which no longer waits for
+the board (D29). Detail:
+[`v3.1.0-plan.md`](v3.1.0-plan.md).
+
+- **AccuracyCoin re-sync first** (D25, `T-ACCURACYCOIN-RESYNC-2610`). Re-sync
+ to upstream HEAD (`9fc47e06` or later), then triage the two new tests and the
+ "Misaligned OAM Behavior" fix red-first. The sibling picks up any changed
+ goldens at its pin move.
+- **FE-01 CPU-multiplier overclock and FE-02 "disable sprite limit"** (D22,
+ `T-CPU-OVERCLOCK`, `T-SPRITE-LIMIT`). Both are in `HardwareOptions`, carried
+ in `.rnm` and the netplay `config_digest`, with one format and protocol bump
+ for both. FE-02 renders extra sprites only: the overflow flag and evaluation
+ timing stay exact.
+- **ACC-01, the PAL/Dendy emphasis red/green swap** (`T-PAL-EMPHASIS`). A
+ documented hardware behaviour, with an epoch rise.
+- **ACC-02, composite artifacts as an opt-in video option** (D20,
+ `T-COMPOSITE-ARTIFACTS`): differential phase distortion and inter-pixel
+ artifacts. Video only, so emulation output does not change.
+- **ACC-13, the NEC rev B MMC3 as a config override** (D20,
+ `T-MMC3-NEC-OVERRIDE`). It lets `mmc3_test_2/6` run under NEC without
+ changing the default.
+- **FE-09, rewind and run-ahead in Vs. DualSystem mode** (D27,
+ `T-PS-dual-runahead`). The two consoles are snapshotted together. ADR 0032
+ has a 2026-10-07 amendment.
+- **CI-02, an epoch fingerprint gate** (`T-EPOCH-FINGERPRINT`). A fixed panel
+ of output hashes fails CI when it moves without an epoch rise. Every behaviour
+ change in this line leans on it.
+- **Records:**
+ - DOC-01..DOC-09;
+ - retire `to-dos/v1.8.x-on-device-verification.md` into the v2.9.3 run sheet
+ (D25);
+ - `docs/mappers.md` and `docs/compatibility.md` open questions;
+ - CI-04's search exclusion for anything vendored that stays.
+- **The 1.96 pin: dropped in v3.0.1 (D5).** Test pipeline 119614 passed all
+ 15 jobs on 1.99.0, the Apple ones included. v3.1.0 only confirms the next
+ `main` pipeline, and `libretro-cross` now fails if the two toolchains differ.
+- **Sibling:**
+ - RTL-1 (`$2006` on a `v_pipeline` load dot), RTL-5 (the decay stimulus),
+ RTL-10 (register the 15 AccuracyCoin sub-test ROMs);
+ - TL-1 (the Verilator skew);
+ - the self-hosted ladder runner (D16);
+ - Phase S documentation: SUB-1 (a dated `ref-docs/` record of the 2026-09-26
+ wiki change), SUB-2 (re-scope the checklist to the live page), SUB-5
+ (refresh `submission-case.md`);
+ - TL-5 (these planning docs, done 2026-10-07).
+
+**Gate:**
+
+- AccuracyCoin all-pass against the re-synced catalogue, whatever its new count
+ is, stated with the upstream commit;
+- each new option's red-first test, and a movie and a netplay session refused
+ across a mismatched option;
+- the epoch fingerprint gate shown to fail on a seeded mutation;
+- the sibling ladder 0 failed, with no seed sweep unless the RTL changed.
+
+### v3.2.0: mappers, both cores
+
+Plan: [`v3.2.0-plan.md`](v3.2.0-plan.md).
+
+- **Oracle (D26, `T-MAPPER-BREADTH-V3`):** the missing families that have real
+ titles, checked against the Dropbox library first.
+ - 8 (FFE; staged dumps already refused), 158 (Tengen 800037), 207 (Taito
+ X1-005 variant), 116, 114/182/215, 197, 165, 172/173, 230, 235, 252.
+ - 157 (Datach) only if the barcode reader is in scope.
+ - The demand for each is unverified until a dump is found.
+- **ACC-08/ACC-09, KNOWN_BLANK triage** (`T-KNOWN-BLANK`): 52 dumps, starting
+ with *Chu Liu Xiang*, the Sansuu 1-3 Nen titles and *Gradius II (VC)*. The
+ ratchet holds in both directions.
+- **MAP-01 tier promotions under D23** (`T-CURATED-EVIDENCE`): candidates 47,
+ 121, 191, 290, 154 and 243.
+- **The dump-corpus sweep (D21, `T-DUMP-CORPUS`):** every family with a
+ Dropbox dump and no recorded boot gets one.
+- **Sibling, Phase F1 (D12, first part):**
+ - FB-16 core options at the start of the release (custom palette, +8 sprites,
+ the region placeholder off);
+ - FB-2 SUROM/SXROM (off-die);
+ - the next ~10 by coverage per effort: 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 (an oracle-gated rung).
+
+**Gate:**
+
+- each family pinned by a register-decode test and a boot snapshot;
+- the honesty gate's tier counts updated;
+- every sibling family with bus and checkpoint gates and a caught mutant;
+- one seed sweep for the release.
+
+### v3.3.0: the memory platform
+
+Plan: [`v3.3.0-plan.md`](v3.3.0-plan.md).
+
+- **Sibling, Phase F2 (D12, second part):**
+ - FB-20, the slot-scheduled SDRAM arbiter, with RTL-9 closing;
+ - DDR3 bring-up;
+ - FB-4 save states (the framework's four slots, oracle-gated by a
+ serialise/restore round trip), then FB-5 rewind;
+ - the real `hps_io` under Verilator for every HPS-facing feature (D16, TL-8).
+- **The headline switch (D4).**
+ - From this release the off-die build is the headline.
+ - The on-die build continues as a reduced "lite" build.
+ - `releases/` carries both from here on.
+- **Oracle (D20):** ACC-03 phi2 write placement, reopened against the v2.6.17
+ conditions, and ACC-04 the sprite-0 stale shifter. They take one
+ state-and-epoch break together. Both are measured against AccuracyCoin, and
+ each is kept only if the whole battery holds.
+- **The graphics stack:** wgpu 31 and egui 0.37 together, with the vendored
+ `egui-winit` re-diffed, or removed if upstream has caught up.
+
+### v3.4.0: hosted services, and the first big boards
+
+Plan: [`v3.4.0-plan.md`](v3.4.0-plan.md).
+
+- **Oracle (D19, D25):**
+ - NET-04 / RA-01: a Cloudflare Worker with Durable Objects for the lobby, and
+ Cloudflare TURN with short-lived credentials minted by the Worker
+ (`T-HOSTED-NETPLAY`);
+ - the browser RA proxy (`T-RA-PROXY`);
+ - a privacy policy (both RA and F-Droid's `NonFreeNet` need one);
+ - RA hardcore compliance (`T-RA-HARDCORE`): block Lua and TAS playback in
+ hardcore, queue offline unlocks, check the User-Agent format, refresh
+ `docs/ra-integration-request.md`, then apply;
+ - NET-01, native 3-4 player netplay over the existing `mesh_net.rs`
+ (`T-NETPLAY-MESH-NATIVE`).
+- **Sibling, Phase F3a:**
+ - MMC2/MMC4 (9/10, *Punch-Out!!*);
+ - FME-7 with 5B audio (69, Provenance-headered, so rungs 1-3 only);
+ - VRC2/VRC4;
+ - FB-9 Zapper.
+
+### v3.5.0: creator tools, and the expansion-audio boards
+
+Plan: [`v3.5.0-plan.md`](v3.5.0-plan.md).
+
+- **Oracle:**
+ - CR-03, the full movie input stream (Zapper, microphone, Vs. coins and
+ service, FDS disk events; an `.rnm` format bump, `T-MOVIE-INPUT-STREAM`);
+ - CR-01 Lua WebSockets and CR-02 shared memory;
+ - desktop polish FE-03..FE-08 and FE-10.
+- **Sibling, Phase F3b:**
+ - MMC5 (needs v3.3.0's arbiter);
+ - N163, VRC6, VRC7/OPLL, Bandai FCG (all Provenance-headered except VRC6;
+ D15);
+ - FB-12 expansion audio with each;
+ - FB-11 Famicom peripherals.
+
+### v3.6.0: libretro, Android API 37, and PAL on the FPGA
+
+Plan: [`v3.6.0-plan.md`](v3.6.0-plan.md).
+
+- **Oracle:**
+ - libretro Core Options v2 and float-audio negotiation, both behind a
+ capability check;
+ - LR-03 the Vs. DIP option;
+ - D28, an investigation of console targets (Switch, PS Vita, 3DS) for a Rust
+ libretro core, recorded with a go or no-go per target
+ (`T-LIBRETRO-CONSOLES`);
+ - Android API 37 (`T-ANDROID-API37`): the `ACCESS_LOCAL_NETWORK` runtime
+ permission for LAN netplay, and background audio under a valid lifecycle.
+ Play's API 37 deadline is about 2027-08-31 (an inference from the yearly
+ pattern).
+- **Sibling, Phase F4a:** FB-7 PAL and Dendy with `T-MISTER-VMODE`. The oracle's
+ PAL step table is Provenance-headered, so the RTL is written from nesdev.
+
+### v3.7.0: the mobile extras, and FDS on the FPGA
+
+Plan: [`v3.7.0-plan.md`](v3.7.0-plan.md).
+
+- **Oracle (D24, `T-MOBILE-EXTRAS`):**
+ - iOS box art and a home-screen widget;
+ - iOS external-display output;
+ - the 20-band EQ and a cheat database on both apps;
+ - MOB-08, the live Kotlin TODOs.
+ - MOB-06, the zero-copy framebuffer (`T-MOB-06-ZEROCOPY`), lands here if
+ UniFFI 0.33 has released (it is merged upstream as PR #2940). Otherwise it
+ moves to the first release after.
+- **Sibling, Phase F4b:** FB-13 FDS (disk image in SDRAM or DDR3, the BIOS on
+ die, write persistence through the HPS, a disk-swap OSD) with FDS audio. Both
+ are Provenance-headered, so the RTL is written from documentation.
+
+### v3.8.0: residuals and the rest of the console
+
+Plan: [`v3.8.0-plan.md`](v3.8.0-plan.md).
+
+- **Oracle:**
+ - ACC-05 (FDS `$4030.D1`), ACC-06 (a measured-RC filter, opt-in) and ACC-10
+ (2A03H), each only where a source exists;
+ - ACC-07, the Vs. DualSystem real cabinets (the combined *Wrecking Crew* dump
+ first);
+ - MAP-05, the long-tail mappers.
+- **Sibling, Phase F4c:** FB-14 NSF, FB-15 Vs. System, and FB-17 band-limited
+ audio, written from documentation and the oracle. It was to be informed by
+ HW-C4, which now runs after it (D29), so HW-C4 re-checks it at the end.
+
+### v3.9.x: the freeze, distribution, v4.0 preparation, and the hardware release
+
+Plan: [`v3.9.0-plan.md`](v3.9.0-plan.md).
+
+- **Mobile signing and store listings (D18).**
+ - Register for Android developer verification, which is global from 2027.
+ Wider distribution than the 20-device limited account needs a government
+ ID.
+ - Set up iOS signing so TestFlight uploads actually run (MOB-02). TestFlight
+ builds expire after 90 days, and uploads need Xcode 27 from about April
+ 2027, an inference.
+ - Then the Play, F-Droid (or IzzyOnDroid) and App Store listings, free and
+ without monetisation (ADR 0035).
+ - This is the release right before v4.0.0's final development, test and
+ release activities.
+- **v4.0 preparation (v3.9.0):**
+ - trial the enum break on a branch;
+ - re-run the four audit scopes;
+ - MiSTer: **the RTL feature freeze**, the parity re-measure, then the RC
+ bitstream pair at the RC date. The board session runs on that pair.
+- **Then the hardware release (D29)**, the last v3.9.x: the board session and
+ the mobile device run, described under [the hardware release](#hw-the-hardware-verification-release-number-chosen-after-the-session-d1).
+ The store listings go in after its device run (ADR 0035 asks for the device
+ evidence first; D18 places them here).
+
+### HW: the hardware-verification release (number chosen after the session, D1)
+
+Plan: [`v3.x-hardware-verification-plan.md`](v3.x-hardware-verification-plan.md).
+Strands A-F come from
+[`v2.9.x-final-audit-and-hardware-plan.md`](v2.9.x-final-audit-and-hardware-plan.md),
+and the bring-up worksheet is the sibling's `docs/bringup-log.md`.
+
+- **Last before v4.0.0 (D29).** It follows v3.9.0's RTL feature freeze and
+ runs on that release's frozen release-candidate pair: the near-parity core,
+ on-die and off-die, rather than the six-mapper core of v3.0.x.
+- **The SuperStation One only** (D11). The DE10-Nano becomes an optional later
+ row (HW-X3). The two-board checklist box is split, never ticked on one board
+ (HW-X2).
+- **Order:**
+ 1. HW-0, stage the kit;
+ 2. Strand A, including HW-A8 (the FPGA marking: 5CSEBA6U23I7 against the
+ published 5CSXFC6D6F31) and HW-A9 (the SDRAM part);
+ 3. Strand B on-die;
+ 4. Strand C (palette, H/V timing, resting audio level, band-limiting);
+ 5. Strand D (battery, AccuracyCoin as bytes, the incumbent A/B, a soak of 4
+ hours or more, the save across a power cycle);
+ 6. the off-die strand, plus HW-O6, the real SDRAM constraints;
+ 7. E1/E2;
+ 8. F1, re-measuring the incumbent on the same silicon.
+- **No feature RTL in this release.** Every fix the board finds becomes a gate
+ first where simulation can reach it. Then re-sweep at the release date,
+ re-run the failed strand and every later one, and flip the anchors (HW-X1).
+- **Board against oracle (D13).** The board wins only when it agrees with
+ documented hardware behaviour. The RTL and the oracle are fixed in the same
+ release, red first, with an epoch rise and an ADR 0037 amendment per case.
+- **The mobile device run** (MOB-01, the 60 rows of
+ `docs/mobile-v2.9.3-run-sheet.md`, extended by every mobile feature landed
+ since, v3.7.0's extras included) belongs to this release under ADR 0043
+ Decision 2. It can split into its own patch if the phone session and the board
+ session fall on different days. The store listings (D18) are submitted after
+ it, on the builds it passed.
+- **After it (D10).** Write the feature delta and the re-measured incumbent
+ number into `submission-case.md`; under D29 that delta is nearly the parity
+ list. Then the maintainer decides whether the
+ sibling goes public and whether to email MiSTer-devel.
+
+**Gate:**
+
+- every `bringup-log.md` row PASS against the md5 read off the console;
+- the ladder green on the tagged commit;
+- AccuracyCoin read back as bytes and diffed;
+- every hardware anchor flipped in the same PR.
+
+### v4.0.0: the API major and MiSTer parity (D3)
+
+Plan: [`v4.0.0-plan.md`](v4.0.0-plan.md).
+
+- **The API break** (`T-API-ENUMS`). The remaining public enums become
+ `#[non_exhaustive]`: about 13 of 20 in `rustynes-core`, 21 of 27 in
+ `rustynes-mappers`, and others in the chip crates, counted at planning time.
+ It is the one change found that must be a MAJOR. Release notes restate every
+ break since v3.0.0.
+- **MiSTer parity:**
+ - save states, cheats, PAL/Dendy, FDS with expansion audio, the Zapper, Four
+ Score;
+ - the mapper families covering the incumbent's licensed-library list;
+ - a re-measured incumbent comparison.
+- **v4.0.0 follows the hardware release** (D1, D29), so the parity core it
+ ships is the one the board verified. If the session's fixes were large, the
+ hardware release folds into v4.0.0 and this release carries both.
+- **MOB-04 is not a MAJOR on its own.** `rustynes-mobile` is internal under
+ `VERSION-PLAN.md`'s definition of public API, so the bridge API change can
+ land in any minor.
+
+## After v4.0.0 (recorded, not planned here)
+
+- **The SuperStation One distribution channel, then an openFPGA (Analogue
+ Pocket) port** (D17), as v4.x line items. `nes_top.sv` is kept
+ framework-free for the port.
+- **The MiSTer-devel submission**, if D10's decision, taken after the
+ hardware release (now just before v4.0.0, D29), says go.
+ Allow a month or more for review. The repository transfer is one-way, and
+ whether it is still part of the process must be confirmed.
+- **winit 0.31**, once egui adopts it (not before Q1 2027, an inference).
+- **A Rust 2027 edition migration**, if rustc gains `Edition2027` (re-check
+ about Q2 2027). It would be a post-v4.0 minor.
+- **The DE10-Nano run** (HW-X3), if a board appears (D11).
+
+## Maintainer decisions
+
+Taken 2026-10-07 unless marked. Each binds every plan in this line.
+
+| ID | Decision |
+| --- | --- |
+| D1 | The hardware-verified release is numbered after the board session, no later than v4.0.0 |
+| D2 | Format breaks (state, movie, netplay, epoch) are allowed in any release with notes; MAJOR = a public Rust API break or a new deliverable class |
+| D3 | v4.0.0 = the remaining public enums `#[non_exhaustive]` plus MiSTer feature parity |
+| D4 | On-die stays the headline until save states (v3.3.0); off-die is the headline from then, on-die a "lite" build; `releases/` carries both |
+| D5 | The libretro 1.96 pin is tested in v3.0.1 with a pushed branch pipeline on 1.99; dropped if all 15 jobs pass, kept and recorded otherwise |
+| D6 | T-NTSC-PROVENANCE: the Bisqwit-style NTSC pass is treated as derived from Bisqwit's code on the NESdev "NTSC video" page (header, section 1 row, `NOTICE`; the self-certification removed) |
+| D7 | The vendored TriCNES source moves out of the repository after a licence check (MIT, verified 2026-10-07 upstream and locally); it stays available, attributed, as reference for AccuracyCoin troubleshooting |
+| D8 | v3.0.1's bitstreams ship at build date 261007 even if the merge is later |
+| D9 | Cleanup authorised: the v3.0.1 sweep branches, PR #584 closed after the merge, the release, review and pin-test branches deleted after the merges |
+| D10 | MiSTer submission: prepare the documents alongside the board work; decide the public repository and the submission after the hardware release and the incumbent re-measure (which D29 places just before v4.0.0) |
+| D11 | Verification on the SuperStation One only; the DE10-Nano is an optional later row |
+| D12 | Feature order: options and cheap mappers (v3.2.0), then save states (v3.3.0), then PAL/Dendy and FDS. Originally "after the board"; since D29 the order stands and no longer waits for it |
+| D13 | When the board and the oracle disagree, the board wins where it agrees with documented hardware; RTL and oracle are fixed in the same release, red first, with an epoch rise and an ADR 0037 amendment |
+| D14 | The RTL keeps its long comments |
+| D15 | Provenance-headered families: rungs 1-3 by default; rung 4 needs an ADR 0037 amendment naming the maintainer, per family |
+| D16 | Sibling CI: a self-hosted runner now; the real `hps_io` under Verilator at v3.3.0; no hosted-CI goldens, no Quartus container |
+| D17 | Other homes: the SuperStation One channel first, then an openFPGA port, both v4.x |
+| D18 | Mobile signing and store listings at about v3.9, right before v4.0.0's final activities |
+| D19 | Hosting on Cloudflare: Durable Objects lobby, Cloudflare TURN, the RA proxy |
+| D20 | Reopen ACC-02 (opt-in), ACC-03, ACC-04 and ACC-13 |
+| D21 | `~/Dropbox/ROMs/` is a test source; needed dumps copied into the gitignored `tests/roms/external/`; never committed |
+| D22 | FE-01 overclock and FE-02 sprite limit both in v3.1.0, carried in movies, netplay and `config_digest` |
+| D23 | A local dump counts as Curated evidence when the record names the title, hash, frames and a committed screenshot (`docs/compatibility.md`) |
+| D24 | All four mobile extras: MOB-06 after UniFFI 0.33, iOS box art and widget, iOS external display, EQ and cheat DB on both |
+| D25 | AccuracyCoin re-sync first in v3.1.0; the v1.8.x checklist retired into the v2.9.3 run sheet; RA hardcore approval worked toward. The merge queue (CI-01) is not adopted |
+| D26 | Mapper scope ranked by real titles, in both repositories |
+| D27 | Lift ADR 0032's rewind and run-ahead exclusions in dual mode |
+| D28 | Investigate console targets (Switch, PS Vita, 3DS) for the libretro core |
+| D29 | The hardware release, the board session and the mobile device run, moves to the end of v3.9.x, after v3.9.0's RTL freeze and immediately before v4.0.0, so the board verifies the most complete core (later the same day; supersedes the "before any feature RTL" ordering of D1's first reading) |
+
+**Still open** (asked at the release that needs them):
+
+| When | Decision |
+| --- | --- |
+| each release | the codename, and merge authorisation |
+| HW | the hardware release's number (D1), with the session's evidence in hand |
+| HW | whether the sibling repository goes public, and whether to submit to MiSTer-devel (D10) |
+| any time before HW | an optional, read-only identification of the SuperStation One's FPGA marking (HW-A8) and SDRAM part (HW-A9), with no bitstream loaded, as insurance against an L-sized re-target found at the end (D29's main risk). Not planned; the maintainer's call |
+| v3.2.0 | whether 157 (Datach) is in scope: it needs a barcode-reader input device |
+| v3.4.0 | the netplay and RA proxy domain, and who operates it (D19 sets the provider, not the name) |
+| v3.6.0 | which console targets go forward, after D28's investigation |
+| v3.9.x | Android identity: full developer verification, or the 20-device account plus IzzyOnDroid/F-Droid |
+| v3.9.x | whether upstream `releases/` ever carries the off-die build (SUB-12) |
diff --git a/to-dos/plans/v3.1.0-plan.md b/to-dos/plans/v3.1.0-plan.md
new file mode 100644
index 000000000..a6ed63983
--- /dev/null
+++ b/to-dos/plans/v3.1.0-plan.md
@@ -0,0 +1,91 @@
+
+# v3.1.0: options, emphasis, and the AccuracyCoin re-sync
+
+The first minor of the line in
+[`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md). It is drafted on
+2026-10-07, before v3.0.1 "Mortar" has merged. The codename is the
+maintainer's, asked at the cut, and the file is renamed
+`v3.1.0--plan.md` then. Re-read every "Where it starts" fact against
+the tree when the release begins, because v3.0.1 may still move some of them.
+
+**The release is mostly oracle work.** The sibling side carries small RTL items
+and documents. Feature RTL starts in v3.2.0: it no longer waits for the
+hardware release, which moved to the end of v3.9.x (D29, 2026-10-07). The
+sibling can prepare Phase F1 here (the options surface, the mapper batch's
+stimuli).
+
+## Where it starts
+
+| Fact | Value | Source |
+| --- | --- | --- |
+| AccuracyCoin | 144/144 against the catalogue as re-synced 2026-09-19 (#528) | `docs/STATUS.md` |
+| AccuracyCoin upstream | 8 commits since, through `9fc47e06` (2026-10-05): "Added 2 new tests", a "Misaligned OAM Behavior" fix, a PPU "Data Bus" → "IO Bus" rename | GitHub API, 2026-10-06 |
+| `EMULATION_EPOCH` | 2 (v3.0.1) | `crates/rustynes-core/src/hardware_options.rs` |
+| `.rnm` / netplay | format 5 / protocol 6 (`"RNE6"`) | ADR 0045 |
+| CPU-multiplier overclock | absent (`rg cpu_multiplier` finds nothing); the scanline overclock shipped v2.9.7 | backlog FE-01 |
+| "Disable sprite limit" | the UI persists the flag and nothing reads it; `rustynes-ppu` has no hook | `config.rs:1797`, `settings_panel.rs:1937-1942` |
+| PAL emphasis | the red/green swap is "not modelled, by choice" | `rustynes-ppu/src/emphasis.rs:62-63` |
+| Dual-mode rewind and run-ahead | excluded by ADR 0032 | ADR 0032 and its 2026-09-30 amendment |
+| Epoch rule | enforced by hand only | ADR 0045 Consequences; `docs/agents/ci-and-release.md` |
+
+## Scope and gates
+
+Every gate is stated before work starts. "Red first" means a test that fails
+on the v3.0.1 tree, and a mutation that reverts the fix and is caught.
+
+| # | Item | Ticket | Gate |
+| --- | --- | --- | --- |
+| 1 | **AccuracyCoin re-sync** (D25): upstream HEAD into `tests/roms/accuracycoin/` and its catalogue; each new or changed test triaged | `T-ACCURACYCOIN-RESYNC-2610` | All-pass on the RAM decoder against the new catalogue, with its count and the upstream commit recorded in `docs/STATUS.md`. Any test that fails gets a red-first fix or a recorded cause. An emulation change raises the epoch |
+| 2 | **Epoch fingerprint gate** (CI-02) | `T-EPOCH-FINGERPRINT` | A fixed panel of output hashes (framebuffer, audio, bus trace on a few ROMs) compared against a committed table that names the epoch. A changed hash with an unchanged epoch fails. Shown to fail on a seeded behaviour mutation, and to pass once the epoch is raised with the table |
+| 3 | **FE-01, the CPU-multiplier overclock** (D22) | `T-CPU-OVERCLOCK` | Off by default; a field in `HardwareOptions` (already `#[non_exhaustive]`, so not an API break); stock timing under every accuracy path that does not opt in. Red-first: a test that the multiplier reaches the core, and that a movie or netplay peer with a different value is refused with the option named |
+| 4 | **FE-02, disable the sprite limit** (D22) | `T-SPRITE-LIMIT` | Render-only. Red first: a ROM with more than 8 sprites on a line shows them with the option on. The overflow flag and evaluation timing are unchanged, pinned by the existing sprite-overflow suites passing with the option on. Carried like item 3 |
+| 5 | **One format bump for items 3 and 4**: `.rnm` 5 → 6, netplay protocol 6 → 7, `config_digest` covering both | — | An older movie and an older peer are refused with a reason; the foreign movie imports still import at the current options |
+| 6 | **ACC-01, the PAL/Dendy emphasis red/green swap** | `T-PAL-EMPHASIS` | From the NESdev NTSC/PAL pages. Red first: an emphasis frame on a 2C07 with the swapped tint; NTSC unchanged; the palette goldens re-blessed in the same commit; the epoch raised |
+| 7 | **ACC-02, composite artifacts as an opt-in video option** (D20) | `T-COMPOSITE-ARTIFACTS` | Differential phase distortion and inter-pixel artifacts, from the documented composite model. A post-process that changes no emulation output, so no epoch rise. The default picture is byte-identical (the visual regression suite) |
+| 8 | **ACC-13, the NEC rev B MMC3 as a per-game or config override** (D20) | `T-MMC3-NEC-OVERRIDE` | `mmc3_test_2/6` passes under the override; the default (rev A) results unchanged; the option recorded in `HardwareOptions` |
+| 9 | **FE-09, rewind and run-ahead in Vs. DualSystem mode** (D27) | `T-PS-dual-runahead` | Both consoles snapshot and restore as one (the existing "RVSD" container). Red first: a rewind across a cabinet frame restores both framebuffers byte-identically. Run-ahead gives the same output as without it. ADR 0032 amended |
+| 10 | **Records** (DOC-01..DOC-09) | — | Each stale statement corrected against the code with a citation; `to-dos/v1.8.x-on-device-verification.md` folded into `docs/mobile-v2.9.3-run-sheet.md` and deleted (D25); markdownlint and the release audits green |
+| 11 | **The libretro toolchain** (D5) | `T-LIBRETRO-TOOLCHAIN` | v3.0.1 dropped the pin (pipeline 119614, 15/15 on 1.99.0): confirm the first `main` pipeline after the merge is green, then close the ticket |
+| 12 | **Sibling RTL-1**: `$2006`'s `v <- t` copy on a `v_pipeline` load dot | — | A stimulus that lands the copy on dot 257 or an increment dot, compared per cycle; RTL changes only if it diverges |
+| 13 | **Sibling RTL-5**: a PPU open-bus decay stimulus that reaches the three inert mutations | — | Each mutation CAUGHT, or classified as stimulus-blind with its reach counted |
+| 14 | **Sibling RTL-10**: register the 15 unregistered AccuracyCoin sub-test ROMs | — | Each resolves to a suite and test address and runs in the ladder |
+| 15 | **Sibling TL-1 and the self-hosted runner** (D16) | — | CI's Verilator version recorded; the runner provisioned with the existing scripts, and one PR's ladder run on it end to end |
+| 16 | **Sibling Phase S documents**: SUB-1, SUB-2, SUB-5 | — | A dated `ref-docs/` record of the live MiSTer contribution page read by hand; the checklist re-scoped with `contribution_checklist_audit.rs` passing; `submission-case.md` refreshed |
+
+**Not in this release:**
+
+- feature RTL (it starts in v3.2.0; until D29 it waited for the hardware
+ release);
+- ACC-03 and ACC-04 (v3.3.0, one state break together);
+- mapper breadth (v3.2.0);
+- hosting (v3.4.0).
+
+## Sprints
+
+| Sprint | Deliverable | Gate | Status |
+| --- | --- | --- | --- |
+| 3.1-S1 | Items 1 and 2: the re-sync, and the fingerprint gate first, so every later behaviour change is checked by it | items 1 and 2 | — |
+| 3.1-S2 | Items 3, 4 and 5: the two options and the single format bump | items 3-5 | — |
+| 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) | — |
+
+## Risks
+
+- **The re-sync can fail a test the oracle cannot pass from documentation.**
+ The ladder applies, and TriCNES is consultable under D7 with attribution.
+ Rung 4 is never used silently.
+- **FE-02 is easy to get wrong.** Rendering extra sprites must not touch
+ evaluation: the overflow flag is observable to games, and several timing ROMs
+ pin it. That is why the gate runs the overflow suites with the option on.
+- **One format bump for two options** assumes both land in this release. If
+ one slips, the bump still happens once, with the remaining option.
+- **v3.0.1 not yet merged.** This plan was written before it merged; re-check
+ the "Where it starts" table.
+
+## Outcome
+
+| # | Outcome | Evidence |
+| --- | --- | --- |
+| — | not started | — |
diff --git a/to-dos/plans/v3.2.0-plan.md b/to-dos/plans/v3.2.0-plan.md
new file mode 100644
index 000000000..e02f8ba5c
--- /dev/null
+++ b/to-dos/plans/v3.2.0-plan.md
@@ -0,0 +1,46 @@
+
+# v3.2.0: mappers in both cores (MiSTer Phase F1)
+
+A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on
+2026-10-07. The sibling half starts here: the hardware release moved to the
+end of v3.9.x (D29), so Phase F1 no longer waits for it, and its RTL is verified
+in simulation until then. (The first draft said the sibling half could not start
+until the hardware release shipped.) The codename is asked at the cut.
+
+## Scope and gates
+
+**Oracle: mapper breadth by real titles** (D26, `T-MAPPER-BREADTH-V3`).
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 1 | The missing families with real titles, each first searched for in `~/Dropbox/ROMs/` (D21): 8 (FFE), 158 (Tengen 800037), 207 (Taito X1-005 variant), 116, 114/182/215, 197, 165, 172/173, 230, 235, 252. 157 (Datach) is open: it needs a barcode reader | Per family: written from its NESdev page; a register-decode unit test; a boot snapshot from a real dump, or a synthetic CC0 fixture where none exists; a tier set honestly (ADR 0011); `docs/mappers.md` updated. The two staged *Doraemon* hacks that are refused as "mapper 8" today boot |
+| 2 | ACC-08 / ACC-09: KNOWN_BLANK triage (`T-KNOWN-BLANK`), starting with *Chu Liu Xiang* (176.2; the mirroring power-on hypothesis, a CPU trace first), the Sansuu 1-3 Nen titles (m185) and *Gradius II (VC)* | Each dump leaves the list by a red-first fix with an epoch rise, or stays with a recorded cause. The ratchet holds in both directions |
+| 3 | MAP-01 tier promotions under D23 (`T-CURATED-EVIDENCE`): 47, 121, 191, 290, 154, 243 | Each has a byte-identity boot snapshot in `external_extended.rs`, and a record naming the title, hash, frames and screenshot; the honesty gate's counts are updated |
+| 4 | The dump-corpus sweep (D21, `T-DUMP-CORPUS`): every implemented family with a Dropbox dump and no recorded boot | A boot snapshot per family, or a recorded refusal with a reason. Nothing committed but snapshots, screenshots and hashes |
+
+**Sibling: Phase F1** (D12, first part). One seed sweep for the release.
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 5 | FB-16 core options at the start of the release, before the sweep: custom palette, +8 sprites per line (mirrors FE-02), the region placeholder off | Each option checked by co-simulation where it is observable, and labelled "compiled, timing-closed, not seen on hardware" until seen |
+| 6 | FB-2 MMC1 SUROM/SXROM (off-die only, 512 KiB PRG): *Dragon Warrior III/IV*, *Final Fantasy I+II* | Bus and checkpoint gates, plus a commercial-frame gate where a dump exists; a caught mutant |
+| 7 | The next ~10 by coverage per effort: the 206 family (Namco 108, a subset of MMC3 decode), 66, 11, 79, 9/10, 118/119, 71/232, 34, and the trivial discretes (13, 87, 180, 185, 94, 140) | Same as row 6, per family |
+| 8 | FB-10 paddle, FB-8 Four Score, FB-6 cheats (an oracle-gated rung: apply a code on both sides and compare the bus) | Each with an exporter stimulus flag and a gate |
+
+## Sprints
+
+| Sprint | Deliverable | Gate | Status |
+| --- | --- | --- | --- |
+| 3.2-S1 | Items 1 and 4 (oracle families and the corpus) | rows 1, 4 | — |
+| 3.2-S2 | Items 2 and 3 | rows 2, 3 | — |
+| 3.2-S3 | Item 5, the sibling options, first | row 5 | — |
+| 3.2-S4 | Items 6 and 7, the sibling mappers | rows 6, 7 | — |
+| 3.2-S5 | Item 8, then the seed sweep and the release cut | row 8; the sweep; both ladders | — |
+
+## Risks
+
+- Several families above are Japan-only or pirate boards, and demand for them
+ is unverified. The Dropbox search comes first, and a family with no dump and
+ no notable title can move to v3.8.0's long tail.
+- On-die M10K is at 85%. Families that need memory are off-die only from the
+ start.
diff --git a/to-dos/plans/v3.3.0-plan.md b/to-dos/plans/v3.3.0-plan.md
new file mode 100644
index 000000000..7b083856b
--- /dev/null
+++ b/to-dos/plans/v3.3.0-plan.md
@@ -0,0 +1,44 @@
+
+# v3.3.0: the memory platform, and the headline switch (MiSTer Phase F2)
+
+A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on
+2026-10-07. The codename is asked at the cut.
+
+## Scope and gates
+
+**Sibling: Phase F2** (D12, second part; D4; D16).
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 1 | FB-20, the slot-scheduled SDRAM arbiter, with refresh in slots known to be idle. RTL-9 (the CHR residual) closes with it | No request over its budget across the full ladder, on a measurement that can fail; the CHR residual gone |
+| 2 | DDR3 bring-up for the framework's save-state region | A round trip through the region in simulation; verified on the SuperStation One |
+| 3 | FB-4 save states (`T-MISTER-SAVESTATE`): the framework's four slots; every register and memory streamed, PRG/CHR-RAM in SDRAM included | Oracle-gated: serialise mid-game, restore, and compare the bus per cycle against an unbroken run |
+| 4 | FB-5 rewind, a ring of states in DDR3 | A rewind of N seconds restores a state byte-identical to the one saved |
+| 5 | The real `hps_io` under Verilator for HPS-facing features (D16, TL-8) | Save, save state and cheat loading exercised against `hps_io` itself, with lint suppressed where needed |
+| 6 | **The headline switch** (D4): off-die becomes the headline; on-die continues as a reduced "lite" build; `releases/` carries both | `docs/bitstream-release.md`, the README and both repositories' release notes updated; both builds swept |
+
+**Oracle.**
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 7 | ACC-03, PPU register write placement (phi2 against M2-low, half a dot), reopened under D20 against the v2.6.17 reopen conditions (`T-PHI2-WRITE`) | Kept only if AccuracyCoin stays all-pass and nestest 0-diff. The default-off `phi2-write-sweep` knob is either the new default or retired, with the measurement recorded |
+| 8 | ACC-04, the sprite-0 stale shifter and internal/external bus split (`T-SPRITE0-STALE`), attempted under D20 without a motivating game; the `stale_sprite_shift_regs_probe.rs` diagnostic is the start | Same bar as row 7. If it fails it, the attempt is recorded as refuted and closed |
+| 9 | One state break and one epoch rise for rows 7 and 8, if either lands | The release notes name the break (D2) |
+| 10 | The graphics stack: wgpu 31 with egui 0.37, together; the vendored `egui-winit` re-diffed, or removed if crates.io has caught up | Every clippy combination and both wasm builds; the frame-pacing measurement re-run |
+
+## Sprints
+
+| Sprint | Deliverable | Gate | Status |
+| --- | --- | --- | --- |
+| 3.3-S1 | Rows 1 and 5 (the arbiter and the test harness come first) | rows 1, 5 | — |
+| 3.3-S2 | Rows 2-4 | rows 2-4 | — |
+| 3.3-S3 | Rows 7-9 (oracle, in parallel) | rows 7-9 | — |
+| 3.3-S4 | Row 10, row 6, then the sweep and the cut | rows 6, 10; both ladders | — |
+
+## Risks
+
+- Save states are the largest single item in the MiSTer backlog. If FB-4 does
+ not fit, rewind (row 4) moves to the next release rather than the release
+ growing.
+- Rows 7 and 8 were deliberately not done before. Each has a clear refute
+ path, and a refutation is a result.
diff --git a/to-dos/plans/v3.4.0-plan.md b/to-dos/plans/v3.4.0-plan.md
new file mode 100644
index 000000000..492170c22
--- /dev/null
+++ b/to-dos/plans/v3.4.0-plan.md
@@ -0,0 +1,37 @@
+
+# v3.4.0: hosted netplay and achievements, and the first big boards (MiSTer Phase F3a)
+
+A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on
+2026-10-07. The codename is asked at the cut.
+
+## Scope and gates
+
+**Oracle: hosted services** (D19, D25).
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 1 | NET-04 hosted signalling (`T-HOSTED-NETPLAY`): a Cloudflare Worker with Durable Objects for rooms, and Cloudflare TURN with short-lived credentials the Worker mints. The room protocol is ported from the existing `signaling_server` | A live two-browser session through the hosted lobby; a relayed session across two NATs; credentials expire. NES rollback traffic is about 22 MB per player-hour (an inference), well inside the free 1,000 GB a month |
+| 2 | RA-01, the browser RetroAchievements proxy (`T-RA-PROXY`), on the same account; `RA_PROXY_BASE` set | A live browser login and unlock with a real account (RA-02) |
+| 3 | A privacy policy, which both RA's compliance page and F-Droid's `NonFreeNet` anti-feature need | Published, and linked from the README and the apps |
+| 4 | RA hardcore compliance (`T-RA-HARDCORE`): Lua and TAS playback blocked in hardcore (as well as cheats, rewind, slowdown, frame advance, state loading, memory editors and the debugger); offline unlocks queued; the User-Agent format checked against RA's `Name/v1.0.0 (OS) core/v0.5.0`; `docs/ra-integration-request.md` refreshed (it still says v1.8.8 and MIT/Apache) | An audit of `session_policy.rs` against the full block list, each block pinned by a test; then the application sent (the process is unverified: the forum post returned 403) |
+| 5 | NET-01, native 3-4 player netplay over the existing `mesh_net.rs` (`T-NETPLAY-MESH-NATIVE`); MOB-09 for mobile follows | A 4-peer loopback session stays in sync over N frames; the desktop UI reaches it |
+
+The domain name and who operates the service are still open (the line plan's
+open-decisions table).
+
+**Sibling: Phase F3a.**
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 6 | MMC2/MMC4 (9/10): the CHR latch on a PPU fetch; *Punch-Out!!* | Bus and checkpoint gates, a commercial-frame gate, a caught mutant |
+| 7 | Sunsoft FME-7 with 5B audio (69). The oracle file is Provenance-headered, so the RTL comes from nesdev with the oracle as a black-box comparator (D15) | Same, plus an audio gate |
+| 8 | Konami VRC2/VRC4 (21/22/23/25) | Same as row 6 |
+| 9 | FB-9 Zapper (`T-MISTER-ZAPPER`) | An exporter stimulus with a gate; the feel is checked on the board at the hardware release (end of v3.9.x, D29) |
+
+## Sprints
+
+| Sprint | Deliverable | Gate | Status |
+| --- | --- | --- | --- |
+| 3.4-S1 | Rows 1-3 | rows 1-3 | — |
+| 3.4-S2 | Rows 4-5 | rows 4-5 | — |
+| 3.4-S3 | Rows 6-9 (sibling, in parallel) | rows 6-9; one seed sweep; both ladders | — |
diff --git a/to-dos/plans/v3.5.0-plan.md b/to-dos/plans/v3.5.0-plan.md
new file mode 100644
index 000000000..fce1d6947
--- /dev/null
+++ b/to-dos/plans/v3.5.0-plan.md
@@ -0,0 +1,38 @@
+
+# v3.5.0: creator tools, and the expansion-audio boards (MiSTer Phase F3b)
+
+A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on
+2026-10-07. The codename is asked at the cut.
+
+## Scope and gates
+
+**Oracle: creator tools and desktop polish.**
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 1 | CR-03, the full movie input stream (`T-MOVIE-INPUT-STREAM`): the Zapper, the microphone, Vs. coins and service, FDS disk events, expansion devices; an `.rnm` format bump, and the netplay input stream if it is affected | A Zapper, a Vs. and an FDS movie each record and replay byte-identically. Older movies are refused with a reason (D2) |
+| 2 | CR-01 Lua WebSocket client (`comm.ws*`) and CR-02 real OS shared memory | Each with a host test; no change to the Lua budget model |
+| 3 | Desktop polish: FE-03 (A/V sync control), FE-04 (named FDS and multicart slots), FE-05 (browser drag-and-drop), FE-06 (`HostWarning` codes), FE-07 (rebindable Power Pad, microphone, keyboard), FE-08 (emu-thread priority on Windows and macOS), FE-10 (DualSystem in the `wasm-canvas` embed) | Each with a test where one can reach it, and the rest recorded as manual checks |
+
+**Sibling: Phase F3b.** MMC5 needs v3.3.0's arbiter.
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 4 | MMC5 (5): ExRAM, split screen, 1 MiB, expansion audio | Bus and checkpoint gates, the worst SDRAM pattern inside budget, *Castlevania III* frame gates |
+| 5 | Namco 163 (19), Konami VRC6 (24/26), VRC7 with OPLL (85), Bandai FCG (16/153/159). N163, VRC7 and FCG are Provenance-headered (D15), so they use rungs 1-3 | Per board: bus, checkpoint and audio gates, a caught mutant |
+| 6 | FB-12 expansion audio with each board; FB-11 Famicom peripherals (keyboard, P2 microphone, Power Pad, Miracle Piano) | Audio gates against the oracle's output; input gates by stimulus |
+
+## Sprints
+
+| Sprint | Deliverable | Gate | Status |
+| --- | --- | --- | --- |
+| 3.5-S1 | Row 1 | row 1 | — |
+| 3.5-S2 | Rows 2-3 | rows 2-3 | — |
+| 3.5-S3 | Rows 4-6 (sibling) | rows 4-6; one seed sweep; both ladders | — |
+
+## Risks
+
+- VRC7's OPLL is the largest audio block and is Provenance-headered in the
+ oracle (`opll.rs`). If documentation does not suffice, the escalation is an
+ ADR 0037 amendment naming the maintainer (D15), never a silent read. The
+ board can move to v3.8.0 rather than the release waiting on it.
diff --git a/to-dos/plans/v3.6.0-plan.md b/to-dos/plans/v3.6.0-plan.md
new file mode 100644
index 000000000..6f9baf71f
--- /dev/null
+++ b/to-dos/plans/v3.6.0-plan.md
@@ -0,0 +1,31 @@
+
+# v3.6.0: libretro, Android API 37, and PAL on the FPGA (MiSTer Phase F4a)
+
+A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on
+2026-10-07. The codename is asked at the cut.
+
+## Scope and gates
+
+**Oracle.**
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 1 | Libretro Core Options v2 (`RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2`, with v0 as a fallback): categories, sublabels, value labels (`T-LIBRETRO-OPTIONS-V2`) | The C-ABI harness (`abi_tests.rs`) covers both paths; every existing option keeps its key |
+| 2 | Float-audio negotiation (`GET_AUDIO_SAMPLE_BATCH_FLOAT`, added to `libretro.h` 2026-06-29) behind a capability check; i16 stays the fallback | Both paths tested; nothing changes when the frontend does not offer it (RetroArch's last tag is still 1.22.2, from 2025-11) |
+| 3 | LR-03, a Vs. DIP-switch core option | The option reaches the cabinet; a test pins it |
+| 4 | D28, console targets for a Rust libretro core (Switch, PS Vita, 3DS) (`T-LIBRETRO-CONSOLES`) | A recorded go or no-go per target, with what blocked it. `libnx` was dropped at v2.9.8 for a reason that must be re-checked, not assumed. Any target that goes forward gets a buildbot job, green |
+| 5 | Android API 37 (`T-ANDROID-API37`): the `ACCESS_LOCAL_NETWORK` runtime permission for LAN and direct-IP netplay; background audio only inside a valid lifecycle (on Android 17 it fails silently otherwise); `targetSdk` 37 | The permission flow covered by a JVM test; a LAN session on the emulator; Play's deadline is about 2027-08-31 (an inference from the yearly pattern) |
+
+**Sibling: Phase F4a.**
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 6 | FB-7 PAL and Dendy with `T-MISTER-VMODE`: 312 lines, 3.2 dots per CPU cycle, the APU PAL tables, video timing, `new_vmode`. The oracle's `frame_counter.rs` (the PAL step table) is Provenance-headered, so the RTL is written from nesdev (D15) | PAL goldens exported from the oracle; a PAL bus and framebuffer gate; a caught mutant per table |
+
+## Sprints
+
+| Sprint | Deliverable | Gate | Status |
+| --- | --- | --- | --- |
+| 3.6-S1 | Rows 1-3 | rows 1-3 | — |
+| 3.6-S2 | Row 4 (investigation) and row 5 | rows 4, 5 | — |
+| 3.6-S3 | Row 6 (sibling) | row 6; one seed sweep; both ladders | — |
diff --git a/to-dos/plans/v3.7.0-plan.md b/to-dos/plans/v3.7.0-plan.md
new file mode 100644
index 000000000..7508969dc
--- /dev/null
+++ b/to-dos/plans/v3.7.0-plan.md
@@ -0,0 +1,33 @@
+
+# v3.7.0: the mobile extras, and FDS on the FPGA (MiSTer Phase F4b)
+
+A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on
+2026-10-07. The codename is asked at the cut.
+
+## Scope and gates
+
+**Oracle: the mobile extras** (D24, `T-MOBILE-EXTRAS`). Every Swift change is
+uncompiled until a device or CI build compiles it. Each one gets a run-sheet
+row.
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 1 | iOS box art (Android has `BoxArt.kt` and `ScraperSources.kt`) and a WidgetKit home-screen widget (Android has `ResumeWidget.kt`) | The iOS CI build compiles; run-sheet rows added |
+| 2 | iOS external-display output, with the phone as the controller | Same |
+| 3 | The 20-band EQ and a cheat database on both apps, through the bridge | Bridge tests on the host; run-sheet rows |
+| 4 | MOB-08, the live Kotlin TODOs (capture audio mux, live save-state thumbnails, the library long-press menu, the list-detail rail, a 3-way cloud-save merge, folder nesting, Tink key rotation, `TODO(i18n)` literals, a box-art fallback) | Each fixed with a JVM test where one reaches it, or recorded |
+| 5 | MOB-06, the zero-copy framebuffer (`T-MOB-06-ZEROCOPY`), **only if UniFFI 0.33 has released** (zero-copy `&mut [u8]` is merged upstream as PR #2940, unreleased at drafting). `run_frame` becomes `run_frame_into(&self, out: &mut [u8])` | About 15 MB/s of per-frame copies gone, measured; the bridge change recorded as internal (`rustynes-mobile` is not public API under `VERSION-PLAN.md`) |
+
+**Sibling: Phase F4b.**
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 6 | FB-13 FDS: the disk image in SDRAM or DDR3, the 8 KiB BIOS on die, write persistence through the HPS, a disk-swap OSD, FDS audio. `fds.rs` is Provenance-headered (D15) | The oracle's FDS goldens gated; a disk write round-tripped through the HPS under the real `hps_io` (D16) |
+
+## Sprints
+
+| Sprint | Deliverable | Gate | Status |
+| --- | --- | --- | --- |
+| 3.7-S1 | Rows 1-3 | rows 1-3 | — |
+| 3.7-S2 | Rows 4-5 | rows 4-5 | — |
+| 3.7-S3 | Row 6 (sibling) | row 6; one seed sweep; both ladders | — |
diff --git a/to-dos/plans/v3.8.0-plan.md b/to-dos/plans/v3.8.0-plan.md
new file mode 100644
index 000000000..935f20c0e
--- /dev/null
+++ b/to-dos/plans/v3.8.0-plan.md
@@ -0,0 +1,34 @@
+
+# v3.8.0: the residuals, and the rest of the console (MiSTer Phase F4c)
+
+A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on
+2026-10-07. The codename is asked at the cut.
+
+## Scope and gates
+
+**Oracle.** Each item lands only where a source exists to pin it; otherwise it
+stays open with the reason recorded.
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 1 | ACC-05, the FDS `$4030.D1` DRAM-refresh watchdog IRQ | Only from published hardware research; red first |
+| 2 | ACC-06, a measured-RC model of the APU's analog filter chain, opt-in | The default output byte-identical |
+| 3 | ACC-10, the 2A03H "unexpected DMA" direction | Only from a measurement; opt-in revision only |
+| 4 | ACC-07, the Vs. DualSystem real cabinets: why the combined *Wrecking Crew* dump never reaches attract mode (no new dumps needed), then Tennis and Mahjong if combined dumps appear | The four `#[ignore]`d boots in `vs_dualsystem.rs`, each unignored or recorded with its cause |
+| 5 | MAP-05, the long-tail mappers: whatever v3.2.0 deferred, plus families with a Dropbox dump (D21, D26) | Per family as in v3.2.0 |
+
+**Sibling: Phase F4c.**
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 6 | FB-14 NSF player | The oracle's NSF output gated |
+| 7 | FB-15 Vs. System: palettes and a DIP OSD | Vs. goldens gated |
+| 8 | FB-17 band-limited audio. `blip.rs` is Provenance-headered (D15), so the RTL is written from documentation. HW-C4's FFT now runs after this release (D29), so it re-checks the result at the hardware release rather than informing it | An audio gate against the oracle's band-limited output |
+
+## Sprints
+
+| Sprint | Deliverable | Gate | Status |
+| --- | --- | --- | --- |
+| 3.8-S1 | Rows 1-3 | rows 1-3 | — |
+| 3.8-S2 | Rows 4-5 | rows 4-5 | — |
+| 3.8-S3 | Rows 6-8 (sibling) | rows 6-8; one seed sweep; both ladders | — |
diff --git a/to-dos/plans/v3.9.0-plan.md b/to-dos/plans/v3.9.0-plan.md
new file mode 100644
index 000000000..332229864
--- /dev/null
+++ b/to-dos/plans/v3.9.0-plan.md
@@ -0,0 +1,59 @@
+
+# v3.9.x: the freeze, distribution, the v4.0.0 preparation, and the hardware release
+
+A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on
+2026-10-07. Like v2.9.x before v3.0.0, this line takes several releases. The
+codenames are asked at each cut.
+
+**Its shape changed the day it was drafted (D29, maintainer, 2026-10-07).** The
+hardware release, the board session and the mobile device run, moved here from
+right after v3.1.0, so the board verifies the most complete core. The line is
+now, in order:
+
+1. **v3.9.0**: the sibling's RTL feature freeze, the v4.0.0 preparation, the
+ release-candidate pair, and the signing set-up (D18);
+2. **the hardware release**, the last v3.9.x: the board session on that pair,
+ the mobile device run, then the store listings. Its number is chosen after
+ the session (D1); if its fixes are large it folds into v4.0.0.
+
+Its risks are in the line plan's
+[Risks of D29](v3.1-to-v4.0-line-plan.md#risks-of-d29).
+
+## v3.9.0: scope and gates
+
+**The freeze and v4.0.0 preparation.**
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 1 | **The sibling's RTL feature freeze.** No feature RTL after this release; the hardware release takes board fixes only | The freeze recorded in the sibling's `docs/bitstream-release.md` and `TASKS.md`; the feature list matches the parity re-measure (row 4) |
+| 2 | Trial `T-API-ENUMS` on a branch: every remaining public enum `#[non_exhaustive]`, counted at the time | The workspace, every feature combination and both wasm builds compile; the downstream match sites listed for v4.0.0's notes |
+| 3 | Re-run the four audit scopes against the tree, as v2.9.0 and v2.9.9 did | Each new finding fixed red first or dispositioned in its ledger |
+| 4 | MiSTer: the parity re-measure against the incumbent's public feature list, then the release-candidate bitstream pair at the RC date. **The board session runs on this pair** | The feature delta written honestly; two clean compiles of each build byte-identical; the pair archived outside `/tmp` |
+
+**Mobile distribution, the set-up half** (D18, `T-MOBILE-DISTRIBUTION`; free
+and without monetisation, ADR 0035).
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 5 | **Android identity.** Developer verification applies to sideloads too: enforced in four countries from 2026-09-30, and global from 2027. The limited-distribution account covers 20 devices; wider distribution needs registration with a government ID. Which path is the maintainer's decision, taken at this release | The chosen path recorded in `docs/android.md`; a signed APK installs on a certified device without the "advanced flow" |
+| 6 | **iOS signing (MOB-02).** No signing secrets were visible at drafting, so TestFlight uploads may never have run. Provision them; rebuild with the then-required Xcode (27 from about April 2027, an inference) | A TestFlight build uploaded by CI; the 90-day expiry noted in the release process |
+
+## The hardware release (the last v3.9.x): scope and gates
+
+Detail: [`v3.x-hardware-verification-plan.md`](v3.x-hardware-verification-plan.md).
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 7 | **The board session** on v3.9.0's pair: HW-0, Strands A-F, HW-O6, every fix a gate first where simulation reaches it, the re-sweep at the release date, the anchors flipped | Every `bringup-log.md` row PASS against the md5 read off the console; the ladder green on the tagged commit |
+| 8 | **The mobile device run** (MOB-01): the run sheet, extended by every mobile feature landed since v2.9.3, on the release builds | The run sheet's rows PASS on the builds that ship |
+| 9 | **The listings**, after row 8 and on the builds it passed: Google Play (a new personal account needs 12 testers for 14 days first), F-Droid or IzzyOnDroid (the `foss` flavour, reproducible from `Cargo.lock`, IzzyOnDroid's 30 MB APK limit checked), and the App Store under guideline 4.7 | Each listing live, or the blocker recorded |
+| 10 | D10: the feature delta and the re-measured incumbent number in `submission-case.md`, for the maintainer's public-repository and submission decision | The document updated; the decision asked |
+
+## Sprints
+
+| Sprint | Release | Deliverable | Gate | Status |
+| --- | --- | --- | --- | --- |
+| 3.9-S1 | v3.9.0 | Rows 1-4 | rows 1-4 | — |
+| 3.9-S2 | v3.9.0 | Rows 5-6 | rows 5-6 | — |
+| 3.9-S3 | HW | Rows 7-8 | rows 7-8 | — |
+| 3.9-S4 | HW | Rows 9-10 | rows 9-10 | — |
diff --git a/to-dos/plans/v3.x-hardware-verification-plan.md b/to-dos/plans/v3.x-hardware-verification-plan.md
index c91ef53e9..4a21752d1 100644
--- a/to-dos/plans/v3.x-hardware-verification-plan.md
+++ b/to-dos/plans/v3.x-hardware-verification-plan.md
@@ -9,6 +9,32 @@ a MAJOR of its own is decided when it is planned
([ADR 0043](../../docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md),
Decision 2).
+**Decided 2026-10-07 (maintainer; the line plan's D1, D11, D13, D16).**
+
+- **The number (D1).** The number is chosen *after* the board session, with
+ its evidence in hand, and it is no later than v4.0.0. In
+ [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md) this release is
+ the slot "HW".
+- **Its position (D29, later the same day).** It is the **last release of
+ v3.9.x**, immediately before v4.0.0, after v3.9.0's RTL feature freeze, so
+ the board verifies the most complete core. The first reading of D1 put it
+ after v3.1.0 and before any feature RTL; the maintainer reversed that. It
+ still carries no feature RTL of its own: a Strand C correction is an RTL
+ change that reopens the seed sweep at that day's build date, so it must not
+ share a release with feature RTL. If its fixes are large, it folds into
+ v4.0.0 (D1).
+- **One board (D11).** Verification is on the SuperStation One only. The
+ DE10-Nano is an optional later row (HW-X3), done only if a board turns up.
+ The two-board box in the contribution checklist is split rather than
+ ticked on one board.
+- **Board against oracle (D13).** When they disagree, the board wins only
+ where it agrees with documented hardware behaviour. The RTL and the oracle
+ are fixed in the same release, red first, with an `EMULATION_EPOCH` rise
+ and an ADR 0037 amendment per case.
+- **CI (D16).** The ladder runs on a self-hosted runner from v3.1.0. This
+ release's fixes are gated there, as well as by the frozen-worktree run.
+ There are no hosted-CI goldens and no Quartus container.
+
> **History of this file.** It was written as the v3.0.0 plan, when ADR 0041
> made v3.0.0 the hardware-verified core. ADR 0043 (2026-09-29) made v3.0.0 the
> API major with a release-candidate core instead, and moved the board session,
@@ -21,11 +47,29 @@ Decision 2).
## The deliverable
-| Artefact | Status at v3.0.0 |
+Written for v3.0.0 and restated for D29's position. By the time this release
+runs, the off-die build has been the headline since v3.3.0 (D4), and both builds
+carry the v3.2.0-v3.8.0 features.
+
+| Artefact | Status at the hardware release |
| --- | --- |
-| **On-die `.rbf`** (`USE_SDRAM_CART = 1'b0`) | **The headline.** Every bring-up strand A–F passed on the SuperStation One |
-| **Off-die `.rbf`** (the SS1's 128 MB SDRAM) | **Secondary, labelled experimental.** MemTest and its own Strand B rows passed |
-| Oracle and sibling | Tagged v3.0.0 together |
+| **Off-die `.rbf`** (the SS1's 128 MB SDRAM) | **The headline since v3.3.0 (D4).** MemTest, its own Strand B rows and HW-O6's real SDRAM constraints passed |
+| **On-die `.rbf`** (`USE_SDRAM_CART = 1'b0`) | **The "lite" build.** Every bring-up strand A–F that applies to it passed on the SuperStation One |
+| Oracle and sibling | Tagged together, at the hardware release's number |
+
+## Risks of D29
+
+The board sees the core only at the end. The line plan's
+[Risks of D29](v3.1-to-v4.0-line-plan.md#risks-of-d29) carries the full list;
+the two this plan owns are:
+
+- **HW-A8 and HW-A9 come last.** A wrong FPGA device or SDRAM part re-targets
+ the parity core, not the six-mapper core, and the sweep and every strand
+ repeat. An optional read-only look at the chip markings before then, with no
+ bitstream loaded, is the cheap insurance; it is listed as an open decision.
+- **More to verify.** Strand B grows with every feature: save states, PAL and
+ Dendy, FDS, the expansion audio and the new mapper families each need rows in
+ `bringup-log.md`, written as each lands rather than at the session.
## The gate
@@ -37,7 +81,42 @@ Decision 2).
- Strand F's incumbent re-measure and feature delta are written up honestly.
- `to-dos/mister/contribution-checklist.md` is complete. Every box that can be
evidence about this core is ticked or carries a reason; the two-board box stays
- open unless a second board has actually run it.
+ open unless a second board has actually run it. Under D11 it is split into a
+ SuperStation One row (ticked by this release) and an open DE10-Nano row.
+- HW-A8, the FPGA marking, is photographed and recorded before anything else
+ is concluded.
+ - The build targets 5CSEBA6U23I7, and one published spec says 5CSXFC6D6F31.
+ - If the SS1 really is the second part, the result is a second Quartus target
+ with its own sweep: an L-sized re-target. The release number (D1) is then
+ chosen with that cost known.
+- HW-O6, the provisional SDRAM constraints, are replaced by the real part's
+ numbers (HW-A9), and the off-die build is re-swept.
+
+## The order
+
+1. **HW-0:** stage the kit (`tools/stage_board_kit.sh`) for this release's own
+ bitstreams.
+2. **Strand A:** the stock core first, including HW-A8 (the device) and HW-A9
+ (the SDRAM part).
+3. **Strand B:** the on-die build, stopping at the first failure.
+4. **Strand C:** the four properties no gate reaches. Budget a second compile
+ cycle, because a palette or timing correction is RTL.
+5. **Strand D:** the battery, AccuracyCoin as bytes, the incumbent A/B on the
+ same silicon, a soak of 4 hours or more, and the save across a power cycle.
+6. **The off-die strand:** MemTest, then B4-B9, AUD-28 (HW-O4), AUD-29 (HW-O5)
+ and HW-O6.
+7. **E1/E2:** direct video, and the menu mask.
+8. **F1:** re-measure the incumbent. Then the maintainer's D10 decision, which
+ covers the public repository and the submission.
+
+Every fix the board finds becomes a gate first, where simulation can reach it.
+Then re-sweep at the release's build date, re-run the failed strand and every
+later one, and flip the anchors (HW-X1).
+
+**The mobile device run** (MOB-01, `docs/mobile-v2.9.3-run-sheet.md`, extended
+by every mobile feature landed since) belongs to this release under ADR 0043
+Decision 2. It may become its own patch if it falls on a different day from the
+board session. The store listings (D18) follow it, on the builds it passed.
## What changes in the same PR
@@ -59,8 +138,16 @@ Decision 2).
maintainer 2026-10-05). The sibling release carries both, plus the datecoded
copy, via `scripts/release-rbf.sh`.
-## Deferred past v3.0.0
+## Deferred past v3.0.0, and now landed before this release
FDS, expansion audio, save states, cheats, Vs. System, NSF, Zapper, Four Score,
paddle, keyboard and PAL on the FPGA; the remaining mapper families; the
-DE10-Nano second board.
+DE10-Nano second board. Under D29 everything in that list except the DE10-Nano
+lands in v3.2.0-v3.8.0, before this release, and is verified on the board here.
+
+Scheduled on 2026-10-07:
+
+- Phases F1-F4, v3.2.0 to v3.8.0, lead to the parity milestone at v4.0.0, in
+ [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md).
+- The DE10-Nano stays an optional row (D11).
+- The off-die build becomes the headline at v3.3.0 (D4).
diff --git a/to-dos/plans/v4.0.0-plan.md b/to-dos/plans/v4.0.0-plan.md
new file mode 100644
index 000000000..463204acf
--- /dev/null
+++ b/to-dos/plans/v4.0.0-plan.md
@@ -0,0 +1,52 @@
+
+# v4.0.0: the API major, and MiSTer parity
+
+The end of the line in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md),
+drafted on 2026-10-07 and defined by the maintainer (D3): **the remaining
+public enums `#[non_exhaustive]`, plus MiSTer feature parity.** The codename is
+asked at the cut.
+
+## Why it is a MAJOR
+
+Under D2, MAJOR is for a public Rust API break or a new deliverable class.
+Format breaks no longer need one.
+
+- **The API break.** Most public enums are still exhaustive. Counting `pub enum`
+ against an adjacent `#[non_exhaustive]` on 2026-10-06 gave these as
+ non-exhaustive: about 7 of 20 in `rustynes-core`, 6 of 27 in
+ `rustynes-mappers`, 2 of 7 in `rustynes-apu`, 1 of 6 in `rustynes-ppu`, 1 of
+ 2 in `rustynes-cpu`, and 1 of 14 in `rustynes-netplay`.
+- **Why now.** Adding a variant to any of the rest breaks downstream matches.
+ Making them all non-exhaustive at once means later variants (new options, new
+ input-event kinds, new error cases) are additive. This is the enum half of
+ v3.0.0's `T-API-EXTENSIBLE`, and the only item found that must be a MAJOR.
+- **v4.0.0 follows the hardware release** (D29, 2026-10-07). That release is
+ the last v3.9.x and carries the new-deliverable-class trigger; if its fixes
+ were large it folds into this one, which then carries both (D1). Either way
+ the parity core v4.0.0 ships is the one the board verified.
+
+## Scope and gates
+
+| # | Item | Gate |
+| --- | --- | --- |
+| 1 | `T-API-ENUMS`: every remaining public enum in `rustynes-core` and its re-exports `#[non_exhaustive]`, from v3.9.x's trial branch | Every crate, feature combination and wasm build compiles; rustdoc `-D warnings`; the no_std build; a CHANGELOG migration note listing the enums |
+| 2 | Release notes restating every break since v3.0.0: the format bumps of v3.1.0, v3.3.0 and v3.5.0, every epoch rise, the enums | Built from the CHANGELOG's entries and every `!` commit and `BREAKING CHANGE:` footer since v3.0.0, so none is missed |
+| 3 | MiSTer parity: save states, cheats, PAL/Dendy, FDS with expansion audio, the Zapper, Four Score, and mapper families covering the incumbent's licensed-library list | Each feature gated in the ladder; the parity table in `submission-case.md` with the re-measured incumbent number |
+| 4 | Bitstreams: both builds (off-die the headline since v3.3.0, D4). If the RTL is unchanged since the hardware release, ship the pair the board verified, byte for byte; a recompile at a new date is a different bitstream from the one tested. If the RTL changed, sweep at the release date and say the result is unverified on the board | Two clean compiles of each byte-identical (or the verified pair's md5s); md5s verified by download from both releases |
+| 5 | The hardware release, only if it folded into this one (D1, D29) | The hardware plan's gate |
+
+## Sprints
+
+| Sprint | Deliverable | Gate | Status |
+| --- | --- | --- | --- |
+| 4.0-S1 | Row 1, merged from the trial | row 1 | — |
+| 4.0-S2 | Row 3's remaining gaps | row 3 | — |
+| 4.0-S3 | Rows 2, 4 and 5, and the release ceremony | rows 2, 4, 5; full gates; both ladders | — |
+
+## Out of scope (after v4.0.0)
+
+- the SuperStation One distribution channel, then the openFPGA port (D17);
+- the MiSTer-devel submission's outcome (D10);
+- winit 0.31, if not taken earlier;
+- a Rust 2027 edition migration;
+- the DE10-Nano run (D11).
From 0c382576ab674c016b833cfbbc0ab5c8cc3ccb32 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Wed, 7 Oct 2026 06:46:06 -0400
Subject: [PATCH 2/5] review(v3.0.1): slice C -- sync de90569b (AGENTS.md
condensed + archive)
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
AGENTS.md | 16 ++++++---------
docs/history/AGENTS-archive.md | 37 ++++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+), 10 deletions(-)
create mode 100644 docs/history/AGENTS-archive.md
diff --git a/AGENTS.md b/AGENTS.md
index a49cf998f..e6fd5ca59 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -36,7 +36,7 @@
- **NO OVER-ATTRIBUTION.** Do not tag a genuine oracle *comparison* ("matches Mesen2's behavior," "cross-checked against ares") as "derived from." Attribute real ports; leave genuinely-independent code independent.
- **DO NOT SELF-CERTIFY.** Never assert "no third-party code is incorporated" / "license-clean" as a finished claim. Surface provenance status for human + expert review; state uncertainty. AI self-attestation of license compliance is not trustworthy — an outside NESdev reviewer, not the tooling, is what caught this.
-- **THE FIREWALL IS PER-REGION, NOT PER-REPO — the oracle's own source has exceptions (2026-08-26).** "RustyNES's code is ours, so it is readable" is true of the repository and **not uniformly true of every block in it.** **26 files** (at v2.7.1; the count is itself a snapshot — regenerate it, never quote this number) carry a `// Provenance:` header disclosing that a REGION of them is derived from a GPL reference emulator, and those regions are **black boxes for HDL purposes even though the file is ours** — writing SystemVerilog from them launders the original expression into the DUT *through* us, which is precisely what ADR 0037 exists to prevent. This was found on the first task of v2.6.4: its headline job is fixing the five `SH`-group stores in the DUT, and `crates/rustynes-cpu/src/cpu.rs` line 3 discloses `SHA/SHX/SHY/SHS/TAS` as **derived from Mesen2's `SyaSxaAxa`** (`Core/NES/NesCpu.h`) — so the single most relevant block of oracle source for that version is one that must not be read to write the RTL. Nothing in the tooling said so. **Before reading oracle source to inform sibling/HDL work, `grep -n "Provenance:" `**; regenerate the list with `grep -rln "^// Provenance:" crates` rather than trusting a snapshot. **The command used to read `crates/*/src/*.rs`, and that glob does not descend** — it silently missed `debugger/source_map.rs` and `bin/pgo_trainer.rs`, so it too reported a short list (24 against the real 26 at v2.7.1). `crates/rustynes-test-harness/tests/provenance_record_audit.rs` now walks the whole tree and fails if a header and its §1 row in `docs/originality-and-provenance.md` disagree in either direction. The ones that bite HDL work are `rustynes-cpu/src/cpu.rs` (SH group — rungs 1/5), **`rustynes-ppu/src/ppu.rs` (rungs 3/5 — the sprite-evaluation FSM, the OAM-data-bus model and the optional OAM-decay model from Mesen2 (the decay model was written at v2.1.4 and recorded only at v2.9.4), and the ALE/octal-latch address-multiplex and OAM-corruption behaviour from TriCNES)**, `rustynes-apu/src/blip.rs` (BLEP), **`rustynes-core/src/bus.rs` (DMA work — the OAM-DMA register-window read ported from TriCNES, and the DMA state modelled on its flags; header added 2026-09-28 at the maintainer's direction, after the v2.9.2 audit triage found the port disclosed only in a doc comment)**, and six mapper files (rung 7). **v2.9.9 added four (NC-17, maintainer 2026-10-04): `rustynes-apu/src/frame_counter.rs` (the lazy `$4015` clear and the PAL step table, from Mesen2) and `rustynes-apu/src/apu.rs` (the DMC-DMA state model, TriCNES / Mesen2) bite the sibling's APU parity work (the `$4017` rules); `rustynes-core/src/vs_dualsystem.rs` and `m019_namco163.rs` do not touch the RTL today.** **`ppu.rs` was missing from this sentence until 2026-09-19 and its absence was acted on**: the v2.6.22 CHR-during-rendering work read `ppu.rs` case 7 to form an RTL hypothesis, having checked the SENTENCE rather than running the command one line above it. The region read — the `$2007` PPUDATA write path — is outside all four disclosed regions, and the in-file note at `ppu.rs:777` re-states that scope, so nothing laundered; but the order was wrong, and it was an external reviewer that prompted the check rather than the process. **This sentence is a convenience, not the authority. The command is the authority, and the reason it exists is that a list of file names goes stale exactly the way the count above did — by ten files.** **The escalation ladder, maintainer-directed, in order — exhaust each rung before the next:** (1) vendored public documentation; (2) **the open Internet** — the vendored wiki is PARTIAL, documenting `SHX`/`SHY` in full and carrying nothing on `SHA`/`TAS`, which one web search supplied; (3) **black-box comparison** — a per-cycle golden diff needs no source at all and usually resolves faster, because at that point the question is "which cycle differs", not "what is the rule"; (4) the derived oracle source, **last resort**. Rung 4 is permitted — the licences are compatible, both repos being GPL-3.0-or-later — **but the existing attributions live in the ORACLE, and the sibling is a separate repository**, so reading it obliges declaring the derivation there too: a site comment, the sibling's provenance doc, `NOTICE`, and an ADR 0037 amendment naming who authorised it. Never silently. On v2.6.4 rungs 1-3 were sufficient and the escalation went unspent — worth knowing, because the pull toward rung 4 is strongest exactly when the DUT and the oracle disagree, which is the moment this rule matters.
+- **THE FIREWALL IS PER-REGION, NOT PER-REPO — the oracle's own source has exceptions.** "RustyNES's code is ours, so it is readable" is true of the repository and **not uniformly true of every block in it.** Files carrying a `// Provenance:` header disclose that a REGION of them is derived from a GPL reference emulator, and those regions are **black boxes for HDL purposes even though the file is ours** — writing SystemVerilog from them launders the original expression into the DUT *through* us, which is precisely what ADR 0037 exists to prevent. **Before reading oracle source to inform sibling/HDL work, `grep -n "Provenance:" `**; list the files with `grep -rln "^// Provenance:" crates` (a glob such as `crates/*/src/*.rs` does not descend into subdirectories and silently misses files). **The command is the authority, not any list of file names or count: those go stale.** `crates/rustynes-test-harness/tests/provenance_record_audit.rs` walks the whole tree and fails if a header and its §1 row in `docs/originality-and-provenance.md` disagree in either direction. **The escalation ladder, maintainer-directed, in order — exhaust each rung before the next:** (1) vendored public documentation; (2) **the open Internet** — the vendored wiki is PARTIAL, documenting `SHX`/`SHY` in full and carrying nothing on `SHA`/`TAS`, which one web search supplied; (3) **black-box comparison** — a per-cycle golden diff needs no source at all and usually resolves faster, because at that point the question is "which cycle differs", not "what is the rule"; (4) the derived oracle source, **last resort**. Rung 4 is permitted — the licences are compatible, both repos being GPL-3.0-or-later — **but the existing attributions live in the ORACLE, and the sibling is a separate repository**, so reading it obliges declaring the derivation there too: a site comment, the sibling's provenance doc, `NOTICE`, and an ADR 0037 amendment naming who authorised it. Never silently. On v2.6.4 rungs 1-3 were sufficient and the escalation went unspent — worth knowing, because the pull toward rung 4 is strongest exactly when the DUT and the oracle disagree, which is the moment this rule matters.
Enforcement lives alongside the prose: `/ref-proj/` is gitignored/`.dockerignore`d/`.markdownlintignore`d and excluded from CodeRabbit; `deny.toml` gates dependency licenses; every derived file carries an SPDX + provenance header. A rule the tooling enforces beats a rule you are merely asked to follow.
@@ -44,9 +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. 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). v2.9.2 triaged a fifth, AI-written audit of both repositories (`docs/audits/v2.9.2-full-audit-report.md`, 32 findings) in its ledger, `docs/audits/v2.9.2-full-audit-disposition.md`: 16 fixed red-first, 2 changed but verifiable only on a device (the Swift halves of AUD-10/14), and the rest refuted, declined or deferred with evidence. The sweep written for AUD-02 found that save states dropped the cartridge RAM of twelve board families; `every_board_snapshot_carries_cartridge_ram` now round-trips the RAM of all 174 mapper ids. It changes emulation behaviour in one deliberate place: an unmapped `$4020-$FFFF` read now updates the CPU's internal data bus (AUD-03). Opposing-direction cancel (AUD-08/09/10) is on by default, by the maintainer's decision (2026-09-28). The MiSTer CPU's /NMI edge detector ran on the DMA-stalled enable and lost an NMI raised inside a DMA (AUD-24, gate `dmanmi074`). v2.9.1 fixed `scripts/perf/ab_check.sh` (both sides had built into one target directory) and re-measured the old rejections; the dead NMI detector's per-dot call (−4.1% to −4.7% on palette frames) is removed in v2.9.8, which carries ADR 0042's removals (its 2026-10-01 amendment). The libretro core is built with `panic = "unwind"`, exercised by a C-ABI harness (`crates/rustynes-libretro/src/abi_tests.rs`), and patches a vendored `rust-libretro-sys` (`vendor/`). **AccuracyCoin 144/144 and nestest 0-diff** hold on the v2.9.2 tree, and the full `--features test-roms` suite passes 2,867 tests. v2.7.4's mobile changes, and v2.9.2's Swift, have a device checklist (`docs/mobile-v2.7.4-device-checklist.md`), consolidated at v2.9.3 into `docs/mobile-v2.9.3-run-sheet.md` with an emulator pre-run; the device run itself moved after v3.0.0 (maintainer, 2026-09-29). The co-simulation is **173 passed, 0 failed, 1 expected failure** on-die and **174 / 0 / 1** off-die (`USE_SDRAM=1`), each ONE frozen-worktree ladder run with nothing skipped; both bitstreams compile at fitter seed 2, chosen from eight per build at one build date (on-die +0.510 ns setup / +0.108 ns hold, off-die +0.390 / +0.081; the SDRAM read is +0.447 setup / +1.184 hold), and two clean compiles of each are byte-identical. The SDRAM pin constraints stay provisional until the SuperStation One's memory is read. **No hardware has run any bitstream**. The board session was planned on v2.9.2's bitstreams (`tools/stage_board_kit.sh` in the sibling stages what it needs); the maintainer moved it after v3.0.0 (2026-09-29). That settled what v3.0.0 ships: [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) makes it the API major with a release-candidate core, and hardware verification moves to a later v3.x release (it supersedes [ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)'s hardware-verified v3.0.0). Per-release detail lives in `CHANGELOG.md` and the GitHub releases; it is deliberately not duplicated here.
-
-- **Timebase (v2.0.0)** — the scheduler substrate is rewritten from a five-counter dot-lockstep model to a single canonical cycle counter, every CPU cycle clocked in two halves (`start_cycle` / `end_cycle`) with any bus access split between them, and the PPU caught up to each half (ADR 0002 / ADR 0029), now the *only* scheduler path. This is a MAJOR-boundary breaking change (ADR 0003): `.rns` save-state and `.rnm` movie format epochs bump (ADR 0028) — a pre-v2.0.0 `.rns` slot now fails to load with a clear error instead of silently misinterpreting stale bytes. Landed across five betas + rc.1 (PRs #217–223). Also new: core-level **Vs. `DualSystem`** dual-console support (`Emu::Dual`, `crates/rustynes-core`) for the four Vs. arcade cabinet boards — core-and-test-harness-only, frontend wiring deferred. The R1/R2 MMC3 IRQ-timing residual is by-design-deferred beyond this release with a mechanism-level finding recorded in ADR 0002 (not closed, not silently dropped). **AccuracyCoin now measures 141/141 (100.00%)**: the v2.0.1 upstream AccuracyCoin re-sync grew the catalog to 146 rows / 141 assigned tests and briefly opened two new PPU gaps ("ALE + Read" $0491, "Hybrid Addresses" $0492), which **v2.0.3** closed by promoting the 2-cycle-ALE PPU fetch model to the unconditional default (both experimental flags retired; additive `PPU_SNAPSHOT_VERSION` v5 tail). AccuracyCoin held 100% (139/139) throughout the v2.0.0 betas and final cut, dipped to 139/141 under the v2.0.1 re-sync, and is back to a full 141/141 from v2.0.3 onward.
+**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`.
- **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`.
@@ -70,9 +68,9 @@ Everything else about the release line is in `CHANGELOG.md`.
---
-**Release history → `CHANGELOG.md`.** The full per-release detail — features, the mapper-count growth (51 → **172 families**), ADRs, and PR trains for **v1.0.0 → v2.0.0** (plus the documentary engine-lineage stages v0.9.0–v0.9.7) — lives in `CHANGELOG.md` (the single source of truth for user-visible change), the per-release GitHub Releases, and `to-dos/plans/`. Every release through v1.10.0 was **additive / off-by-default**, so with new features off those builds stayed byte-identical; **v2.0.0 was RustyNES's first designated breaking release** (ADR 0003; v2.9.8 carried v3.0.0's breaks early, see above) — the one-clock, every-cycle-bus-access scheduler (ADR 0002 / ADR 0029) is now the *only* path, and the old PPU-dot lockstep model is retired. **AccuracyCoin holds 100% (139/139)** on every release including v2.0.0. Workspace baseline: edition 2024, Rust **1.96**, license **GPL-3.0-or-later** (RustyNES is a derivative work of GPL emulators — Mesen2 GPLv3, puNES/FCEUX/Nestopia GPLv2-or-later; relicensed in v2.2.9 per ADR 0036, credited in `docs/originality-and-provenance.md` + `NOTICE`), author **DoubleGate**; the WebAssembly / GitHub Pages build is live at .
+**Release history → `CHANGELOG.md`.** The full per-release detail — features, the mapper-count growth, ADRs, and PR trains for **v1.0.0 → v2.0.0** (plus the documentary engine-lineage stages v0.9.0–v0.9.7) — lives in `CHANGELOG.md` (the single source of truth for user-visible change), the per-release GitHub Releases, and `to-dos/plans/`. Every release through v1.10.0 was **additive / off-by-default**, so with new features off those builds stayed byte-identical; **v2.0.0 was RustyNES's first designated breaking release** (ADR 0003; v2.9.8 carried v3.0.0's breaks early, see above) — the one-clock, every-cycle-bus-access scheduler (ADR 0002 / ADR 0029) is now the *only* path, and the old PPU-dot lockstep model is retired. Workspace baseline: edition 2024, Rust **1.99** (see `rust-toolchain.toml`), license **GPL-3.0-or-later** (RustyNES is a derivative work of GPL emulators — Mesen2 GPLv3, puNES/FCEUX/Nestopia GPLv2-or-later; relicensed in v2.2.9 per ADR 0036, credited in `docs/originality-and-provenance.md` + `NOTICE`), author **DoubleGate**; the WebAssembly / GitHub Pages build is live at .
-**Engine-lineage versioning (read carefully).** The core descends from an accuracy program whose internal "v1.x / v2.x" milestones are folded into RustyNES stages v0.9.0–v0.9.7 → the v1.0.0 production cut. Read deep-narrative "v2.0" anchors from before 2026-07-03 (the master-clock refactor, old ADRs / audit logs under `docs/`) as **upstream engine lineage**, never as RustyNES release versions — that engine-lineage v2.0 work shipped as the v1.0.0 production core (2026-06-13) and is a *different* thing from RustyNES's own **v2.0.0 "Timebase"** release (2026-07-03, the base of the current v2.0.x "Harbor" line), which replaces that same dot-lockstep scheduler with the one-clock model. `docs/STATUS.md` is the authoritative per-suite pass-count + mapper matrix.
+**Engine-lineage versioning (read carefully).** The core descends from an accuracy program whose internal "v1.x / v2.x" milestones are folded into RustyNES stages v0.9.0–v0.9.7 → the v1.0.0 production cut. Read deep-narrative "v2.0" anchors from before 2026-07-03 (the master-clock refactor, old ADRs / audit logs under `docs/`) as **upstream engine lineage**, never as RustyNES release versions — that engine-lineage v2.0 work shipped as the v1.0.0 production core (2026-06-13) and is a *different* thing from RustyNES's own **v2.0.0 "Timebase"** release (2026-07-03), which replaces that same dot-lockstep scheduler with the one-clock model. `docs/STATUS.md` is the authoritative per-suite pass-count + mapper matrix.
## Build / test / lint
@@ -197,10 +195,10 @@ These cross-cutting decisions span multiple files. Reading individual chip docs
- Branch names: `/`.
- A chip-behavior change touches both the chip code and the chip's `docs/.md`. They drift apart easily; don't let them.
- For accuracy work: pin the failing test ROM expectation first, then implement until it passes.
-- Hot paths (`Cpu::tick`, `Ppu::tick`, mapper register access): no allocations, prefer fixed arrays, profile (`cargo bench` + `perf record`) before adding abstractions. **On the frame-cost number:** the `≤ 2 ms/frame headless` figure in `docs/performance.md` is a **design-phase aspiration** (written before the cycle-accurate core existed, for 2018-era Skylake) — it is NOT a live gate. The implemented core measures **~3.95 ms** (`nes_run_frame_nestest_fast`) / **~2.65 ms** (`nes_run_frame_flowing_palette_fast`) on the shipped fast dot path, and ~4.46 / ~2.67 ms on the exact path (i9-10850K, 2026-09-23), which `docs/performance.md` records as knowingly accepted for the master-clock design. The stock `full_frame` benches select the exact path explicitly; from v2.2.3 to 2026-09-23 they silently measured the fast path, which `docs/performance.md` §"Current figures" corrects. That is ~23% of the 16.639 ms NTSC budget. The dominant costs are work the accuracy model *requires* — `cpu_clock` is APU BLEP synthesis + the non-linear mixer (mixer ceiling measured ≤1.9%), and `Ppu::tick` is the per-dot lockstep loop — and the obvious levers were already measured and **rejected** (`emit_pixel` bounds-check elision was *slower*; the SIMD blitter was *slower*). Do not "optimize toward 2 ms" by trading away accuracy; the real-world multiplier on frame cost is **run-ahead**, not the per-frame core cost. Any optimization must be **byte-identical**, and must pass the adoption rule in `scripts/perf/ab_check.sh`: **the bar is evidence quality, not effect size** (maintainer decision, v2.3.1) — a consistent, reproduced, statistically clean gain is adoptable even below 3%. What is not negotiable is the second independent run (a single run has already produced a p = 0.00 result on all four workloads that was pure artifact), and a mixed-sign result across workloads is a rejection, not an average. Read the A/B/A order-bias control before the candidate column. (Until v2.7.5 this line said ">3% same-runner A/B bar", which contradicted the script; older records in `docs/performance.md` cite the bar that was in force when they were written.)
+- Hot paths (`Cpu::tick`, `Ppu::tick`, mapper register access): no allocations, prefer fixed arrays, profile (`cargo bench` + `perf record`) before adding abstractions. **On the frame-cost number:** the `≤ 2 ms/frame headless` figure in `docs/performance.md` is a **design-phase aspiration** (written before the cycle-accurate core existed, for 2018-era Skylake) — it is NOT a live gate. `docs/performance.md` §"Current figures" holds the measured numbers and records them as knowingly accepted for the master-clock design (the stock `full_frame` benches select the exact path explicitly). The dominant costs are work the accuracy model *requires* — `cpu_clock` is APU BLEP synthesis + the non-linear mixer (mixer ceiling measured ≤1.9%), and `Ppu::tick` is the per-dot lockstep loop — and the obvious levers were already measured and **rejected** (`emit_pixel` bounds-check elision was *slower*; the SIMD blitter was *slower*). Do not "optimize toward 2 ms" by trading away accuracy; the real-world multiplier on frame cost is **run-ahead**, not the per-frame core cost. Any optimization must be **byte-identical**, and must pass the adoption rule in `scripts/perf/ab_check.sh`: **the bar is evidence quality, not effect size** (maintainer decision, v2.3.1) — a consistent, reproduced, statistically clean gain is adoptable even below 3%. What is not negotiable is the second independent run (a single run has already produced a p = 0.00 result on all four workloads that was pure artifact), and a mixed-sign result across workloads is a rejection, not an average. Read the A/B/A order-bias control before the candidate column.
- `unsafe` requires a `// SAFETY:` comment explaining the invariant. The chip stack is `#![no_std]` + `extern crate alloc;`; only `rustynes-frontend` and `rustynes-cheevos` (FFI) carry `unsafe`.
- **Comprehensive rustdoc + comments (project rule).** Craft extensive `//!` crate/module preambles and `///` / `//` inline comments matching the quantity, quality, and technical depth of the existing `rustynes-*` crates — explain the *why* alongside the architectural detail, the memory-safety guarantees, and the lockstep-timing considerations.
-- **Comprehensive commit bodies (project rule).** Commit message bodies are robust, comprehensive, and technically detailed: go beyond a summary to explain architectural impact, the mathematical implementation, memory constraints, and the deep technical specifics (the maintainer's house style; see `docs/guidelines`).
+- **Comprehensive commit bodies (project rule).** Commit message bodies are robust, comprehensive, and technically detailed: go beyond a summary to explain architectural impact, the mathematical implementation, memory constraints, and the deep technical specifics (the maintainer's house style).
- Code style: rustfmt defaults + the crate-level import grouping in `rustfmt.toml`; `.editorconfig` mandates UTF-8 / LF / a final newline and indentation of four spaces for Rust, two for Markdown / TOML / YAML. Justify any local `#[allow]`.
## Operating notes for Claude Code
@@ -243,8 +241,6 @@ that is a reason to add a tenth — not a reason to grow this section back.
- 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.
- **RetroAchievements client identity:** the RA HTTP User-Agent (how RA authenticates/identifies/allowlists the client) is `RustyNES/ rcheevos/` — the `RA_USER_AGENT` const in `crates/rustynes-cheevos/src/http.rs`; the rcheevos version auto-syncs from the vendored `rc_version.h` via `build.rs` (`RCHEEVOS_VERSION`). Keep the leading `RustyNES/` token (a regression test guards it).
-- **Exhaustive Documentation Sweeps:** When tasked with generating comprehensive project documentation or wikis, always recursively list and read the contents of `docs/`, `ref-docs/`, and `to-dos/` to ensure no deep technical knowledge is missed.
-- **GitHub Wiki Initialization:** When assisting with GitHub Wiki deployments for the first time, instruct the user to click "Create the first page" in the GitHub UI to provision the `.wiki.git` repository. If the Wiki is cloned locally inside the main repository, ensure its folder (e.g., `RustyNES.wiki/`) is added to `.gitignore`.
- **Symlinked Agent Configs:** Ensure symlinked agent files (like `GEMINI.md` -> `AGENTS.md`) are explicitly removed from `.gitignore` so they are correctly tracked by version control.
<<< MC-PROJECT-END >>>
diff --git a/docs/history/AGENTS-archive.md b/docs/history/AGENTS-archive.md
new file mode 100644
index 000000000..b825a4d63
--- /dev/null
+++ b/docs/history/AGENTS-archive.md
@@ -0,0 +1,37 @@
+# History archive for AGENTS.md
+
+## Archived from AGENTS.md on 2026-10-07
+
+Paragraphs removed or condensed in the live file, reproduced verbatim (original line numbers in the labels). The live file keeps the rule in each case.
+
+### Original line 39: provenance paragraph (file-by-file snapshot and incident narrative)
+
+- **THE FIREWALL IS PER-REGION, NOT PER-REPO — the oracle's own source has exceptions (2026-08-26).** "RustyNES's code is ours, so it is readable" is true of the repository and **not uniformly true of every block in it.** **26 files** (at v2.7.1; the count is itself a snapshot — regenerate it, never quote this number) carry a `// Provenance:` header disclosing that a REGION of them is derived from a GPL reference emulator, and those regions are **black boxes for HDL purposes even though the file is ours** — writing SystemVerilog from them launders the original expression into the DUT *through* us, which is precisely what ADR 0037 exists to prevent. This was found on the first task of v2.6.4: its headline job is fixing the five `SH`-group stores in the DUT, and `crates/rustynes-cpu/src/cpu.rs` line 3 discloses `SHA/SHX/SHY/SHS/TAS` as **derived from Mesen2's `SyaSxaAxa`** (`Core/NES/NesCpu.h`) — so the single most relevant block of oracle source for that version is one that must not be read to write the RTL. Nothing in the tooling said so. **Before reading oracle source to inform sibling/HDL work, `grep -n "Provenance:" `**; regenerate the list with `grep -rln "^// Provenance:" crates` rather than trusting a snapshot. **The command used to read `crates/*/src/*.rs`, and that glob does not descend** — it silently missed `debugger/source_map.rs` and `bin/pgo_trainer.rs`, so it too reported a short list (24 against the real 26 at v2.7.1). `crates/rustynes-test-harness/tests/provenance_record_audit.rs` now walks the whole tree and fails if a header and its §1 row in `docs/originality-and-provenance.md` disagree in either direction. The ones that bite HDL work are `rustynes-cpu/src/cpu.rs` (SH group — rungs 1/5), **`rustynes-ppu/src/ppu.rs` (rungs 3/5 — the sprite-evaluation FSM, the OAM-data-bus model and the optional OAM-decay model from Mesen2 (the decay model was written at v2.1.4 and recorded only at v2.9.4), and the ALE/octal-latch address-multiplex and OAM-corruption behaviour from TriCNES)**, `rustynes-apu/src/blip.rs` (BLEP), **`rustynes-core/src/bus.rs` (DMA work — the OAM-DMA register-window read ported from TriCNES, and the DMA state modelled on its flags; header added 2026-09-28 at the maintainer's direction, after the v2.9.2 audit triage found the port disclosed only in a doc comment)**, and six mapper files (rung 7). **v2.9.9 added four (NC-17, maintainer 2026-10-04): `rustynes-apu/src/frame_counter.rs` (the lazy `$4015` clear and the PAL step table, from Mesen2) and `rustynes-apu/src/apu.rs` (the DMC-DMA state model, TriCNES / Mesen2) bite the sibling's APU parity work (the `$4017` rules); `rustynes-core/src/vs_dualsystem.rs` and `m019_namco163.rs` do not touch the RTL today.** **`ppu.rs` was missing from this sentence until 2026-09-19 and its absence was acted on**: the v2.6.22 CHR-during-rendering work read `ppu.rs` case 7 to form an RTL hypothesis, having checked the SENTENCE rather than running the command one line above it. The region read — the `$2007` PPUDATA write path — is outside all four disclosed regions, and the in-file note at `ppu.rs:777` re-states that scope, so nothing laundered; but the order was wrong, and it was an external reviewer that prompted the check rather than the process. **This sentence is a convenience, not the authority. The command is the authority, and the reason it exists is that a list of file names goes stale exactly the way the count above did — by ten files.** **The escalation ladder, maintainer-directed, in order — exhaust each rung before the next:** (1) vendored public documentation; (2) **the open Internet** — the vendored wiki is PARTIAL, documenting `SHX`/`SHY` in full and carrying nothing on `SHA`/`TAS`, which one web search supplied; (3) **black-box comparison** — a per-cycle golden diff needs no source at all and usually resolves faster, because at that point the question is "which cycle differs", not "what is the rule"; (4) the derived oracle source, **last resort**. Rung 4 is permitted — the licences are compatible, both repos being GPL-3.0-or-later — **but the existing attributions live in the ORACLE, and the sibling is a separate repository**, so reading it obliges declaring the derivation there too: a site comment, the sibling's provenance doc, `NOTICE`, and an ADR 0037 amendment naming who authorised it. Never silently. On v2.6.4 rungs 1-3 were sufficient and the escalation went unspent — worth knowing, because the pull toward rung 4 is strongest exactly when the DUT and the oracle disagree, which is the moment this rule matters.
+
+### Original line 47: release paragraph
+
+**Current release: 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). v2.9.2 triaged a fifth, AI-written audit of both repositories (`docs/audits/v2.9.2-full-audit-report.md`, 32 findings) in its ledger, `docs/audits/v2.9.2-full-audit-disposition.md`: 16 fixed red-first, 2 changed but verifiable only on a device (the Swift halves of AUD-10/14), and the rest refuted, declined or deferred with evidence. The sweep written for AUD-02 found that save states dropped the cartridge RAM of twelve board families; `every_board_snapshot_carries_cartridge_ram` now round-trips the RAM of all 174 mapper ids. It changes emulation behaviour in one deliberate place: an unmapped `$4020-$FFFF` read now updates the CPU's internal data bus (AUD-03). Opposing-direction cancel (AUD-08/09/10) is on by default, by the maintainer's decision (2026-09-28). The MiSTer CPU's /NMI edge detector ran on the DMA-stalled enable and lost an NMI raised inside a DMA (AUD-24, gate `dmanmi074`). v2.9.1 fixed `scripts/perf/ab_check.sh` (both sides had built into one target directory) and re-measured the old rejections; the dead NMI detector's per-dot call (−4.1% to −4.7% on palette frames) is removed in v2.9.8, which carries ADR 0042's removals (its 2026-10-01 amendment). The libretro core is built with `panic = "unwind"`, exercised by a C-ABI harness (`crates/rustynes-libretro/src/abi_tests.rs`), and patches a vendored `rust-libretro-sys` (`vendor/`). **AccuracyCoin 144/144 and nestest 0-diff** hold on the v2.9.2 tree, and the full `--features test-roms` suite passes 2,867 tests. v2.7.4's mobile changes, and v2.9.2's Swift, have a device checklist (`docs/mobile-v2.7.4-device-checklist.md`), consolidated at v2.9.3 into `docs/mobile-v2.9.3-run-sheet.md` with an emulator pre-run; the device run itself moved after v3.0.0 (maintainer, 2026-09-29). The co-simulation is **173 passed, 0 failed, 1 expected failure** on-die and **174 / 0 / 1** off-die (`USE_SDRAM=1`), each ONE frozen-worktree ladder run with nothing skipped; both bitstreams compile at fitter seed 2, chosen from eight per build at one build date (on-die +0.510 ns setup / +0.108 ns hold, off-die +0.390 / +0.081; the SDRAM read is +0.447 setup / +1.184 hold), and two clean compiles of each are byte-identical. The SDRAM pin constraints stay provisional until the SuperStation One's memory is read. **No hardware has run any bitstream**. The board session was planned on v2.9.2's bitstreams (`tools/stage_board_kit.sh` in the sibling stages what it needs); the maintainer moved it after v3.0.0 (2026-09-29). That settled what v3.0.0 ships: [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) makes it the API major with a release-candidate core, and hardware verification moves to a later v3.x release (it supersedes [ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)'s hardware-verified v3.0.0). Per-release detail lives in `CHANGELOG.md` and the GitHub releases; it is deliberately not duplicated here.
+
+### Original line 49: Timebase (v2.0.0) bullet
+
+- **Timebase (v2.0.0)** — the scheduler substrate is rewritten from a five-counter dot-lockstep model to a single canonical cycle counter, every CPU cycle clocked in two halves (`start_cycle` / `end_cycle`) with any bus access split between them, and the PPU caught up to each half (ADR 0002 / ADR 0029), now the *only* scheduler path. This is a MAJOR-boundary breaking change (ADR 0003): `.rns` save-state and `.rnm` movie format epochs bump (ADR 0028) — a pre-v2.0.0 `.rns` slot now fails to load with a clear error instead of silently misinterpreting stale bytes. Landed across five betas + rc.1 (PRs #217–223). Also new: core-level **Vs. `DualSystem`** dual-console support (`Emu::Dual`, `crates/rustynes-core`) for the four Vs. arcade cabinet boards — core-and-test-harness-only, frontend wiring deferred. The R1/R2 MMC3 IRQ-timing residual is by-design-deferred beyond this release with a mechanism-level finding recorded in ADR 0002 (not closed, not silently dropped). **AccuracyCoin now measures 141/141 (100.00%)**: the v2.0.1 upstream AccuracyCoin re-sync grew the catalog to 146 rows / 141 assigned tests and briefly opened two new PPU gaps ("ALE + Read" $0491, "Hybrid Addresses" $0492), which **v2.0.3** closed by promoting the 2-cycle-ALE PPU fetch model to the unconditional default (both experimental flags retired; additive `PPU_SNAPSHOT_VERSION` v5 tail). AccuracyCoin held 100% (139/139) throughout the v2.0.0 betas and final cut, dipped to 139/141 under the v2.0.1 re-sync, and is back to a full 141/141 from v2.0.3 onward.
+
+### Original line 73: release history paragraph
+
+**Release history → `CHANGELOG.md`.** The full per-release detail — features, the mapper-count growth (51 → **172 families**), ADRs, and PR trains for **v1.0.0 → v2.0.0** (plus the documentary engine-lineage stages v0.9.0–v0.9.7) — lives in `CHANGELOG.md` (the single source of truth for user-visible change), the per-release GitHub Releases, and `to-dos/plans/`. Every release through v1.10.0 was **additive / off-by-default**, so with new features off those builds stayed byte-identical; **v2.0.0 was RustyNES's first designated breaking release** (ADR 0003; v2.9.8 carried v3.0.0's breaks early, see above) — the one-clock, every-cycle-bus-access scheduler (ADR 0002 / ADR 0029) is now the *only* path, and the old PPU-dot lockstep model is retired. **AccuracyCoin holds 100% (139/139)** on every release including v2.0.0. Workspace baseline: edition 2024, Rust **1.96**, license **GPL-3.0-or-later** (RustyNES is a derivative work of GPL emulators — Mesen2 GPLv3, puNES/FCEUX/Nestopia GPLv2-or-later; relicensed in v2.2.9 per ADR 0036, credited in `docs/originality-and-provenance.md` + `NOTICE`), author **DoubleGate**; the WebAssembly / GitHub Pages build is live at .
+
+### Original line 75: engine-lineage paragraph
+
+**Engine-lineage versioning (read carefully).** The core descends from an accuracy program whose internal "v1.x / v2.x" milestones are folded into RustyNES stages v0.9.0–v0.9.7 → the v1.0.0 production cut. Read deep-narrative "v2.0" anchors from before 2026-07-03 (the master-clock refactor, old ADRs / audit logs under `docs/`) as **upstream engine lineage**, never as RustyNES release versions — that engine-lineage v2.0 work shipped as the v1.0.0 production core (2026-06-13) and is a *different* thing from RustyNES's own **v2.0.0 "Timebase"** release (2026-07-03, the base of the current v2.0.x "Harbor" line), which replaces that same dot-lockstep scheduler with the one-clock model. `docs/STATUS.md` is the authoritative per-suite pass-count + mapper matrix.
+
+### Original line 200: hot-path / frame-cost bullet (dated benchmark figures)
+
+- Hot paths (`Cpu::tick`, `Ppu::tick`, mapper register access): no allocations, prefer fixed arrays, profile (`cargo bench` + `perf record`) before adding abstractions. **On the frame-cost number:** the `≤ 2 ms/frame headless` figure in `docs/performance.md` is a **design-phase aspiration** (written before the cycle-accurate core existed, for 2018-era Skylake) — it is NOT a live gate. The implemented core measures **~3.95 ms** (`nes_run_frame_nestest_fast`) / **~2.65 ms** (`nes_run_frame_flowing_palette_fast`) on the shipped fast dot path, and ~4.46 / ~2.67 ms on the exact path (i9-10850K, 2026-09-23), which `docs/performance.md` records as knowingly accepted for the master-clock design. The stock `full_frame` benches select the exact path explicitly; from v2.2.3 to 2026-09-23 they silently measured the fast path, which `docs/performance.md` §"Current figures" corrects. That is ~23% of the 16.639 ms NTSC budget. The dominant costs are work the accuracy model *requires* — `cpu_clock` is APU BLEP synthesis + the non-linear mixer (mixer ceiling measured ≤1.9%), and `Ppu::tick` is the per-dot lockstep loop — and the obvious levers were already measured and **rejected** (`emit_pixel` bounds-check elision was *slower*; the SIMD blitter was *slower*). Do not "optimize toward 2 ms" by trading away accuracy; the real-world multiplier on frame cost is **run-ahead**, not the per-frame core cost. Any optimization must be **byte-identical**, and must pass the adoption rule in `scripts/perf/ab_check.sh`: **the bar is evidence quality, not effect size** (maintainer decision, v2.3.1) — a consistent, reproduced, statistically clean gain is adoptable even below 3%. What is not negotiable is the second independent run (a single run has already produced a p = 0.00 result on all four workloads that was pure artifact), and a mixed-sign result across workloads is a rejection, not an average. Read the A/B/A order-bias control before the candidate column. (Until v2.7.5 this line said ">3% same-runner A/B bar", which contradicted the script; older records in `docs/performance.md` cite the bar that was in force when they were written.)
+
+### Original line 246: Exhaustive Documentation Sweeps bullet
+
+- **Exhaustive Documentation Sweeps:** When tasked with generating comprehensive project documentation or wikis, always recursively list and read the contents of `docs/`, `ref-docs/`, and `to-dos/` to ensure no deep technical knowledge is missed.
+
+### Original line 247: GitHub Wiki Initialization bullet
+
+- **GitHub Wiki Initialization:** When assisting with GitHub Wiki deployments for the first time, instruct the user to click "Create the first page" in the GitHub UI to provision the `.wiki.git` repository. If the Wiki is cloned locally inside the main repository, ensure its folder (e.g., `RustyNES.wiki/`) is added to `.gitignore`.
From 38e09a48d738aa27bd3a58d36fe8ba7c4aaf349c Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Wed, 7 Oct 2026 07:17:03 -0400
Subject: [PATCH 3/5] review(v3.0.1): slice C -- sync 0f902095 (dependency
notes)
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
docs/agents/dependencies.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/agents/dependencies.md b/docs/agents/dependencies.md
index e9a8ea1d3..208bda088 100644
--- a/docs/agents/dependencies.md
+++ b/docs/agents/dependencies.md
@@ -15,3 +15,5 @@
- **The egui 0.36 / wgpu 30 bump is blocked on ONE upstream RELEASE, not on us — re-measured 2026-09-18.** `chore/egui-0.36-wgpu-30-blocked` is the ONLY copy of that migration: `main` is on `egui = "0.35"` / `wgpu = "29"`, so **deleting the branch destroys the work**. The blocker was never this project's code. `egui::DroppedFile` declares `bytes()` under `#[cfg(not(target_arch = "wasm32"))]` while `egui-winit`'s `NativeFile` implements it unconditionally, so the dependency itself fails to compile for wasm32 with `E0407`, and RustyNES ships a wasm demo. **Upstream fixed it**: PR #8516 "Fix egui-winit compilation on wasm32", merged **2026-09-07** into `main`, gating `mod dropped_file;` behind `cfg(not(target_arch = "wasm32"))`. **It is in no published version** — 0.36.2 shipped **2026-09-08**, the day AFTER that merge, without it. Both ends were verified by COMPILING a throwaway crate carrying this project's exact feature set (`default-features = false`, `links`/`wayland`/`x11`): 0.36.2 fails with the identical `E0407`, and rev `da7169ed` finishes clean. **The unblock condition is therefore a version, not an investigation**: the first published `egui-winit` whose `src/lib.rs` gates `mod dropped_file;` — expect 0.36.3 or 0.37. Four traps around this, every one of which cost a wrong conclusion here. **The fix is in `lib.rs`, NOT in the file the error cites** — `dropped_file.rs` still carries the unconditional `impl` on upstream `main`, so reading the file the compiler points at reads as "still broken" when it is fixed one level up. **`emilk/egui`'s default branch is `main`**, and a stale `master` ref also exists and answers `raw.githubusercontent.com` requests, so fetching `master` silently describes the wrong tree. **A `[patch.crates-io]` to that rev is SILENTLY IGNORED** against a `0.36.2` requirement, because the git rev declares `0.36.1` and does not satisfy `^0.36.2` — cargo drops the patch and builds the registry copy while the error still names the registry path; depend on the git rev directly, or require `"0.36"`, when probing. And **using the rev at all is refused by this project's own policy**: `deny.toml` sets `unknown-git = "deny"` with `allow-git = []`, and `Cargo.lock` carries zero git-sourced packages, so landing it early means widening the supply-chain policy — a maintainer decision, examined on 2026-09-18 and deliberately NOT taken in favour of waiting for the release. When it lands: the branch was cut at v2.3.2 against a `main` now at v2.6.20, so **rebase before trusting it**, and remember the bump is atomic — `egui-wgpu` 0.36 requires wgpu 30, so egui/egui-wgpu/egui-winit/wgpu/naga move together or the graph carries wgpu 29 and 30 at once and every device/queue handoff fails to unify. The five API deltas the branch already solved are enumerated in its commit body (`git log origin/chore/egui-0.36-wgpu-30-blocked`).
- **lz4_flex 0.14+ requires the crate's own `alloc` feature explicitly** for `compress_prepend_size`/`decompress_size_prepended` (used by `rewind.rs`/`zwinder.rs`) — it split real no_std support into an `alloc`-vs-`std` distinction that didn't exist in 0.13. A `cargo build --workspace` will NOT catch a missing `alloc` feature here because `rustynes-core`'s own default-on `std` feature implies it via cargo's feature unification; only a standalone `cargo build -p rustynes-core --target thumbv7em-none-eabihf --no-default-features` (the exact CI `no_std build` job) will. Run that command locally before pushing any bump that touches this dependency.
+- **A SCANNER'S "VULNERABLE DEPENDENCY" MAY BE A PACKAGE NO BUILD COMPILES: CHECK THE GRAPH, NOT THE LOCKFILE (v3.0.1).** Socket flagged GHSA-qwgh-2vcv-g2f7 (block-buffer, "panic corrupts inline buffer position", fixed only in 0.12.1) against `block-buffer 0.10.4` on `main`, marked "Not fixable". Both halves were right, and it still does not apply. The lockfile chain is `ratatui 0.30 -> ratatui-termwiz -> termwiz 0.23.3 -> sha2 0.10 -> digest 0.10 -> block-buffer 0.10.4`. Cargo locks a dependency's OPTIONAL dependencies whether or not any feature enables them (ratatui's `ratatui-termwiz`, `ratatui-termina` and `serde` are all locked), and nothing here enables ratatui's termwiz backend: `help-tui` takes ratatui's defaults (crossterm). `cargo tree -e normal,build | grep -c termwiz` is 0 for the default frontend, `--features full`, the wasm32 target and the whole workspace, and `cargo deny check advisories` passes. No upstream fix exists: termwiz's newest release (0.23.3, 2025-03-20) still requires `sha2 ^0.10`, which no patched block-buffer satisfies. Dismiss it in the scanner as not compiled; re-check if a feature ever enables a ratatui backend other than crossterm.
+- **SOCKET'S SUPPLY-CHAIN SCORE IS NOT A VULNERABILITY SCORE (v3.0.1).** On #590 it scored tokio 1.53.2 at 58, libc 0.2.190 at 78 (-9) and objc2 0.6.5 at 79, each with Vulnerability, Maintenance and License at 100. Those releases were 2-5 days old, and published by their usual maintainers (Darksonn, madsmtm). libc 0.2.190 was the crate's first crates.io Trusted Publishing release, published by a GitHub workflow rather than a person. tokio showed no delta, so its 58 is unchanged from 1.53.1. The specific alerts are only visible in the logged-in dashboard (the public package pages return 403 to scripts). Exposure: tokio enters only through `wasm-bindgen-futures` on wasm32 and is never compiled natively; objc2 only on Apple targets; libc everywhere (alsa, cpal, directories). Holding a crate back over a fresh-release score contradicts taking every dependency at its newest, and buys nothing a vulnerability score would show.
From bdd6011630e4e8fc3356f3e5648f6565e385aae1 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Wed, 7 Oct 2026 07:31:20 -0400
Subject: [PATCH 4/5] review(v3.0.1): slice C -- sync review round 1 (eecf8eaf,
61e9a42f)
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
ARCHITECTURE.md | 4 ++--
CONTRIBUTING.md | 2 +-
NOTICE | 8 +++++---
docs/benchmarks.md | 2 +-
docs/build-and-tooling.md | 2 +-
docs/dev/BUILD.md | 2 +-
docs/dev/STYLE_GUIDE.md | 2 +-
docs/tooling/oracle-tooling-setup.md | 7 +++++--
to-dos/plans/v3.x-hardware-verification-plan.md | 2 +-
9 files changed, 18 insertions(+), 13 deletions(-)
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 33d2d21fb..c23c54915 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -1,7 +1,7 @@
# RustyNES Architecture
**Document Version:** 2.1.0
-**Last Updated:** 2026-08-30
+**Last Updated:** 2026-10-07
**Applies to:** RustyNES v3.0.1 (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.
@@ -53,7 +53,7 @@ These cross-cutting choices span many files and are not negotiable without re-de
```text
rustynes/
-├── Cargo.toml # Workspace definition (edition 2024, MSRV 1.99; libretro path 1.96)
+├── Cargo.toml # Workspace definition (edition 2024, MSRV 1.99 for every crate)
├── crates/
│ ├── rustynes-core/ # Glue: Nes struct, run loop, scheduler, Bus,
│ │ # save state, region config. Re-exports chip crates.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 95c2ad020..b19cb96fe 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -134,7 +134,7 @@ forbids, so a flaky test is a bug to fix, not to retry.
- **Format:** `cargo fmt` (rustfmt defaults).
- **Lint:** pass `cargo clippy --workspace --all-targets -- -D warnings` with no warnings.
-- **Edition:** Rust 2024. **Toolchain:** 1.99 (pinned in `rust-toolchain.toml`). **MSRV:** 1.99, except the seven crates the libretro core builds (`rustynes-{cpu,ppu,apu,mappers,core,gamedb,libretro}`), which keep 1.96 for the libretro buildbot; CI checks them on 1.96.
+- **Edition:** Rust 2024. **Toolchain:** 1.99 (pinned in `rust-toolchain.toml`). **MSRV:** 1.99 for every crate. The libretro buildbot uses the same toolchain (`RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml`), and CI fails if the two ever differ, so move both in one change.
- The chip stack (`rustynes-{cpu,ppu,apu,mappers,core}`) is `#![no_std]` + `extern crate alloc;`. `unsafe` is only permitted at FFI boundaries (`rustynes-cheevos`) and the one native priority hook in `rustynes-frontend`, and **must** carry a `// SAFETY:` comment explaining the invariant.
- No emojis in code, comments, or commits (project policy).
diff --git a/NOTICE b/NOTICE
index a76f68916..3989ca478 100644
--- a/NOTICE
+++ b/NOTICE
@@ -128,8 +128,9 @@ Incorporated third-party components (permissively licensed, GPL-compatible)
Ported models were taken from commit 9199870; the last VENDORED oracle was
commit 94f1b117 (re-synced 2026-09-19; this line said f388af0b until v3.0.1,
one re-sync stale). The two are stated separately on purpose --
- one records what was derived from, the other what is in the tree, and this
- entry previously gave one id for both after the vendored copy moved on.
+ one records what was derived from, the other what was in the tree until the
+ v3.0.1 removal, and this entry previously gave one id for both after the
+ vendored copy moved on.
Copyright (c) 2025 Chris Siebert -- MIT
Note (v2.2.6 -> resolved v2.3.0): v2.2.6 disclosed that the octal-latch /
hybrid-address *timing* had been calibrated to TriCNES's per-dot behavior
@@ -144,7 +145,8 @@ Incorporated third-party components (permissively licensed, GPL-compatible)
current behavior is therefore documentation/oracle-derived -- it matches the
NESdev-documented delayed-`CopyV`-during-render timing and is pinned by the
MIT AccuracyCoin test ROM plus an exact-141/141 CI gate. TriCNES (MIT) remains
- the original cross-reference; its source is vendored with attribution. See
+ the original cross-reference; its source was vendored with attribution until
+ v3.0.1 and now lives outside the repository. See
docs/originality-and-provenance.md sec. 4 and ADR 0030.
* egui-winit 0.36.2 -- winit integration for egui, vendored with a three-site
diff --git a/docs/benchmarks.md b/docs/benchmarks.md
index 31c47bd84..b73c64eee 100644
--- a/docs/benchmarks.md
+++ b/docs/benchmarks.md
@@ -52,7 +52,7 @@ changing. (See the Phase-7/F "Option X" note in `rustynes-core/Cargo.toml`.)
- **Host:** Intel Core i9-10850K @ 3.60 GHz (10C/20T, Comet Lake), CachyOS
Linux, `powersave` cpufreq governor.
-- **Toolchain:** rustc 1.86.0 (the toolchain pinned when these were measured; the pin is 1.96 at v3.0.0), release
+- **Toolchain:** rustc 1.86.0 (the toolchain pinned when these were measured; the pin is 1.99 since v3.0.1), release
profile `opt-level = 3`, `lto = "thin"`, `codegen-units = 1`,
`panic = "abort"`, `overflow-checks = false` (the `bench` profile inherits
`release`).
diff --git a/docs/build-and-tooling.md b/docs/build-and-tooling.md
index 12df0751b..1256ac554 100644
--- a/docs/build-and-tooling.md
+++ b/docs/build-and-tooling.md
@@ -5,7 +5,7 @@
## Toolchain
- **Rust edition**: 2024.
-- **MSRV (minimum supported Rust version)**: 1.99, the pinned toolchain, for every crate except the seven the libretro core builds (`rustynes-{cpu,ppu,apu,mappers,core,gamedb,libretro}`), which declare 1.96 because the libretro buildbot stays on 1.96.0 (v3.0.1; see `.gitlab-ci.yml`). (History: 1.86 until v1.3.0 "Bedrock", which moved to 1.96 for the edition-2024 + egui 0.34.3 / wgpu 29 / rfd 0.17.2 dependency tier; 1.96 until v3.0.1.)
+- **MSRV (minimum supported Rust version)**: 1.99, the pinned toolchain, for every crate. The libretro buildbot builds on the same toolchain (`RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml`, which CI's `libretro-cross` checks against `rust-toolchain.toml`); v3.0.1 held its seven crates at 1.96 for one day, until a test pipeline showed the build image no longer passed `-C ar`. (History: 1.86 until v1.3.0 "Bedrock", which moved to 1.96 for the edition-2024 + egui 0.34.3 / wgpu 29 / rfd 0.17.2 dependency tier; 1.96 until v3.0.1.)
- **Channel**: the pinned `1.99.0` stable release — *not* a floating `stable`. `rust-toolchain.toml` is the single source of truth: every GitHub Actions job resolves its toolchain from that file (`.github/actions/rust-setup` parses the `channel` and fails closed if it cannot), and local builds pick it up automatically as a directory override. **One exception:** the libretro buildbot runs 1.96.0, set by `RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml` (which outranks the file), because its build image passes `-C ar`, a hard error from Rust 1.97. CI's `libretro-cross` job reads that value from `.gitlab-ci.yml` and builds on it, so the 1.96 floor is checked on every PR. There is no `toolchain:` version literal anywhere in `.github/`, so bumping the pin is a one-line edit here — but read the `-C ar` warning in `rust-toolchain.toml` before bumping to 1.97 or newer.
- **Nightly** is used for exactly one thing, outside CI and not a gate: `cargo fuzz`, which requires it for the sanitizer flags it threads through `rustc` (`cargo +nightly fuzz run ` — see `fuzz/README.md`). No build, test, lint, docs, release, or packaging path uses nightly.
- **Targets supported**: `x86_64-unknown-linux-gnu`, `aarch64-apple-darwin`, `x86_64-pc-windows-msvc`. Tier 2: `aarch64-unknown-linux-gnu`. Cross-compile targets declared in `rust-toolchain.toml` (auto-installed): `thumbv7em-none-eabihf` (the `no_std` chip-stack gate) and `wasm32-unknown-unknown` (browser). Android arm64/arm/x86_64 via `cargo ndk` (see `docs/android.md`). The `x86_64-apple-darwin` release target was retired (ADR 0009).
diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md
index 4eba091f9..f63f8d18d 100644
--- a/docs/dev/BUILD.md
+++ b/docs/dev/BUILD.md
@@ -21,7 +21,7 @@
### Required
-- **Rust** 1.99.0 (pinned in `rust-toolchain.toml`; the channel auto-installs). The libretro core's crates must also build on 1.96.0, which the libretro buildbot uses: `RUSTUP_TOOLCHAIN=1.96.0 cargo check --release -p rustynes-libretro`.
+- **Rust** 1.99.0 (pinned in `rust-toolchain.toml`; the channel auto-installs). The libretro buildbot uses the same toolchain (`RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml`; CI fails if it differs from the pin).
Edition 2024. (The pin was 1.86 until v1.3.0 "Bedrock", which moved to 1.96
for the edition-2024 + egui 0.34.3 / wgpu 29 / rfd 0.17.2 dependency tier,
and 1.96 until v3.0.1.)
diff --git a/docs/dev/STYLE_GUIDE.md b/docs/dev/STYLE_GUIDE.md
index 68b9ce739..9b5626c36 100644
--- a/docs/dev/STYLE_GUIDE.md
+++ b/docs/dev/STYLE_GUIDE.md
@@ -16,7 +16,7 @@ Coding conventions and best practices for contributing to RustyNES.
```toml
[package]
edition = "2024"
-rust-version.workspace = true # 1.99; the seven libretro-path crates declare "1.96"
+rust-version.workspace = true # 1.99, inherited by every crate
```
### Formatting
diff --git a/docs/tooling/oracle-tooling-setup.md b/docs/tooling/oracle-tooling-setup.md
index 38d686cb0..df3cbb6a6 100644
--- a/docs/tooling/oracle-tooling-setup.md
+++ b/docs/tooling/oracle-tooling-setup.md
@@ -72,7 +72,7 @@ stood here. A grep target survives a re-vendor; a line number does not.
checklist, answer keys (`$0477` DMC+OAM `04 03 04 03 04 03 02 01…`).
- `ref-docs/tricnes-vs-rustynes-accuracy-roadmap-2026-06-02.md` — the roadmap.
-### 2a. In-repo buildable instrumented harness (the per-cycle cross-diff oracle)
+### 2a. The buildable instrumented harness, outside the repository (the per-cycle cross-diff oracle)
The cross-diff oracle used for the DMA-tail / Program-M work is a **trimmed, instrumented TriCNES
built from source** (TriCNES is MIT — Chris Siebert 2025). It was vendored in this repo until
@@ -82,7 +82,10 @@ v3.0.1 and now lives outside it:
(instrumented with the per-cycle window logger; 88 lines against upstream), `Program.cs`,
`6502Documentation.cs`, `mappers/` (all 11 `Mapper_*.cs`, **required to build**),
`tricnes-harness.csproj`, `LICENSE`. Build with `dotnet build -c Release` (.NET 10 SDK). On a
- machine without it, restore the tree from this repository's history before the v3.0.1 removal.
+ machine without it, extract it from this repository's history straight to that OUTSIDE path,
+ never into the working tree (guardrails section 3a): `mkdir -p ~/reference-oracles/TriCNES-rustynes-harness
+ && git archive 416fe7d7^:crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src | tar -x
+ -C ~/reference-oracles/TriCNES-rustynes-harness`.
- `~/reference-oracles/TriCNES/` — a clone of `github.com/100thCoin/TriCNES` at `94f1b117`, for
reference and re-trimming.
- Cross-diff driver: `scripts/tricnes_xdiff.py` (+ the ad-hoc helpers under `scripts/diag/`, salvaged
diff --git a/to-dos/plans/v3.x-hardware-verification-plan.md b/to-dos/plans/v3.x-hardware-verification-plan.md
index 4a21752d1..8c7754df0 100644
--- a/to-dos/plans/v3.x-hardware-verification-plan.md
+++ b/to-dos/plans/v3.x-hardware-verification-plan.md
@@ -51,7 +51,7 @@ Written for v3.0.0 and restated for D29's position. By the time this release
runs, the off-die build has been the headline since v3.3.0 (D4), and both builds
carry the v3.2.0-v3.8.0 features.
-| Artefact | Status at the hardware release |
+| Artifact | Status at the hardware release |
| --- | --- |
| **Off-die `.rbf`** (the SS1's 128 MB SDRAM) | **The headline since v3.3.0 (D4).** MemTest, its own Strand B rows and HW-O6's real SDRAM constraints passed |
| **On-die `.rbf`** (`USE_SDRAM_CART = 1'b0`) | **The "lite" build.** Every bring-up strand A–F that applies to it passed on the SuperStation One |
From b00cbf1e2daecec8bec0a83aa3dd67776d3ba084 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Wed, 7 Oct 2026 09:18:36 -0400
Subject: [PATCH 5/5] review(v3.0.1): slice C -- sync review round 3 (36c15ea3)
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
CHANGELOG.md | 3 ++-
OVERVIEW.md | 2 +-
VERSION-PLAN.md | 2 +-
docs/adr/0035-rustynes-is-permanently-non-commercial.md | 5 +++--
docs/agents/libretro.md | 2 +-
docs/build-and-tooling.md | 2 +-
docs/performance.md | 5 +++--
docs/scheduler.md | 5 +++--
to-dos/plans/v2.0.x-mobile-finalization-plan.md | 2 +-
to-dos/plans/v3.0.1-mortar-plan.md | 2 +-
to-dos/plans/v3.4.0-plan.md | 4 ++--
to-dos/plans/v3.9.0-plan.md | 4 ++--
to-dos/plans/v4.0.0-plan.md | 2 +-
13 files changed, 22 insertions(+), 18 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ba07dda38..3c0046cd6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -141,7 +141,8 @@ hardware-verified**. The maintainer's decisions are in
- `cargo test --release --workspace --features test-roms --no-fail-fast`:
3,234 passed, 0 failed, 11 ignored on the release tree (v3.0.0: 3,223 / 0 /
11). `cargo test --workspace`: 2,886 / 0 / 7; the cosim crate 54 / 0.
- AccuracyCoin 144/144, nestest 0-diff.
+ AccuracyCoin 144/144, nestest 0-diff. The per-suite counts and the mapper
+ matrix are in [`docs/STATUS.md`](docs/STATUS.md).
- The local commercial suites (`--features test-roms,commercial-roms`):
`external_real_games` 60/0, `external_extended` 137/0, `external_coverage`
6/0. The one moved baseline is *Famicom Yarou Vol.1* (T-GA23C-CHRRAM), which
diff --git a/OVERVIEW.md b/OVERVIEW.md
index 06fa56f0d..3bc8b90c0 100644
--- a/OVERVIEW.md
+++ b/OVERVIEW.md
@@ -22,7 +22,7 @@
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 one deliberate 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.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**.
diff --git a/VERSION-PLAN.md b/VERSION-PLAN.md
index 43b70541f..4cff5b920 100644
--- a/VERSION-PLAN.md
+++ b/VERSION-PLAN.md
@@ -19,7 +19,7 @@ v1.0.0 is the **production cut that integrates the cycle-accurate emulation engi
MAJOR.MINOR.PATCH[-PRERELEASE]
```
-- **MAJOR** — an incompatible break of the public Rust API (`rustynes-core` and the chip-crate types it re-exports), or the first release of a new deliverable class ([ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md): the first hardware-verified FPGA core). Now at `3`: **v3.0.0 "Cornerstone"** was MAJOR by the first trigger and shipped an unverified release-candidate core; the hardware-verified core is a later v3.x release, numbered after the board session and no later than v4.0.0 ([ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) and its 2026-10-07 amendment). **A format break alone is not a MAJOR trigger since 2026-10-07** (maintainer decision; see "Breaking-change policy" below).
+- **MAJOR** — an incompatible break of the public Rust API (`rustynes-core` and the chip-crate types it re-exports), or the first release of a new deliverable class when that release is designated MAJOR ([ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md) named the first hardware-verified FPGA core; [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) lets it be a minor instead, and its 2026-10-07 amendments place it at the end of v3.9.x). Now at `3`: **v3.0.0 "Cornerstone"** was MAJOR by the first trigger and shipped an unverified release-candidate core; the hardware-verified core is a later v3.x release, numbered after the board session and no later than v4.0.0 ([ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) and its 2026-10-07 amendment). **A format break alone is not a MAJOR trigger since 2026-10-07** (maintainer decision; see "Breaking-change policy" below).
- **MINOR** — features (new mappers, new frontend features, new platforms). May carry a documented format break.
- **PATCH** — bug fixes and accuracy refinements. May carry a documented format break when the fix needs one (v3.0.1 raised `EMULATION_EPOCH`).
- **PRERELEASE** — `-alpha.N` / `-beta.N` / `-rc.N` when stabilizing a future minor/major.
diff --git a/docs/adr/0035-rustynes-is-permanently-non-commercial.md b/docs/adr/0035-rustynes-is-permanently-non-commercial.md
index 3c8262edd..21a094d63 100644
--- a/docs/adr/0035-rustynes-is-permanently-non-commercial.md
+++ b/docs/adr/0035-rustynes-is-permanently-non-commercial.md
@@ -80,8 +80,9 @@ v4.0.0's final development, test and release activities.**
That covers:
-- Android developer verification, which also applies to sideloaded apps and is
- global from 2027;
+- Android developer verification, which covers the ordinary installation path
+ (from 2026-09-30 in four countries, worldwide on certified devices in 2027);
+ ADB and Android's "advanced flow" can still install an unregistered app;
- iOS signing, so that TestFlight uploads run;
- the Google Play, F-Droid or IzzyOnDroid, and App Store listings.
diff --git a/docs/agents/libretro.md b/docs/agents/libretro.md
index 36f81077a..cd6a9deaf 100644
--- a/docs/agents/libretro.md
+++ b/docs/agents/libretro.md
@@ -19,7 +19,7 @@
- **Libretro buildbot CI (issue #311) shipped in PR #312 (2026-07-19/20, `b49dd1e0`) — and the upstream half is now DONE; the "stays OPEN by design" instruction this bullet used to lead with is RETIRED, see below.** `.gitlab-ci.yml` + a `[lib] name = "rustynes"` naming-collision fix + `[workspace] default-members` + RA memory-maps + an FDS load-path fix/disk-control + native Game Genie cheats all shipped. TAS and Netplay needed no new libretro-side wiring — RetroArch's own rollback/movie machinery already rides the existing `on_serialize`/`on_unserialize` hooks. **DONE as of 2026-07-21, re-measured 2026-09-20** — this bullet described the upstream half as pending for two months after it landed. Companion PRs `libretro/docs#1164` (merged 2026-07-21) and `libretro/libretro-super#2021` (merged 2026-07-24) are both in, the mirror + buildbot step the libretro team owned is complete, and `rustynes_libretro.so.zip` is on the nightly buildbot for **linux/x86_64, windows/x86_64 and apple/osx/arm64** (checked by fetching the three `latest/` listings, not by asking anyone). `dist/info/rustynes_libretro.info` upstream reads `license = "GPLv3+"`, `display_version = "v2.3.9"`. **Issue #311 is CLOSED and that is now CORRECT** — but read its timeline before citing it, because it closed twice for opposite reasons: `2026-07-19T22:28` by doublegate via a commit-body keyword (premature, reopened 27 minutes later at `22:55`), then `2026-07-21T13:07` **by `hizzlekizzle`, an upstream maintainer, by hand and with no commit id** — which is exactly the condition this bullet demanded. **The keyword rule still stands and the timeline is its evidence:** never put a closing keyword for an unfinished issue in a commit or PR body, because the first close proves GitHub acts on it instantly and the tracker then reports finished work that was not done. What retires an issue is a verified outcome — here, three buildbot listings and a maintainer's own click.
- **The libretro buildbot is a THIRD CI system with its own rules — and the pinned toolchain fights it.** The first real run (pipeline #91899, 2026-07-20) passed 1 of 10 jobs. `rust-toolchain.toml`'s `channel = "1.96.0"` makes rustup install a *fresh* toolchain inside libretro's build image, bypassing the image's pre-provisioned cross targets, so 8 jobs died on `E0463: can't find crate for core`; each job in `.gitlab-ci.yml` now runs `rustup target add ${RUST_TARGET}` (NOT added to `rust-toolchain.toml`'s `targets` — that would cost every contributor and GH Actions job ~8 extra `rust-std` downloads). The Apple jobs must use `!reference` rather than `extends` for that, because GitLab's `extends` REPLACES array keys and would silently drop the templates' `SDKROOT`/`STRIP`/`CC`/`CXX` exports. **tvOS: the upstream template's `cargo +nightly build -Zbuild-std` override is OBSOLETE — don't reinstate it.** It dates from when `aarch64-apple-tvos` was tier 3 with no distributed `rust-std`; the target has since been promoted and rustup ships a complete prebuilt std **including `panic_abort`** (verified on the pinned 1.96.0: `rustup target add aarch64-apple-tvos` gives 26 rlibs and the crate `cargo check`s clean, bindgen included). Our job overrides `script` back to `!reference [.libretro-rust-apple-base, script]`, putting tvOS on the same pinned stable as every other job. That one change dissolved THREE stacked workarounds the `+nightly` path had forced: a nightly-channel reinstall (`+nightly` outranks both `rust-toolchain.toml` and `RUSTUP_TOOLCHAIN`, so the job rode the image's stale 1.94.0-nightly, below our MSRV); `CARGO_PROFILE_RELEASE_PANIC=unwind` (bare `-Zbuild-std` omits `panic_abort`, and `CARGO_UNSTABLE_BUILD_STD` does NOT override the hardcoded crate list — the CLI `-Z` flag wins); and clearing the image's `-C ar` (see the next bullet). (Since v2.8.0 that same variable is set to `unwind` again for EVERY buildbot job, tvOS included, by `.core-defs` — deliberately, for panic containment (L-1.1), not as the old tvOS workaround; `panic_abort` being available does not make the abort profile effective there.) Worth reporting upstream: every Rust core's tvOS job could drop `+nightly` the same way. **A green GitHub Actions run does not imply a green buildbot** — the `libretro-cross` CI job (the buildbot triples a Linux runner can model — at #554, 2026-09-24: 64- and 32-bit MinGW-Windows, Linux aarch64 / i686, armhf, the webOS `armv7-unknown-linux-gnueabi`, and Android/NDK; this parenthesis first said "MinGW-Windows and Android/NDK", which had been stale since the aarch64 and armhf legs landed; the Apple families are deliberately excluded, as bindgen needs a real per-target sysroot and there is no Apple SDK on a Linux runner) is the early-warning gate; before touching anything libretro-related, cross-check `cargo check --release -p rustynes-libretro --target ` locally.
-- **The libretro build image injects `-C ar` into EVERY Apple job, and it is a hard error from Rust 1.97 — a bomb armed against the next MSRV bump.** The image (not the `rust-apple.yml` template, which sets no `RUSTFLAGS` at all, and not our `.cargo/config.toml`) adds `-Car=,Clink-arg=-undefined,Clink-arg=dynamic_lookup,-rpath=` to osx-x64 / osx-arm64 / ios-arm64 / tvos-arm64. `-C ar` was a deprecated no-op for years and became a **hard error in 1.97** (bisected locally: 1.93.0-nightly / 1.96.0 / 1.96.1 warn; 1.97.1 and 1.99.0-nightly error). No job trips it today — all four Apple jobs are on the pinned 1.96.0 and merely log the warning. **The day `rust-toolchain.toml` moves to 1.97+, all four fail together** — the warning lives in that file, at the line someone would edit. Discarding the flags is behaviour-preserving, not a gamble: rustc splits `-C` at the FIRST `=`, so the whole comma-joined string is swallowed as the `ar` value and those link args have never reached the linker for *any* core (cargo prints it as one argv token), and two upstream Rust cores have green tvOS jobs on the same image with the same dead token. The override works without knowing where the image sets it because cargo takes rustflags from exactly one source, first match wins: `CARGO_ENCODED_RUSTFLAGS` → `RUSTFLAGS` → `target..rustflags` → `build.rustflags` (verified locally against a global `~/.cargo/config.toml` `build.rustflags`: `RUSTFLAGS=""` removes every injected `-C`, and empty means zero flags, not one empty argument).
+- **(HISTORICAL, until 2026-09-03; the current state is the "UNDID THE SPLIT" bullet below.) The libretro build image injected `-C ar` into EVERY Apple job, and it is a hard error from Rust 1.97 — a bomb armed against the next MSRV bump.** The image (not the `rust-apple.yml` template, which sets no `RUSTFLAGS` at all, and not our `.cargo/config.toml`) adds `-Car=,Clink-arg=-undefined,Clink-arg=dynamic_lookup,-rpath=` to osx-x64 / osx-arm64 / ios-arm64 / tvos-arm64. `-C ar` was a deprecated no-op for years and became a **hard error in 1.97** (bisected locally: 1.93.0-nightly / 1.96.0 / 1.96.1 warn; 1.97.1 and 1.99.0-nightly error). No job trips it today — all four Apple jobs are on the pinned 1.96.0 and merely log the warning. **The day `rust-toolchain.toml` moves to 1.97+, all four fail together** — the warning lives in that file, at the line someone would edit. Discarding the flags is behaviour-preserving, not a gamble: rustc splits `-C` at the FIRST `=`, so the whole comma-joined string is swallowed as the `ar` value and those link args have never reached the linker for *any* core (cargo prints it as one argv token), and two upstream Rust cores have green tvOS jobs on the same image with the same dead token. The override works without knowing where the image sets it because cargo takes rustflags from exactly one source, first match wins: `CARGO_ENCODED_RUSTFLAGS` → `RUSTFLAGS` → `target..rustflags` → `build.rustflags` (verified locally against a global `~/.cargo/config.toml` `build.rustflags`: `RUSTFLAGS=""` removes every injected `-C`, and empty means zero flags, not one empty argument).
- **v3.0.1 SPLIT THE TOOLCHAIN instead of disarming the `-C ar` bomb.** `rust-toolchain.toml` moved to 1.99.0; `.gitlab-ci.yml` `.core-defs` sets `RUSTUP_TOOLCHAIN: "1.96.0"` (it outranks `rust-toolchain.toml`), and every job's `rustup target add` became `rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}`. The libretro templates set no `RUSTUP_TOOLCHAIN` or rustflags (checked in `rust-apple.yml`, `rust-linux-x64.yml`, `rust-windows-x64.yml`, `rust-android-jni.yml`, `rust-webos.yml`, 2026-10-06). The seven crates the core compiles declare `rust-version = "1.96"`, and GitHub CI's `libretro-cross` reads the version out of `.gitlab-ci.yml` (no literal in `.github/`) and builds on it. **That check has already paid for itself:** clippy 1.99 rewrote `for b in self.ram.iter_mut()` (a `Box<[u8; 2048]>`) to `for b in &mut self.ram`, which 1.96 rejects (`&mut Box<[T; N]>` is not `IntoIterator` there); clippy honoured nothing about the crate's `rust-version`, and only the 1.96 build caught it (`bus.rs`, fixed as `&mut *self.ram`). The documented two-line `RUSTFLAGS` fix in `rust-toolchain.toml` would let the buildbot follow; it was not taken in v3.0.1. **CORRECTED the same day: the buildbot CAN be tested before merge.** libretro's mirror of the GitHub repo runs a pipeline for every pushed BRANCH, not only `main` (`git.libretro.com/api/v4/projects/libretro%2FRustyNES/pipelines?ref=`; the v3.0.0 cycle shows pipelines for `release/v3.0.0` and the review slices). The pushed `release/v3.0.1` got pipeline 119606 within about 20 minutes, and all 15 jobs (Apple included) passed on the 1.96 pin. The job LIST is public; job LOGS need a login (401). A review-slice branch fails every job by construction (each holds only part of a release), so expect red there and do not read it as a defect.
- **AND v3.0.1 UNDID THE SPLIT, the same day (2026-10-07).** The research for the v3.1 roadmap found that `libretro-infrastructure/libretro-build-rust` removed the `-C` usage from the build image on 2026-09-03 (`841f3619`, "Remove -C usage as no longer supported by rust"; the rebuilt image's own pipeline passed 2026-09-23). A throwaway branch, `test/libretro-rust-1.99`, set only `RUSTUP_TOOLCHAIN: "1.99.0"`; its pipeline 119614 passed all 15 jobs, `osx-x64`, `osx-arm64`, `ios-arm64` and `tvos-arm64` included. So the pin now equals `rust-toolchain.toml` again, the seven crates inherit the workspace `rust-version`, and `libretro-cross` FAILS if the two toolchains differ, so neither can drift alone. Two lessons: the hazard lived in someone else's image, so re-check it there before planning around it; and since the image now separates its `-C link-arg` flags properly, those link arguments reach the linker for the first time. That is harmless on 119614, but it is the first thing to suspect if an Apple link changes behaviour.
- **`rust-libretro 0.3.2` is unmaintained (no commit since 2023-02) and has a MinGW bug we work around.** It casts a keycode with `cfg(target_family = "windows")`, but C enum signedness follows the *ABI*: only **MSVC** gives plain enums `int` — under **MinGW** (`x86_64-pc-windows-gnu`, what the buildbot builds) bindgen emits `c_uint` and the crate fails `E0308`. `.cargo/config.toml`'s `[env] BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu = "--target=x86_64-pc-windows-msvc"` fixes it; the generated-bindings diff is 28 lines, all enum signedness. Don't "clean up" that env var without rebuilding for `x86_64-pc-windows-gnu`.
diff --git a/docs/build-and-tooling.md b/docs/build-and-tooling.md
index 1256ac554..ebb2bffdd 100644
--- a/docs/build-and-tooling.md
+++ b/docs/build-and-tooling.md
@@ -6,7 +6,7 @@
- **Rust edition**: 2024.
- **MSRV (minimum supported Rust version)**: 1.99, the pinned toolchain, for every crate. The libretro buildbot builds on the same toolchain (`RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml`, which CI's `libretro-cross` checks against `rust-toolchain.toml`); v3.0.1 held its seven crates at 1.96 for one day, until a test pipeline showed the build image no longer passed `-C ar`. (History: 1.86 until v1.3.0 "Bedrock", which moved to 1.96 for the edition-2024 + egui 0.34.3 / wgpu 29 / rfd 0.17.2 dependency tier; 1.96 until v3.0.1.)
-- **Channel**: the pinned `1.99.0` stable release — *not* a floating `stable`. `rust-toolchain.toml` is the single source of truth: every GitHub Actions job resolves its toolchain from that file (`.github/actions/rust-setup` parses the `channel` and fails closed if it cannot), and local builds pick it up automatically as a directory override. **One exception:** the libretro buildbot runs 1.96.0, set by `RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml` (which outranks the file), because its build image passes `-C ar`, a hard error from Rust 1.97. CI's `libretro-cross` job reads that value from `.gitlab-ci.yml` and builds on it, so the 1.96 floor is checked on every PR. There is no `toolchain:` version literal anywhere in `.github/`, so bumping the pin is a one-line edit here — but read the `-C ar` warning in `rust-toolchain.toml` before bumping to 1.97 or newer.
+- **Channel**: the pinned `1.99.0` stable release — *not* a floating `stable`. `rust-toolchain.toml` is the single source of truth: every GitHub Actions job resolves its toolchain from that file (`.github/actions/rust-setup` parses the `channel` and fails closed if it cannot), and local builds pick it up automatically as a directory override. **The libretro buildbot uses the same toolchain**: `RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml` (which outranks the file) must equal the pin, and CI's `libretro-cross` job fails if the two differ, so a pin move edits both files in one change. There is no `toolchain:` version literal anywhere in `.github/`. (Until v3.0.1 the buildbot was a 1.96.0 exception, because its build image passed `-C ar`, a hard error from Rust 1.97; the image dropped the flag on 2026-09-03, and `rust-toolchain.toml` keeps the two-line fix should it return.)
- **Nightly** is used for exactly one thing, outside CI and not a gate: `cargo fuzz`, which requires it for the sanitizer flags it threads through `rustc` (`cargo +nightly fuzz run ` — see `fuzz/README.md`). No build, test, lint, docs, release, or packaging path uses nightly.
- **Targets supported**: `x86_64-unknown-linux-gnu`, `aarch64-apple-darwin`, `x86_64-pc-windows-msvc`. Tier 2: `aarch64-unknown-linux-gnu`. Cross-compile targets declared in `rust-toolchain.toml` (auto-installed): `thumbv7em-none-eabihf` (the `no_std` chip-stack gate) and `wasm32-unknown-unknown` (browser). Android arm64/arm/x86_64 via `cargo ndk` (see `docs/android.md`). The `x86_64-apple-darwin` release target was retired (ADR 0009).
diff --git a/docs/performance.md b/docs/performance.md
index 95913f23b..acaa9ce91 100644
--- a/docs/performance.md
+++ b/docs/performance.md
@@ -16,8 +16,9 @@ Set quantitative performance targets, identify expected hot paths, and lay out t
> **These are DESIGN-PHASE targets, written before the cycle-accurate core
> existed — they are aspirations, not gates.** The frame-cost row in particular
> was never met and is knowingly accepted: the implemented core measures
-> **~3.95 ms** (`nes_run_frame_nestest_fast`) / **~2.65 ms**
-> (`nes_run_frame_flowing_palette_fast`) on the shipped fast dot path, and
+> **~3.95 ms** (`nes_run_frame_nestest_fast`) on the shipped fast dot path and
+> **~2.65 ms** (`nes_run_frame_flowing_palette_fast`, a rendering-disabled
+> control: the fast path's guard bails, so it never enters that path), and
> ~4.46 / ~2.67 ms on the exact path, on a 2020 desktop (i9-10850K; see
> "Current figures" below, measured 2026-09-23). The gate that
> actually runs in CI is the **relative, same-runner regression check** (§CI
diff --git a/docs/scheduler.md b/docs/scheduler.md
index f3d520c95..8945f39ec 100644
--- a/docs/scheduler.md
+++ b/docs/scheduler.md
@@ -157,8 +157,9 @@ This guarantees that save/load round-trips and a re-played input sequence produc
> These are the original **design-phase aspirations**, not gates. The frame-cost
> figure was not met and is knowingly accepted — the implemented cycle-accurate
-> core measures **~3.95 ms** (nestest) / **~2.65 ms** (flowing palette) on the
-> shipped fast dot path, ~23% of the 16.639 ms NTSC budget (the v2.7.0 core,
+> core measures **~3.95 ms** (nestest) on the shipped fast dot path, ~23% of the
+> 16.639 ms NTSC budget, and **~2.65 ms** on flowing palette, a rendering-disabled
+> control whose fast-path variant never enters that path (its guard bails) (the v2.7.0 core,
> 2026-09-23; v2.9.7's A12 change added about 1.9% on nestest). See
> `docs/performance.md` §"Current figures" for the measured numbers, the
> exact-path pair, and why the main optimization levers were measured and
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 12718489a..f1864d6c1 100644
--- a/to-dos/plans/v2.0.x-mobile-finalization-plan.md
+++ b/to-dos/plans/v2.0.x-mobile-finalization-plan.md
@@ -8,7 +8,7 @@
> listing is an unversioned later step with no monetization attached. The plan is
> kept as written for the record; the section-level notes below mark the parts
> ADR 0035 removed.
-
+>
> **Maintainer decision, 2026-06-23.** The Android (v1.8.x) and iOS (v1.9.0) apps
> will **not** ship to their app stores on their original, independent timelines.
> Both store launches are **held until after v2.0.0 "Timebase"** so the shipped
diff --git a/to-dos/plans/v3.0.1-mortar-plan.md b/to-dos/plans/v3.0.1-mortar-plan.md
index 2da19abf5..16d1366e4 100644
--- a/to-dos/plans/v3.0.1-mortar-plan.md
+++ b/to-dos/plans/v3.0.1-mortar-plan.md
@@ -31,7 +31,7 @@ The first patch on v3.0.0 "Cornerstone"; the codename is "Mortar" (maintainer,
| 2 | done | `c986411f`: unbanked per NES 2.0 mapper 372's page ("CHR-RAM (1, unbanked)"); Vol.1 draws its "7 IN 1" menu; epoch 1 → 2 |
| 3 | done, and it found a DUT defect | sibling `684af85`: `mapper4mmc3oddskip080` reaches the exception on 3 of 100 frames; the rule's live `rendering` gate was wrong (ledger 3.49), fixed to "was dot 1 rendering"; the exception's mutant CAUGHT on the new `IRQ_RISE` surface (`tb/irq_rise_diff.py`) |
| 4 | done | `4aae8c13` (Rust 1.99, libretro buildbot first held on 1.96 via `RUSTUP_TOOLCHAIN`, then moved to 1.99 once test pipeline 119614 passed 15/15 (D5), every crate and action, Android, web, Docker, pre-commit); sibling `e77050d` (runner pinned, Verilator 5.020 → 5.032 under review) |
-| 5 | done | 290 ledger rows → 473 verdicts (140 fixed, 133 declined, 80 still valid, 55 refuted, 36 obsolete, 28 no findings, 1 open); the 80 fixed by four worktree agents and cherry-picked; 279 replies posted (153 thread replies, 77 resolves, 126 PR comments); afterwards 0 unresolved and 0 unanswered threads in both repositories |
+| 5 | done | 290 ledger rows → 473 verdicts (140 fixed, 133 declined, 80 still valid, 55 refuted, 36 obsolete, 28 no findings, 1 open); the 80 fixed by four worktree agents and cherry-picked; 279 replies posted (153 thread replies and 126 PR comments), plus 77 threads resolved; afterwards 0 unresolved and 0 unanswered threads in both repositories |
## What the sweep is NOT
diff --git a/to-dos/plans/v3.4.0-plan.md b/to-dos/plans/v3.4.0-plan.md
index 492170c22..fd98d5fc9 100644
--- a/to-dos/plans/v3.4.0-plan.md
+++ b/to-dos/plans/v3.4.0-plan.md
@@ -10,9 +10,9 @@ A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on
| # | Item | Gate |
| --- | --- | --- |
-| 1 | NET-04 hosted signalling (`T-HOSTED-NETPLAY`): a Cloudflare Worker with Durable Objects for rooms, and Cloudflare TURN with short-lived credentials the Worker mints. The room protocol is ported from the existing `signaling_server` | A live two-browser session through the hosted lobby; a relayed session across two NATs; credentials expire. NES rollback traffic is about 22 MB per player-hour (an inference), well inside the free 1,000 GB a month |
+| 1 | NET-04 hosted signalling (`T-HOSTED-NETPLAY`): a Cloudflare Worker with Durable Objects for rooms, and Cloudflare TURN with short-lived credentials the Worker mints. The room protocol is ported from the existing `signaling_server` | A live two-browser session through the hosted lobby; a relayed session across two NATs; credentials expire. NES rollback traffic is about 22 MB per player-hour (an inference), so the free 1,000 GB a month (shared with Cloudflare Realtime SFU) covers roughly 45,000 relayed player-hours; only sessions that fall back to TURN use it. No usage forecast exists, so the gate records the first month's measured traffic against that figure |
| 2 | RA-01, the browser RetroAchievements proxy (`T-RA-PROXY`), on the same account; `RA_PROXY_BASE` set | A live browser login and unlock with a real account (RA-02) |
-| 3 | A privacy policy, which both RA's compliance page and F-Droid's `NonFreeNet` anti-feature need | Published, and linked from the README and the apps |
+| 3 | A privacy policy, which RetroAchievements' compliance requirements call for. Separately, F-Droid's `NonFreeNet` anti-feature (it marks dependence on a non-free network service and does not itself require a policy) is declared on the F-Droid build if the RA integration meets that definition | The policy published, and linked from the README and the apps; the F-Droid anti-feature decision recorded with its reason |
| 4 | RA hardcore compliance (`T-RA-HARDCORE`): Lua and TAS playback blocked in hardcore (as well as cheats, rewind, slowdown, frame advance, state loading, memory editors and the debugger); offline unlocks queued; the User-Agent format checked against RA's `Name/v1.0.0 (OS) core/v0.5.0`; `docs/ra-integration-request.md` refreshed (it still says v1.8.8 and MIT/Apache) | An audit of `session_policy.rs` against the full block list, each block pinned by a test; then the application sent (the process is unverified: the forum post returned 403) |
| 5 | NET-01, native 3-4 player netplay over the existing `mesh_net.rs` (`T-NETPLAY-MESH-NATIVE`); MOB-09 for mobile follows | A 4-peer loopback session stays in sync over N frames; the desktop UI reaches it |
diff --git a/to-dos/plans/v3.9.0-plan.md b/to-dos/plans/v3.9.0-plan.md
index 332229864..f041ebffe 100644
--- a/to-dos/plans/v3.9.0-plan.md
+++ b/to-dos/plans/v3.9.0-plan.md
@@ -35,8 +35,8 @@ and without monetisation, ADR 0035).
| # | Item | Gate |
| --- | --- | --- |
-| 5 | **Android identity.** Developer verification applies to sideloads too: enforced in four countries from 2026-09-30, and global from 2027. The limited-distribution account covers 20 devices; wider distribution needs registration with a government ID. Which path is the maintainer's decision, taken at this release | The chosen path recorded in `docs/android.md`; a signed APK installs on a certified device without the "advanced flow" |
-| 6 | **iOS signing (MOB-02).** No signing secrets were visible at drafting, so TestFlight uploads may never have run. Provision them; rebuild with the then-required Xcode (27 from about April 2027, an inference) | A TestFlight build uploaded by CI; the 90-day expiry noted in the release process |
+| 5 | **Android identity.** Developer verification began on 2026-09-30 in Brazil, Indonesia, Singapore and Thailand, for apps distributed through the participating stores (Google Play, Samsung Galaxy Store, Xiaomi GetApps and others), and extends to certified devices worldwide in 2027. An unregistered app can still be installed with ADB or Android's new "advanced flow". The account paths differ: an individual developer gives a government photo ID and proof of address, an organisation gives organisation documents, and a limited-distribution account covers up to 20 devices without a government ID. Which path is the maintainer's decision, still open and taken at this release | The chosen path recorded in `docs/android.md`; a signed APK installs on a certified device without the "advanced flow" |
+| 6 | **iOS signing (MOB-02).** No signing secrets were visible at drafting, so TestFlight uploads may never have run. Provision them. From April 2027, App Store Connect accepts iPhone and iPad uploads only if built with the iOS / iPadOS 27 SDK or later (Apple, announced 2026-09-09); Xcode 27, released 2026-09-14, is the tool that provides it | A TestFlight build uploaded by CI; the 90-day expiry noted in the release process |
## The hardware release (the last v3.9.x): scope and gates
diff --git a/to-dos/plans/v4.0.0-plan.md b/to-dos/plans/v4.0.0-plan.md
index 463204acf..1738c1280 100644
--- a/to-dos/plans/v4.0.0-plan.md
+++ b/to-dos/plans/v4.0.0-plan.md
@@ -29,7 +29,7 @@ Format breaks no longer need one.
| # | Item | Gate |
| --- | --- | --- |
-| 1 | `T-API-ENUMS`: every remaining public enum in `rustynes-core` and its re-exports `#[non_exhaustive]`, from v3.9.x's trial branch | Every crate, feature combination and wasm build compiles; rustdoc `-D warnings`; the no_std build; a CHANGELOG migration note listing the enums |
+| 1 | `T-API-ENUMS`: every remaining public enum in `rustynes-core` and its re-exports, and in `rustynes-netplay` (which `rustynes-core` does not re-export; `NetMessage` among them), `#[non_exhaustive]`, from v3.9.x's trial branch | Every crate, feature combination and wasm build compiles; rustdoc `-D warnings`; the no_std build; a CHANGELOG migration note listing the enums |
| 2 | Release notes restating every break since v3.0.0: the format bumps of v3.1.0, v3.3.0 and v3.5.0, every epoch rise, the enums | Built from the CHANGELOG's entries and every `!` commit and `BREAKING CHANGE:` footer since v3.0.0, so none is missed |
| 3 | MiSTer parity: save states, cheats, PAL/Dendy, FDS with expansion audio, the Zapper, Four Score, and mapper families covering the incumbent's licensed-library list | Each feature gated in the ladder; the parity table in `submission-case.md` with the re-measured incumbent number |
| 4 | Bitstreams: both builds (off-die the headline since v3.3.0, D4). If the RTL is unchanged since the hardware release, ship the pair the board verified, byte for byte; a recompile at a new date is a different bitstream from the one tested. If the RTL changed, sweep at the release date and say the result is unverified on the board | Two clean compiles of each byte-identical (or the verified pair's md5s); md5s verified by download from both releases |