diff --git a/.github/release-notes/v2.2.4.md b/.github/release-notes/v2.2.4.md
new file mode 100644
index 00000000..29ad7ead
--- /dev/null
+++ b/.github/release-notes/v2.2.4.md
@@ -0,0 +1,63 @@
+# RustyNES v2.2.4 — "Cartridge" (libretro core builds/installs for RetroArch)
+
+A **libretro / RetroArch distribution** cut. Its single purpose is that the RustyNES core **builds and installs cleanly through the Libretro buildbot** ([git.libretro.com/libretro/RustyNES](https://git.libretro.com/libretro/RustyNES)), so RetroArch users can pull it straight from the in-app **Online Updater → Core Downloader**.
+
+A *cartridge* is the thing you slot in to play. That is what this release is about: getting the RustyNES core packaged and advertised correctly so it drops into RetroArch and runs.
+
+**Zero emulation-core changes.** The deterministic `#![no_std]` chip stack, the save-state / TAS / netplay formats, and every golden vector are **byte-identical to v2.2.3**, so **AccuracyCoin holds 141/141 (100.00%)** and nestest is 0-diff *by construction*.
+
+---
+
+## The libretro core is complete, current, and builds for the buildbot
+
+`crates/rustynes-libretro` is a thin wrapper over `rustynes-core`, so it inherits every recent change automatically — it needed no code change to carry the v2.2.3 work:
+
+- **The fast PPU dot path** is the core default now, so the libretro core gets the ~11% rendering-heavy win for free (it constructs `Nes` with the current defaults).
+- **The `PPU_SNAPSHOT_VERSION` 8 / APU v4 save-state schema** is transparent to the wrapper: `get_serialize_size` and `on_serialize` size and emit the *current* snapshot via `Nes::snapshot_core_into` / `VsDualSystem::snapshot`, not a hardcoded layout. RetroArch save states, run-ahead, and rollback netplay therefore carry the v8 sprite-evaluation FSM + OAM data-bus state with no wrapper change — and because libretro save states are session-local, the ADR-0028 epoch never enters the picture.
+- **The `Mapper::mix_audio` i32 widening**, the Zapper model, and the `mNNN_` mapper rename are all below the crate's public dependency surface.
+
+Both buildbot cross-ABIs the GitHub early-warning gate models build clean, verified with the exact CI command:
+
+```text
+cargo check --release -p rustynes-libretro --target x86_64-pc-windows-gnu Finished
+cargo check --release -p rustynes-libretro --target aarch64-linux-android Finished
+```
+
+(The full ten-job buildbot recipe went green in v2.2.2; the Apple ABIs are excluded from the Linux-runner gate because bindgen needs a real per-target Apple sysroot.)
+
+## `rustynes_libretro.info` metadata corrected
+
+`rustynes_libretro.info` is the file RetroArch's core downloader reads to learn a core's capabilities. Three entries were stale or wrong:
+
+- **`disk_control` `false` → `true` — the real fix.** The FDS multi-side **Disk Control interface** (`enable_disk_control_interface()` plus the `on_set_eject_state` / `on_get_image_index` / `on_get_num_images` / `on_replace_image_index` callback trampolines) has been wired since the buildbot recipe landed — but the `.info` advertised it as absent, so RetroArch's **Quick Menu → Disk Control** never surfaced multi-disk FDS swapping. Now a Famicom Disk System game with more than one disk side is swappable from the RetroArch UI as intended.
+- **`display_version` `v1.0.0` → `v2.2.4`** — stale since the v1.0.0 era (the runtime `library_version` the core reports has always tracked `CARGO_PKG_VERSION`; this is the static metadata string RetroArch shows).
+- **Description mapper count `168` → `172`**, with a note that FDS multi-disk swapping runs through the Disk Control interface.
+
+## Documented follow-up: libretro core options
+
+Libretro **core options** (region NTSC/PAL/Dendy, overscan crop, palette selection, the opt-in accuracy toggles) remain unexposed. `core_options = "false"` in the `.info` is **accurate, not stale** — the `CoreOptions` impl is deliberately empty. This is a genuine future enhancement, not a v2.2.4 gap: it is a new capability that deserves its own focused work and testing (each option wired to the core config API and verified against the determinism / save-state contract), rather than being rushed into a distribution cut.
+
+## Tooling: the Antigravity reviewer standardized onto the shared template
+
+`scripts/agy-review.sh` and `.github/workflows/antigravity-review.yml` are now byte-identical to the canonical `antigravity-pr-review` template installed across RustyNES / RustySNES / RustyN64, so future reviewer improvements apply uniformly. It carries the large-diff-to-file handling, the 20,000-line `gh pr diff` API-limit local-`git diff` fallback (fetches PR objects but never checks them out), the `isCrossRepository` fork gate, fail-closed metadata, a default-branch checkout with `persist-credentials: false`, and the `synchronize` auto-re-review trigger.
+
+---
+
+## Upgrade notes
+
+- **Nothing breaks.** There are no core, save-state, movie, or netplay changes. `.rns` save states and `.rnm` movies from v2.2.3 load and play identically.
+- **RetroArch users:** once the buildbot mirrors this tag, the RustyNES core appears in **Online Updater → Core Downloader**; FDS multi-disk games gain working Disk Control.
+- No API changes; `Mapper::mix_audio` stays `i32` (unchanged from v2.2.3).
+
+## Verification
+
+```text
+cargo check --release -p rustynes-libretro clean
+cargo check --release -p rustynes-libretro --target x86_64-pc-windows-gnu clean
+cargo check --release -p rustynes-libretro --target aarch64-linux-android clean
+cargo test --workspace --features test-roms AccuracyCoin 141/141, nestest 0-diff
+cargo fmt --all --check · clippy --workspace --all-targets -D warnings · retroachievements combo clean
+no_std build (thumbv7em-none-eabihf) · markdownlint · actionlint clean
+```
+
+Mobile versions stay frozen at their v2.0.x host-only release points; the joint store launch remains v2.3.0.
diff --git a/AGENTS.md b/AGENTS.md
index 9d8392a0..cb2066ca 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -27,7 +27,7 @@
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: v2.2.3 "Datum"** (2026-07-23) — a **performance and accuracy-closure patch**, the product of a measure-first appraisal that profiled the emulator and acted on what the profile showed rather than on intuition. **Performance:** the specialized PPU fast dot path is promoted to the **default** and exposed to users for the first time — `Nes::set_fast_dotloop` had **no caller outside the core**, so a **−11.3%** frame-time win (fresh clean-host Criterion, reproducing v2.1.8's +12.3% by a different method; differential-tested bit-identical every frame since v2.1.8) shipped switched off and unreachable; release builds now ship **PGO-optimized** Linux binaries when the existing >3%-and-byte-identical gate passes; and CI gained a same-runner **relative** frame-time regression gate, closing a hole where a 2.5x slowdown passed the deliberately-loose absolute ceiling. **Two optimizations were measured and REJECTED** and are documented with their numbers per `docs/performance.md`'s convention — P3 (`emit_pixel` bounds-check elision) made the shipped default *slower* (+4.32% / +3.35% on the `_fast` workloads, p ≤ 0.02), and P4 (`cpu_clock`) found both textbook optimizations already implemented with the one remaining lever capped at **≤1.9%**. **Accuracy:** the **last two Holy Mapperel residuals are closed**, so all 17 ROMs report `detail=0000` (was 15/17) — MMC1's two software WRAM write-protect layers (`$E000` bit 4 + SNROM's CHR-register layer, gated on `chr_is_ram`) and FME-7's open bus on the RAM-selected-but-disabled window, both routed through the trait's existing `cpu_read_unmapped` contract. MMC1 is the change Holy Mapperel's README calls a game-compatibility hazard (FCEUX / PowerPak omit it), so it was validated before landing: **60/60** commercial ROMs including seven battery-backed MMC1 saves, plus **138/138** extended. The **Sunsoft 5B absolute level** is calibrated against Mesen2, which required widening `Mapper::mix_audio` to `i32` (the correct full-scale 5B tone `1882 * 18.471 = 34,761` does not fit `i16`). A **save-state schema gap** is fixed — `PPU_SNAPSHOT_VERSION` **8** carrying the sprite-eval FSM + OAM data-bus state, plus an APU **v4** tail — which is what made AccuracyCoin report **141/141 through run-ahead** as well as without it; a new standing field-vs-schema audit found it and the two APU gaps mechanically. A **Zapper beam-relative light model** lands opt-in / default-off (no pass-fail light-gun ROM exists to adjudicate it). **AccuracyCoin holds 141/141 (100.00%)**, nestest 0-diff. Also: the eleven `sprintN.rs` mapper modules (27,631 lines, ~110 boards) are renamed for the boards they emulate with `mNNN_` mapper-number prefixes, proven content-preserving by a byte-for-byte item comparison (930 items, 0 altered) and an identical 172-ID dispatch table.
+**Current release: v2.2.4 "Cartridge"** (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%)** and nestest is 0-diff by construction. `crates/rustynes-libretro` wraps `rustynes-core`, so it inherits every v2.2.3 change automatically (the fast-dot-path default; the `PPU_SNAPSHOT_VERSION` 8 / APU v4 save-state schema, transparent because `get_serialize_size` / `on_serialize` size and emit the *current* snapshot via `Nes::snapshot_core_into` rather than a fixed layout; the `Mapper::mix_audio` i32 widening; the Zapper model; the `mNNN_` mapper rename), and both buildbot cross-ABIs the CI early-warning gate models — `x86_64-pc-windows-gnu` and `aarch64-linux-android` — `cargo check --release -p rustynes-libretro` clean. The concrete change is a **`rustynes_libretro.info` metadata correction**: **`disk_control` `false` → `true`** (the real fix — the FDS multi-side Disk Control interface has been wired since the buildbot recipe landed but was advertised as absent, hiding multi-disk FDS swapping from RetroArch's Quick Menu), `display_version` `v1.0.0` → `v2.2.4`, and the description mapper count `168` → `172`. Libretro **core options** (region / overscan / palette / accuracy toggles) remain unexposed — `core_options = "false"` is accurate, a documented future enhancement rather than a v2.2.4 gap. The Antigravity PR reviewer standardization onto the shared template rides along. On top of **v2.2.3 "Datum"** (2026-07-23) — a **performance and accuracy-closure patch**, the product of a measure-first appraisal that profiled the emulator and acted on what the profile showed rather than on intuition. **Performance:** the specialized PPU fast dot path is promoted to the **default** and exposed to users for the first time — `Nes::set_fast_dotloop` had **no caller outside the core**, so a **−11.3%** frame-time win (fresh clean-host Criterion, reproducing v2.1.8's +12.3% by a different method; differential-tested bit-identical every frame since v2.1.8) shipped switched off and unreachable; release builds now ship **PGO-optimized** Linux binaries when the existing >3%-and-byte-identical gate passes; and CI gained a same-runner **relative** frame-time regression gate, closing a hole where a 2.5x slowdown passed the deliberately-loose absolute ceiling. **Two optimizations were measured and REJECTED** and are documented with their numbers per `docs/performance.md`'s convention — P3 (`emit_pixel` bounds-check elision) made the shipped default *slower* (+4.32% / +3.35% on the `_fast` workloads, p ≤ 0.02), and P4 (`cpu_clock`) found both textbook optimizations already implemented with the one remaining lever capped at **≤1.9%**. **Accuracy:** the **last two Holy Mapperel residuals are closed**, so all 17 ROMs report `detail=0000` (was 15/17) — MMC1's two software WRAM write-protect layers (`$E000` bit 4 + SNROM's CHR-register layer, gated on `chr_is_ram`) and FME-7's open bus on the RAM-selected-but-disabled window, both routed through the trait's existing `cpu_read_unmapped` contract. MMC1 is the change Holy Mapperel's README calls a game-compatibility hazard (FCEUX / PowerPak omit it), so it was validated before landing: **60/60** commercial ROMs including seven battery-backed MMC1 saves, plus **138/138** extended. The **Sunsoft 5B absolute level** is calibrated against Mesen2, which required widening `Mapper::mix_audio` to `i32` (the correct full-scale 5B tone `1882 * 18.471 = 34,761` does not fit `i16`). A **save-state schema gap** is fixed — `PPU_SNAPSHOT_VERSION` **8** carrying the sprite-eval FSM + OAM data-bus state, plus an APU **v4** tail — which is what made AccuracyCoin report **141/141 through run-ahead** as well as without it; a new standing field-vs-schema audit found it and the two APU gaps mechanically. A **Zapper beam-relative light model** lands opt-in / default-off (no pass-fail light-gun ROM exists to adjudicate it). **AccuracyCoin holds 141/141 (100.00%)**, nestest 0-diff. Also: the eleven `sprintN.rs` mapper modules (27,631 lines, ~110 boards) are renamed for the boards they emulate with `mNNN_` mapper-number prefixes, proven content-preserving by a byte-for-byte item comparison (930 items, 0 altered) and an identical 172-ID dispatch table.
The prior release, **v2.2.2 "Conduit"** (2026-07-21), was a **build, distribution, and CI-integrity patch**: the **libretro buildbot recipe from 1 of 10 jobs green to all ten building** (the last step before RustyNES lands in RetroArch's built-in core downloader), a **GitHub Actions supply-chain hardening** pass (`persist-credentials: false` on all 19 checkouts, a fail-closed release-tag check via `git/matching-refs`, `dtolnay/rust-toolchain` SHA-pinned off `@master`), and the toolchain **collapsed to one pinned source of truth** — no toolchain version literal anywhere under `.github/` and **no `nightly` on any build path**. **Zero emulation-core changes**, so AccuracyCoin held 141/141 by construction. Its one behavioural improvement in a shipped artifact: the libretro **tvOS** core built with `panic = "abort"` like every other platform.
@@ -43,7 +43,7 @@ The prior release, **v2.2.0 "Capstone"** (2026-07-12), was the **milestone cut**
- **Mapper breadth → 172 families** (up from 168 at the v1.7.x tag), Core / Curated / BestEffort behind the CI accuracy-honesty gate.
- **Release automation** — `.github/workflows/release-auto.yml`: when a new version goes final-green on `main`, it auto-tags + publishes the GitHub Release (body from a maintainer-authored `.github/release-notes/vX.Y.Z.md` override, else the CHANGELOG `[X.Y.Z]` section; title codename parsed from the CHANGELOG header) and builds + attaches the desktop binaries by invoking `release.yml` via `workflow_call` (a tag pushed by `GITHUB_TOKEN` can't trigger `on: push: tags`, hence the direct call). The v1.8.0–v1.9.9 GitHub Releases are all published with comprehensive notes + Linux / macOS-aarch64 / Windows binaries.
-Platform additions through v1.10.0 were **host-only and additive**: the deterministic `#![no_std]` chip stack was untouched and byte-identical on ARM. **v2.0.0 "Timebase" is different by design** — it rewrites the scheduler substrate itself (still `#![no_std]`-clean, AccuracyCoin now back at a full **141/141 (100%)** from v2.0.3 — see above, but the save-state / movie format epochs deliberately bump per ADR 0028, so cross-version `.rns`/`.rnm` round-trip is a v1.x-only guarantee, not a v1.x⇄v2.x one). Forward path: the **v2.0.x "Harbor" mobile-finalization re-port train** onto the v2.0.0 core has fully shipped — v2.0.1 (first Android re-port + AccuracyCoin oracle re-sync), v2.0.2–v2.0.3 (the 2-cycle-ALE accuracy closure to 141/141), v2.0.4 (Android release candidate), v2.0.5–v2.0.8 (iOS finalization), and v2.0.9 (both-apps readiness) — followed by the **v2.1.x "Fathom" accuracy line** (v2.1.0 → v2.1.10) capped by the **v2.2.0 "Capstone"** milestone cut, then the v2.2.1 housekeeping patch, **v2.2.2 "Conduit"**, and **v2.2.3 "Datum"**, the current release; see the "Current release" paragraph above. The **v2.1.5 → v2.2.0** line is a **"deepen the existing project"** run (accuracy / performance / features / quality); **v2.1.5 "Vernier"** opened it (the tepples Holy Mapperel mapper bank-reachability / IRQ regression net, the first PAL-region APU oracle at `pal_apu_tests` 10/10, the MMC3 R1/R2 F5.0 A12-phase study, a measured fat-LTO A/B, and a real TURN NAT-traversal retransmit production fix — all NTSC-byte-identical), **v2.1.6 "Timbre"** continued it (the expansion-audio decibel oracle, the hardware/Mesen2 channel-level calibration incl. the Namco 163 ~12 dB fix, VRC7 patch-set verification vs Nuke.YKT, and a frontend Audio Mixer panel — base 2A03 NTSC output byte-identical), **v2.1.7 "Stepping"** added opt-in PPU / 2A03 die-revisions + power-on RAM/palette hardware models (the DMA "unexpected read" frontier proven a documented no-op on every oracle, ADR 0033 — honest, not faked), **v2.1.8 "Tempo"** the default-OFF specialized fast PPU dot path (~12% rendering-heavy, differential-tested bit-identical) + a SIMD-validated software blitter + a wasm size pass, **v2.1.9 "Aperture"** the marquee CRT shader stack + a raw NTSC composite signal-decode path + GIF/WAV capture + a palette editor, **v2.1.10 "Loom"** the TAStudio greenzone + Lua API breadth + the browser-RA auth-proxy deploy stack + Vs. `DualSystem` libretro presentation, and **v2.2.0 "Capstone"** the milestone cut closing the run (the netplay matchmaking / lobby stack + the FDS medium model + a peripherals & quality/security pass — fuzz targets 3 → 8, a `Movie::deserialize` OOM-DoS fix, a read-only Tools → ROM Info browser) — all NTSC-byte-identical, AccuracyCoin 141/141 throughout; the v2.1.5 → v2.2.0 run is now closed. The **joint Google Play + Apple App Store + AltStore PAL + F-Droid launch** — with it the `rustynes-monetization` activation — is the future **v2.3.0** (moved from the earlier v2.1.0 / v2.2.0 targets).
+Platform additions through v1.10.0 were **host-only and additive**: the deterministic `#![no_std]` chip stack was untouched and byte-identical on ARM. **v2.0.0 "Timebase" is different by design** — it rewrites the scheduler substrate itself (still `#![no_std]`-clean, AccuracyCoin now back at a full **141/141 (100%)** from v2.0.3 — see above, but the save-state / movie format epochs deliberately bump per ADR 0028, so cross-version `.rns`/`.rnm` round-trip is a v1.x-only guarantee, not a v1.x⇄v2.x one). Forward path: the **v2.0.x "Harbor" mobile-finalization re-port train** onto the v2.0.0 core has fully shipped — v2.0.1 (first Android re-port + AccuracyCoin oracle re-sync), v2.0.2–v2.0.3 (the 2-cycle-ALE accuracy closure to 141/141), v2.0.4 (Android release candidate), v2.0.5–v2.0.8 (iOS finalization), and v2.0.9 (both-apps readiness) — followed by the **v2.1.x "Fathom" accuracy line** (v2.1.0 → v2.1.10) capped by the **v2.2.0 "Capstone"** milestone cut, then the v2.2.1 housekeeping patch, **v2.2.2 "Conduit"**, **v2.2.3 "Datum"**, and **v2.2.4 "Cartridge"** (the libretro/RetroArch distribution cut), the current release; see the "Current release" paragraph above. The **v2.1.5 → v2.2.0** line is a **"deepen the existing project"** run (accuracy / performance / features / quality); **v2.1.5 "Vernier"** opened it (the tepples Holy Mapperel mapper bank-reachability / IRQ regression net, the first PAL-region APU oracle at `pal_apu_tests` 10/10, the MMC3 R1/R2 F5.0 A12-phase study, a measured fat-LTO A/B, and a real TURN NAT-traversal retransmit production fix — all NTSC-byte-identical), **v2.1.6 "Timbre"** continued it (the expansion-audio decibel oracle, the hardware/Mesen2 channel-level calibration incl. the Namco 163 ~12 dB fix, VRC7 patch-set verification vs Nuke.YKT, and a frontend Audio Mixer panel — base 2A03 NTSC output byte-identical), **v2.1.7 "Stepping"** added opt-in PPU / 2A03 die-revisions + power-on RAM/palette hardware models (the DMA "unexpected read" frontier proven a documented no-op on every oracle, ADR 0033 — honest, not faked), **v2.1.8 "Tempo"** the default-OFF specialized fast PPU dot path (~12% rendering-heavy, differential-tested bit-identical) + a SIMD-validated software blitter + a wasm size pass, **v2.1.9 "Aperture"** the marquee CRT shader stack + a raw NTSC composite signal-decode path + GIF/WAV capture + a palette editor, **v2.1.10 "Loom"** the TAStudio greenzone + Lua API breadth + the browser-RA auth-proxy deploy stack + Vs. `DualSystem` libretro presentation, and **v2.2.0 "Capstone"** the milestone cut closing the run (the netplay matchmaking / lobby stack + the FDS medium model + a peripherals & quality/security pass — fuzz targets 3 → 8, a `Movie::deserialize` OOM-DoS fix, a read-only Tools → ROM Info browser) — all NTSC-byte-identical, AccuracyCoin 141/141 throughout; the v2.1.5 → v2.2.0 run is now closed. The **joint Google Play + Apple App Store + AltStore PAL + F-Droid launch** — with it the `rustynes-monetization` activation — is the future **v2.3.0** (moved from the earlier v2.1.0 / v2.2.0 targets).
---
@@ -185,7 +185,7 @@ These cross-cutting decisions span multiple files. Reading individual chip docs
- `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 **v2.2.3 "Datum"** (2026-07-23, a performance and accuracy-closure patch — the fast PPU dot path promoted to default and exposed, PGO binaries shipped on the release path, a same-runner relative frame-time CI gate, the last two Holy Mapperel residuals closed [MMC1 WRAM write-protect + FME-7 open bus, all 17 ROMs now `detail=0000`], the Sunsoft 5B level calibrated with `Mapper::mix_audio` widened to i32, a save-state schema gap fixed at `PPU_SNAPSHOT_VERSION` 8 + an APU v4 tail, an opt-in Zapper beam-relative light model, and the eleven `sprintN.rs` mapper modules renamed to `mNNN_.rs`; two optimizations measured and REJECTED and documented as such; AccuracyCoin 141/141 — on top of **v2.2.2 "Conduit"** [2026-07-21, a build/distribution/CI-integrity patch — the libretro buildbot recipe taken from 1 of 10 jobs green to all ten building, a GitHub Actions supply-chain hardening pass, and the toolchain collapsed to one pinned source of truth with no `nightly` on any build path; zero emulation-core changes], itself on **v2.2.1** [2026-07-15, a housekeeping patch: dev-tooling archival, a zero-source-change dependency consolidation, and a gitignored FDS test-corpus addition], itself on **v2.2.0 "Capstone"** [2026-07-12], the milestone cut that closes the v2.1.5 → v2.2.0 "deepen the existing project" run — its two remaining marquees the netplay matchmaking / lobby stack and the FDS medium model, atop a peripherals + quality/security pass (Famicom `$4016`-bit-2 microphone + 3×3-aperture Zapper; cargo-fuzz targets 3 → 8 finding + fixing two `Movie::deserialize` OOM-DoS paths; a read-only Tools → ROM Info browser); every change additive or default-off, AccuracyCoin 141/141) on the v2.0.0 "Timebase" one-clock / every-cycle-bus-access scheduler rewrite + Vs. `DualSystem` dual-console support. The v2.0.x "Harbor" mobile-finalization train (v2.0.1→v2.0.9) and the entire v2.1.x "Fathom" line (v2.1.0→v2.1.10) plus the v2.2.0 "Capstone" milestone have all shipped — the run's steps being v2.1.5 "Vernier" (regression-net & residual) → v2.1.6 "Timbre" (expansion-audio fidelity) → v2.1.7 "Stepping" (opt-in PPU/2A03 die-revisions + power-on RAM/palette models; the DMA "unexpected read" frontier a documented no-op on every oracle, ADR 0033) → v2.1.8 "Tempo" (a default-OFF fast PPU dot path + SIMD blitter + wasm size pass) → v2.1.9 "Aperture" (a marquee CRT shader stack + raw NTSC composite signal-decode + GIF/WAV capture + palette editor) → v2.1.10 "Loom" (TAStudio greenzone + Lua API breadth + browser-RA auth-proxy deploy stack + Vs. `DualSystem` libretro presentation) → v2.2.0 "Capstone" (the milestone cut closing the run) → v2.2.1 (housekeeping) → **v2.2.2 "Conduit"** the build/distribution/CI-integrity patch (the prior release) — preceded by v1.10.0 "Arcade" the native Libretro / RetroArch core, the v1.9.0→v1.9.9 iOS TestFlight train, the v1.8.0→v1.8.9 "Android" train, and the desktop-feature lineage v1.1.0→v1.7.1, all on the v1.0.0 production core (see the top "Current release" block + `docs/STATUS.md`). **Never claim any version *later* than v2.2.3 is released** — in particular the joint mobile app-store launch (Google Play + Apple App Store + AltStore PAL + F-Droid) is the future **v2.3.0** (NOT v2.1.0 or v2.2.0 — the entire v2.1.x line and the v2.2.0 "Capstone" milestone have all already shipped, closing the "deepen the existing project" run; the store launch moved out to v2.3.0 — see `to-dos/ROADMAP.md`). Two distinct "v2.0"s exist and must not be conflated, **both now shipped, at different times, for different reasons**: the **engine-lineage v2.0** master-clock work shipped as the **v1.0.0** production core (2026-06-13) — it was the *only* scheduler through v1.10.0. RustyNES's own **v2.0.0 "Timebase"** release (2026-07-03) is a *different* milestone that *replaces* that same dot-lockstep scheduler outright: the **one-clock + every-cycle-bus-access collapse** (a single canonical cycle counter + a split-around-the-access `start_cycle`/`end_cycle` PPU catch-up, mirroring Mesen2's structure), full Vs. `DualSystem` dual-console emulation (core-and-harness-only; frontend wiring deferred), and the breaking save-state / cross-version changes it entailed (ADR 0002 / ADR 0028 / ADR 0029) — the one release that broke byte-identity / save-state compatibility, by design. The R1/R2 hard-tier MMC3 IRQ-timing residual was investigated under a bounded-effort campaign and is by-design-deferred beyond v2.0.0, not closed — see ADR 0002's decision-update section for the mechanism-level finding.
+- 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 **v2.2.4 "Cartridge"** (2026-07-24, a libretro/RetroArch distribution cut — zero emulation-core changes so AccuracyCoin holds 141/141 by construction; the libretro core is confirmed up-to-date with all recent changes and builds for the buildbot ABIs [`x86_64-pc-windows-gnu`, `aarch64-linux-android`], and `rustynes_libretro.info` is corrected: `disk_control` false→true [the FDS Disk Control interface was wired but advertised absent], `display_version` v1.0.0→v2.2.4, mapper count 168→172; core options remain a documented future enhancement; the Antigravity reviewer standardization rides along), on top of **v2.2.3 "Datum"** (2026-07-23, a performance and accuracy-closure patch — the fast PPU dot path promoted to default and exposed, PGO binaries shipped on the release path, a same-runner relative frame-time CI gate, the last two Holy Mapperel residuals closed [MMC1 WRAM write-protect + FME-7 open bus, all 17 ROMs now `detail=0000`], the Sunsoft 5B level calibrated with `Mapper::mix_audio` widened to i32, a save-state schema gap fixed at `PPU_SNAPSHOT_VERSION` 8 + an APU v4 tail, an opt-in Zapper beam-relative light model, and the eleven `sprintN.rs` mapper modules renamed to `mNNN_.rs`; two optimizations measured and REJECTED and documented as such; AccuracyCoin 141/141 — on top of **v2.2.2 "Conduit"** [2026-07-21, a build/distribution/CI-integrity patch — the libretro buildbot recipe taken from 1 of 10 jobs green to all ten building, a GitHub Actions supply-chain hardening pass, and the toolchain collapsed to one pinned source of truth with no `nightly` on any build path; zero emulation-core changes], itself on **v2.2.1** [2026-07-15, a housekeeping patch: dev-tooling archival, a zero-source-change dependency consolidation, and a gitignored FDS test-corpus addition], itself on **v2.2.0 "Capstone"** [2026-07-12], the milestone cut that closes the v2.1.5 → v2.2.0 "deepen the existing project" run — its two remaining marquees the netplay matchmaking / lobby stack and the FDS medium model, atop a peripherals + quality/security pass (Famicom `$4016`-bit-2 microphone + 3×3-aperture Zapper; cargo-fuzz targets 3 → 8 finding + fixing two `Movie::deserialize` OOM-DoS paths; a read-only Tools → ROM Info browser); every change additive or default-off, AccuracyCoin 141/141) on the v2.0.0 "Timebase" one-clock / every-cycle-bus-access scheduler rewrite + Vs. `DualSystem` dual-console support. The v2.0.x "Harbor" mobile-finalization train (v2.0.1→v2.0.9) and the entire v2.1.x "Fathom" line (v2.1.0→v2.1.10) plus the v2.2.0 "Capstone" milestone have all shipped — the run's steps being v2.1.5 "Vernier" (regression-net & residual) → v2.1.6 "Timbre" (expansion-audio fidelity) → v2.1.7 "Stepping" (opt-in PPU/2A03 die-revisions + power-on RAM/palette models; the DMA "unexpected read" frontier a documented no-op on every oracle, ADR 0033) → v2.1.8 "Tempo" (a default-OFF fast PPU dot path + SIMD blitter + wasm size pass) → v2.1.9 "Aperture" (a marquee CRT shader stack + raw NTSC composite signal-decode + GIF/WAV capture + palette editor) → v2.1.10 "Loom" (TAStudio greenzone + Lua API breadth + browser-RA auth-proxy deploy stack + Vs. `DualSystem` libretro presentation) → v2.2.0 "Capstone" (the milestone cut closing the run) → v2.2.1 (housekeeping) → **v2.2.2 "Conduit"** the build/distribution/CI-integrity patch (the prior release) — preceded by v1.10.0 "Arcade" the native Libretro / RetroArch core, the v1.9.0→v1.9.9 iOS TestFlight train, the v1.8.0→v1.8.9 "Android" train, and the desktop-feature lineage v1.1.0→v1.7.1, all on the v1.0.0 production core (see the top "Current release" block + `docs/STATUS.md`). **Never claim any version *later* than v2.2.4 is released** — in particular the joint mobile app-store launch (Google Play + Apple App Store + AltStore PAL + F-Droid) is the future **v2.3.0** (NOT v2.1.0 or v2.2.0 — the entire v2.1.x line and the v2.2.0 "Capstone" milestone have all already shipped, closing the "deepen the existing project" run; the store launch moved out to v2.3.0 — see `to-dos/ROADMAP.md`). Two distinct "v2.0"s exist and must not be conflated, **both now shipped, at different times, for different reasons**: the **engine-lineage v2.0** master-clock work shipped as the **v1.0.0** production core (2026-06-13) — it was the *only* scheduler through v1.10.0. RustyNES's own **v2.0.0 "Timebase"** release (2026-07-03) is a *different* milestone that *replaces* that same dot-lockstep scheduler outright: the **one-clock + every-cycle-bus-access collapse** (a single canonical cycle counter + a split-around-the-access `start_cycle`/`end_cycle` PPU catch-up, mirroring Mesen2's structure), full Vs. `DualSystem` dual-console emulation (core-and-harness-only; frontend wiring deferred), and the breaking save-state / cross-version changes it entailed (ADR 0002 / ADR 0028 / ADR 0029) — the one release that broke byte-identity / save-state compatibility, by design. The R1/R2 hard-tier MMC3 IRQ-timing residual was investigated under a bounded-effort campaign and is by-design-deferred beyond v2.0.0, not closed — see ADR 0002's decision-update section for the mechanism-level finding.
- **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.39.0`). The local `markdownlint` binary is a newer version that reports rules v0.39.0 lacks (e.g. MD060) — those are NOT gated; verify with `pre-commit run markdownlint --all-files`, not the bare binary. `.markdownlint.json` 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 vendored `tricnes/` + upstream READMEs, and the frozen `docs/archive/` + `to-dos/archive/` trees — don't lint or reformat those.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b4b258a9..3c25205e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,79 @@ cycle-accurate core later replaced.
## [Unreleased]
+## [2.2.4] - 2026-07-24 - "Cartridge" (libretro core builds/installs for RetroArch)
+
+A **libretro / RetroArch distribution** cut. Its purpose is that the RustyNES
+core builds and installs cleanly through the Libretro buildbot
+() so RetroArch users can pull it
+from the in-app core downloader. **Zero emulation-core changes**, so
+AccuracyCoin holds **141/141 (100.00%)**, nestest is 0-diff, and the `#![no_std]`
+chip stack, save-state / TAS / netplay formats, and every golden vector are
+byte-identical to v2.2.3 by construction.
+
+### Libretro / distribution
+
+- **The libretro core is confirmed complete and up-to-date with every recent
+ change, and builds for the buildbot ABIs.** `crates/rustynes-libretro` wraps
+ `rustynes-core`, so it inherits the v2.2.3 work automatically and required no
+ code change to carry it: the fast PPU dot path (now the core default) is
+ active; the `PPU_SNAPSHOT_VERSION` 8 + APU v4 save-state schema is transparent
+ because `get_serialize_size` / `on_serialize` size and emit the *current*
+ snapshot via `Nes::snapshot_core_into` rather than a hardcoded layout; the
+ `Mapper::mix_audio` i32 widening, the Zapper model, and the `mNNN_` mapper
+ rename are all below the crate's public dependency surface. Both buildbot
+ cross-ABIs the GitHub early-warning gate models — `x86_64-pc-windows-gnu` and
+ `aarch64-linux-android` — `cargo check --release -p rustynes-libretro`
+ clean.
+- **`rustynes_libretro.info` metadata corrected** (the file RetroArch's core
+ downloader reads to learn the core's capabilities):
+ - **`disk_control` `false` → `true`** — the real fix. The FDS multi-side Disk
+ Control interface (`enable_disk_control_interface()` + the
+ `on_set_eject_state` / `on_get_image_index` / … callback trampolines) has
+ been wired since the buildbot recipe landed, but the `.info` advertised it
+ as absent, so RetroArch's Quick Menu → Disk Control never surfaced multi-disk
+ FDS swapping.
+ - `display_version` `v1.0.0` → `v2.2.4` (stale since the v1.0.0 era).
+ - Description mapper count `168` → `172`, and a note that FDS multi-disk
+ swapping runs through the Disk Control interface.
+- Documented follow-up: libretro **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.
+
+### Tooling
+
+- **The Antigravity PR reviewer is standardized onto the shared template**
+ (`scripts/agy-review.sh` + `.github/workflows/antigravity-review.yml`), the
+ same canonical version now installed across RustyNES / RustySNES / RustyN64.
+ It carries the large-diff handling (a diff too big to inline goes to `agy` as
+ a file), the 20,000-line `gh pr diff` API-limit local-`git diff` fallback, the
+ `isCrossRepository` fork gate, fail-closed metadata, default-branch checkout
+ with `persist-credentials: false`, and the `synchronize` auto-re-review
+ trigger.
+- **Reviewer security hardening (found by the reviewer itself).** The
+ Antigravity reviewer, run against this PR, flagged five security regressions
+ the standardized template had relative to RustyNES's prior version — all
+ fixed: `printf '%q '`-escaped `script(1)` fallback (was a raw `${flags[*]}` in
+ `sh -c` — command injection), an author-scoped comment-deletion filter (was
+ marker-only — arbitrary comment deletion), removal of the unscoped SQLite
+ conversation-store fallback (a shared-runner data-leak vector), stripping
+ `GH_TOKEN` / `GITHUB_TOKEN` from `agy`'s environment (`env -u`), and the
+ `issue_comment` author-association re-check restored in the script.
+- **Large-diff handoff made readable (found by the reviewer, fixed on the
+ runner).** The reviewer files a diff too large to inline into a gitignored
+ working-tree scratch dir (`.agy-review-work/`, relocated from `.git/` after the
+ reviewer flagged the hidden-dir read risk) and tells `agy` to read it — but
+ that on-disk handoff never actually worked: `agy`'s sandboxed file tool
+ resolves relative paths against its own workspace root, not the shell CWD, so
+ the file came back "does not exist" and the review was empty. Latent until a
+ diff first crossed the ~90 KB inline budget (below it the diff is inlined and
+ the file path is never exercised). Root cause and fix proven on the live `agy`
+ runner with a three-way probe under the exact review flags: the prompt now
+ hands `agy` an **absolute** path, and `--add-dir "$PWD"` adds the checkout to
+ `agy`'s sandbox workspace — but only in file-handoff mode, so an inline review
+ keeps zero filesystem access. All changes ride the canonical template so the
+ three consuming repos stay in sync.
+
## [2.2.3] - 2026-07-23 - "Datum" (fast dot path promoted + PGO shipped + the last two mapper residuals closed)
### Performance
diff --git a/Cargo.lock b/Cargo.lock
index 5b00f659..991986d9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4290,7 +4290,7 @@ dependencies = [
[[package]]
name = "rustynes-android"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"android-activity",
"android_logger",
@@ -4308,7 +4308,7 @@ dependencies = [
[[package]]
name = "rustynes-apu"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"bitflags 2.13.1",
"criterion",
@@ -4321,7 +4321,7 @@ dependencies = [
[[package]]
name = "rustynes-cheevos"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"cc",
"ureq",
@@ -4329,7 +4329,7 @@ dependencies = [
[[package]]
name = "rustynes-core"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"bitflags 2.13.1",
"criterion",
@@ -4346,7 +4346,7 @@ dependencies = [
[[package]]
name = "rustynes-cpu"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"bitflags 2.13.1",
"criterion",
@@ -4357,7 +4357,7 @@ dependencies = [
[[package]]
name = "rustynes-frontend"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"anstyle",
"arboard",
@@ -4411,11 +4411,11 @@ dependencies = [
[[package]]
name = "rustynes-gfx-shaders"
-version = "2.2.3"
+version = "2.2.4"
[[package]]
name = "rustynes-hdpack"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"lewton",
"png",
@@ -4426,7 +4426,7 @@ dependencies = [
[[package]]
name = "rustynes-ios"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"bytemuck",
"cpal",
@@ -4440,7 +4440,7 @@ dependencies = [
[[package]]
name = "rustynes-libretro"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"libc",
"rust-libretro",
@@ -4449,7 +4449,7 @@ dependencies = [
[[package]]
name = "rustynes-mappers"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"bitflags 2.13.1",
"criterion",
@@ -4461,7 +4461,7 @@ dependencies = [
[[package]]
name = "rustynes-mobile"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"rustynes-core",
"rustynes-hdpack",
@@ -4476,14 +4476,14 @@ dependencies = [
[[package]]
name = "rustynes-monetization"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"uniffi",
]
[[package]]
name = "rustynes-netplay"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"futures-util",
"js-sys",
@@ -4499,7 +4499,7 @@ dependencies = [
[[package]]
name = "rustynes-ppu"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"bitflags 2.13.1",
"criterion",
@@ -4511,14 +4511,14 @@ dependencies = [
[[package]]
name = "rustynes-ra"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"rustynes-cheevos",
]
[[package]]
name = "rustynes-script"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"mlua",
"piccolo",
@@ -4529,7 +4529,7 @@ dependencies = [
[[package]]
name = "rustynes-test-harness"
-version = "2.2.3"
+version = "2.2.4"
dependencies = [
"insta",
"png",
diff --git a/Cargo.toml b/Cargo.toml
index 7a5fc85a..d3d85516 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,7 +32,7 @@ members = [
default-members = ["crates/rustynes-libretro"]
[workspace.package]
-version = "2.2.3"
+version = "2.2.4"
edition = "2024"
rust-version = "1.96"
license = "MIT OR Apache-2.0"
diff --git a/README.md b/README.md
index c21714ab..e1e627e1 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-

+


@@ -775,35 +775,46 @@ and the Material-for-MkDocs documentation handbook at
## Current Release
-RustyNES's current release is **v2.2.3 "Datum"**, a **performance and
-accuracy-closure patch**. A measure-first appraisal profiled the emulator and
-acted on what it found rather than on intuition.
-
-**Performance.** The specialized PPU fast dot path — measured at **−11.3%**
-frame time on rendering-heavy content and differential-tested bit-identical
-every frame since v2.1.8 — is now the **default**, and reachable from the
-frontend for the first time (`Nes::set_fast_dotloop` previously had no caller
-outside the core, so the win shipped switched off and unreachable). Release
-builds now ship **PGO-optimized** Linux binaries when the >3%-and-byte-identical
-gate passes, and CI gained a same-runner relative frame-time regression gate to
-close a hole where a 2.5x slowdown could pass the old absolute ceiling.
-
-**Accuracy.** The **last two Holy Mapperel residuals are closed** — all 17 ROMs
-now report `detail=0000`. MMC1's two software WRAM write-protect layers and
-FME-7's open-bus-on-disabled-RAM window are both modelled, the former validated
-against **60/60** commercial ROMs (including seven battery-backed MMC1 saves,
-precisely the titles that corrupt if the RAM enable is wrong) plus **138/138**
-extended. The Sunsoft 5B's absolute level is calibrated against Mesen2, which
-required widening `Mapper::mix_audio` to `i32` — the correct full-scale 5B tone
-does not fit `i16`. A save-state schema gap found by a new standing audit is
-fixed (`PPU_SNAPSHOT_VERSION` 8, plus an APU v4 tail), which is what made
-AccuracyCoin report 141/141 through run-ahead as well as without it.
-
-**Two optimizations were measured and rejected**, and are documented with their
-numbers — the project records what did not clear the bar as well as what did.
-**AccuracyCoin holds 141/141 (100.00%)**, nestest 0-diff.
-
-It follows **v2.2.2 "Conduit"**, a build, distribution, and CI-integrity patch
+RustyNES's current release is **v2.2.4 "Cartridge"**, a **libretro / RetroArch
+distribution** cut. Its purpose is that the RustyNES core builds and installs
+cleanly through the Libretro buildbot
+([git.libretro.com/libretro/RustyNES](https://git.libretro.com/libretro/RustyNES))
+so RetroArch users can pull it from the in-app core downloader.
+
+**Zero emulation-core changes**, so **AccuracyCoin holds 141/141 (100.00%)**,
+nestest is 0-diff, and the `#![no_std]` chip stack, save-state / TAS / netplay
+formats, and every golden vector are byte-identical to v2.2.3 by construction.
+`crates/rustynes-libretro` wraps `rustynes-core`, so it inherits every v2.2.3
+change automatically (the fast dot path default; the `PPU_SNAPSHOT_VERSION` 8 /
+APU v4 save-state schema, handled transparently because the serialize path sizes
+and emits the *current* snapshot via `Nes::snapshot_core_into`; the
+`Mapper::mix_audio` i32 widening; the Zapper model; the `mNNN_` mapper rename),
+and both buildbot cross-ABIs the CI gate models — `x86_64-pc-windows-gnu` and
+`aarch64-linux-android` — build clean.
+
+The concrete work is a **`rustynes_libretro.info` metadata correction** (the file
+RetroArch's core downloader reads): **`disk_control` `false` → `true`** — the
+real fix, since the FDS multi-side Disk Control interface has been wired since
+the buildbot recipe landed but was advertised as absent, hiding multi-disk FDS
+swapping from RetroArch's Quick Menu; plus `display_version` `v1.0.0` → `v2.2.4`
+and the mapper count `168` → `172`. Libretro **core options** (region / overscan
+/ palette / accuracy toggles) remain a documented future enhancement
+(`core_options = "false"` is accurate, not stale). The Antigravity PR reviewer
+standardization onto the shared template rides along.
+
+It follows **v2.2.3 "Datum"**, a performance and accuracy-closure patch: the
+specialized PPU fast dot path (**−11.3%** on rendering-heavy content,
+differential-tested bit-identical since v2.1.8) promoted to the **default** and
+exposed to users for the first time; PGO-optimized Linux release binaries when
+the >3%-and-byte-identical gate passes; a same-runner relative frame-time
+regression gate; the **last two Holy Mapperel residuals closed** (all 17 ROMs
+`detail=0000` — MMC1's two WRAM write-protect layers validated against **60/60**
+commercial ROMs, FME-7's open-bus window); the Sunsoft 5B level calibrated to
+Mesen2 (widening `Mapper::mix_audio` to `i32`); and a save-state schema gap fixed
+(`PPU_SNAPSHOT_VERSION` 8 + APU v4 tail). Two optimizations were measured and
+rejected, documented with their numbers.
+
+Earlier still: **v2.2.2 "Conduit"**, a build, distribution, and CI-integrity patch
that took the libretro buildbot recipe from 1 of 10 jobs green to **all ten
building**, hardened the GitHub Actions supply chain, and collapsed the
toolchain to a single pinned source of truth with no `nightly` on any build
diff --git a/crates/rustynes-libretro/rustynes_libretro.info b/crates/rustynes-libretro/rustynes_libretro.info
index c3768bfc..58bc8f5b 100644
--- a/crates/rustynes-libretro/rustynes_libretro.info
+++ b/crates/rustynes-libretro/rustynes_libretro.info
@@ -5,7 +5,7 @@ supported_extensions = "nes|fds"
corename = "RustyNES"
license = "MIT OR Apache-2.0"
permissions = ""
-display_version = "v1.0.0"
+display_version = "v2.2.4"
categories = "Emulator"
# Hardware Information
@@ -25,7 +25,7 @@ core_options = "false"
load_subsystem = "false"
hw_render = "false"
needs_fullpath = "false"
-disk_control = "false"
+disk_control = "true"
database = "Nintendo - Nintendo Entertainment System|Nintendo - Family Computer Disk System"
# Firmware / BIOS
@@ -35,4 +35,4 @@ firmware0_path = "disksys.rom"
firmware0_opt = "true"
notes = "(!) disksys.rom (md5): ca30b50f880eb660a320674ed365ef7a|RustyNES requires this BIOS in the system directory to boot Famicom Disk System games."
-description = "RustyNES is a cycle-accurate Nintendo Entertainment System emulator written entirely in safe Rust. Targeting the higan and Mesen2 accuracy tier, it relies on a strict, lockstep master-clock scheduler at PPU-dot resolution rather than per-game hacks. The core guarantees absolute determinism, making it an exceptional choice for advanced libretro features like run-ahead latency reduction, rollback netplay, and RetroAchievements. With high compatibility spanning over 168 mapper families, full Famicom Disk System support, and true RGB Vs. System arcade boards, RustyNES is ideal for users who seek reference-grade accuracy paired with modern stability."
+description = "RustyNES is a cycle-accurate Nintendo Entertainment System emulator written entirely in safe Rust. Targeting the higan and Mesen2 accuracy tier, it relies on a strict, lockstep master-clock scheduler at PPU-dot resolution rather than per-game hacks. The core guarantees absolute determinism, making it an exceptional choice for advanced libretro features like run-ahead latency reduction, rollback netplay, and RetroAchievements. With high compatibility spanning 172 mapper families, full Famicom Disk System support (multi-disk swapping via the Disk Control interface), and true RGB Vs. System arcade boards, RustyNES is ideal for users who seek reference-grade accuracy paired with modern stability."
diff --git a/docs/STATUS.md b/docs/STATUS.md
index 87df66ad..9ed61da9 100644
--- a/docs/STATUS.md
+++ b/docs/STATUS.md
@@ -1,6 +1,32 @@
# RustyNES — Project Status Matrix
-> **Current release: v2.2.3** (2026-07-23) — **"Datum"**, a **performance and
+> **Current release: v2.2.4** (2026-07-24) — **"Cartridge"**, 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%)** and nestest is 0-diff by
+> construction. `crates/rustynes-libretro` wraps `rustynes-core` and so inherits
+> every v2.2.3 change automatically (the fast-dot-path default; the
+> `PPU_SNAPSHOT_VERSION` 8 / APU v4 save-state schema, handled transparently
+> because `get_serialize_size` / `on_serialize` size and emit the *current*
+> snapshot via `Nes::snapshot_core_into`, not a fixed layout; the
+> `Mapper::mix_audio` i32 widening; the Zapper model; the `mNNN_` mapper rename),
+> and both buildbot cross-ABIs the CI early-warning gate models —
+> `x86_64-pc-windows-gnu` and `aarch64-linux-android` — `cargo check --release -p
+> rustynes-libretro` clean. The concrete change is a **`rustynes_libretro.info`
+> metadata correction**: **`disk_control` `false` → `true`** (the real fix — the
+> FDS multi-side Disk Control interface has been wired since the buildbot recipe
+> landed, but was advertised as absent, hiding multi-disk FDS swapping from
+> RetroArch's Quick Menu), `display_version` `v1.0.0` → `v2.2.4`, and the mapper
+> count `168` → `172`. Libretro **core options** (region / overscan / palette /
+> accuracy toggles) remain unexposed — `core_options = "false"` is accurate, a
+> documented future enhancement rather than a v2.2.4 gap. The Antigravity PR
+> reviewer standardization onto the shared template rides along. On top of
+> **v2.2.3** (below):
+>
+> > **v2.2.3** (2026-07-23) — **"Datum"**, a **performance and
> accuracy-closure patch** on top of v2.2.2 (below), produced by a measure-first
> appraisal that profiled the emulator and acted on what it found.
>
diff --git a/to-dos/ROADMAP.md b/to-dos/ROADMAP.md
index 84d95725..156be199 100644
--- a/to-dos/ROADMAP.md
+++ b/to-dos/ROADMAP.md
@@ -37,16 +37,17 @@ v2.8.0 → v0.9.7; the synthesis itself = **v1.0.0**.
> AccuracyCoin to **100.00%**) is *upstream engine history* and shipped as the
> **v1.0.0 production core**. The forward **RustyNES v2.0.0 "Timebase"**
> (ADR 0002/0029) was a *different* milestone — the one-clock/every-cycle-bus-
-> access scheduler collapse — that is now **code-complete on `main`, tag
-> pending** (see "v2.0.0 'Timebase' — code-complete, tag pending" below for
-> what actually shipped, including the one known gap: the MMC3 R1/R2
+> access scheduler collapse — which **shipped as v2.0.0 on 2026-07-03** (see
+> the "v2.0.0 'Timebase' — historical landing snapshot" section below for
+> what shipped, including the one known gap: the MMC3 R1/R2
> IRQ-timing residual, by-design-deferred rather than closed). The engine's
> own `v1.x`/`v2.x` markers in the bullets and "Phases" sections remain
> historical anchors, **never** RustyNES release numbers.
## Status
-- **Current release:** **RustyNES v2.1.0 "Fathom"** (2026-07-09) — the **accuracy-remediation** release and the first of the new "Fathom" line, a **core / desktop** cut landing **ahead of** the joint mobile store launch (**moved from v2.1.0 to v2.2.0**, so the Android + iOS apps re-release on this improved core). The deterministic core is unchanged but for one **display-only** PPU fix, so **AccuracyCoin holds 141/141 (100.00%)**, nestest 0-diff, `#![no_std]` untouched, no save-state bump. Lands: **(F1.1)** the **PPU palette backdrop-override** (rendering-disabled + `v` in `$3F00-$3FFF` → `palette[v & 0x1F]`, **byte-exact with TriCNES**; 9 snapshots re-blessed — 2 palette demos + 7 commercial games — all converging with the oracle, `external_real_games` 60/60 byte-identical); **(F1.2/F1.3)** OAM + open-bus audits regression-locked; **(F3)** the **mapper completion** — **86** families promoted BestEffort → Curated with commercial-ROM oracle evidence (57 staged + 29 GoodNES v3.23b), so the tier is **51 Core + 95 Curated + 26 BestEffort** and oracle-gated coverage rises **60 → 146** of 172 (the 26 left have no cleanly-booting dump — 16 NES 2.0 high-id + 8 no-cart + 2 jam-at-boot); **(F5)** the **MMC3 R1/R2 scanline-IRQ residual CLOSED** by-design-permanent (ADR 0002 F5.0 — differential 1-dot deficit, structurally unreachable, zero game impact), with all **20** `#[ignore]`'d tests catalogued in the new `docs/accuracy-ledger.md`; and **(F0)** doc reconciliation (MMC5 + DualSystem stale docs). Version bump (workspace `2.0.8 → 2.1.0`; mobile `MARKETING_VERSION`s unchanged — apps re-release at v2.2.0). See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[2.1.0]` + `docs/accuracy-ledger.md` + `to-dos/plans/v2.1.0-fathom-accuracy-remediation-plan.md`.
+- **Current release:** **RustyNES 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** (this libretro/RetroArch cut). Each is byte-identical NTSC (AccuracyCoin 141/141 throughout). **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.
- **Preceding release:** **RustyNES v2.0.8 "Harbor"** (2026-07-09) — 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`.
@@ -55,7 +56,7 @@ v2.8.0 → v0.9.7; the synthesis itself = **v1.0.0**.
- **Earlier in the train:** **RustyNES v2.0.3 "Harbor" ("Keel")** (2026-07-08) — the third release of the **v2.0.x mobile-finalization train** and the one that makes the octal-latch accuracy work real at the shipped default. The **2-cycle-ALE PPU fetch model is promoted from the experimental `mc-ppu-2cycle-ale` flag to the unconditional, only PPU fetch path** (ADR 0030), so the shipped default now scores **AccuracyCoin 141/141 (100.00%, RAM-authoritative)** — both **"ALE + Read"** (`$0491`) and **"Hybrid Addresses"** (`$0492`) pass out of the box (previously an honest 139/141). This is the genuine two-dot fetch (even-dot ALE-drive + `octal_latch` load; odd-dot `(address & 0x3F00) | octal_latch` splice + read) where the latch *naturally* carries the stale byte (`copy_v_delay = 4` → NT splice `$2F19` for Hybrid; `$2007`-ALE overlap freeze → `$0FFF` for ALE+Read), replacing v2.0.2's whole-dot `+1 coarse-X` stand-in. **Both experiment flags retired** (`mc-ppu-2cycle-ale` + `mc-ppu-bus-addr-hybrid`); stand-in code deleted; `octal_trace` survives behind the new default-off `ppu-octal-trace`. Verified: **60-ROM oracle 60/60** with two documented re-blesses (SMB3, Uchuu Keibitai SDF — single-tile `$2006`-during-render shifts, more TriCNES-faithful, audio/cycle byte-identical), nestest 0-diff, mmc3 18/18, `ppu_sprites` 19/19; ~10% headless frame-cost rise (~4.15 ms/frame). **Save-state:** additive **`PPU_SNAPSHOT_VERSION` 4 → 5** tail (netplay-rollback determinism; pre-v5 `.rns` still load; forward-incompatible with ≤v2.0.2 but not an ADR-0028 epoch break). Also: the **Harbor Android foss/play monetization glue** (step 5 — AppLovin MAX + RevenueCat 8.10.0 `MonetizationGate`, gating/paywall/session/progress; no-op `foss` twin; both flavors assemble, dormant pending v2.0.9 on-device verify) + a **host-localizable mobile bridge-warning** API (`HostWarning` enum + `drain_warning_codes()`). See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[2.0.3]` + `to-dos/plans/v2.0.3-2cycle-ale-plan.md`.
- **Earlier in the train:** **RustyNES v2.0.2 "Harbor" ("Soundings")** (2026-07-08) — the second release of the **v2.0.x mobile-finalization train** and Harbor's **headline accuracy release**: the two new upstream AccuracyCoin PPU tests v2.0.1 documented as honest gaps — **"ALE + Read"** (`$0491`) and **"Hybrid Addresses"** (`$0492`) — are now **solved flag-on** by a whole-dot port of TriCNES's **octal-latch multiplexed-bus PPU model** (ADR 0030, commit `27c103c`), behind the pre-existing default-off `mc-ppu-bus-addr-hybrid` flag. **Shipped default stays honest 139/141 (98.58%), byte-identical to v2.0.1; flag-on the same build is verified 141/141 (100.00%)** (framebuffer 100%, nestest 0-diff, mmc3 A12 + IRQ all pass, `ppu_sprites` 19/19). The campaign corrected two ADR 0030 premises — **Mesen2 does NOT pass these tests** (both bytes `0x0A`; the correct oracle is TriCNES, the AccuracyCoin author's own MIT emulator, `ref-proj/TriCNES` commit `9199870`), and **a whole-dot port suffices** (the full 2-cycle-ALE refactor was not required). Per the maintainer's **refine-then-promote** decision (ADR 0030), the flag ships **default-off** in v2.0.2 and is **promoted to default (shipped 141/141) in v2.0.3** — after the Hybrid `+1 coarse-X` approximation is reworked to a first-principles latch-carry model and gated on the 60-ROM commercial byte-identity oracle. No snapshot-format bump (`PPU_SNAPSHOT_VERSION` stays 4). **This release does not claim the shipped build is 141/141, nor that the flag is promoted.** See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[2.0.2]` + `to-dos/plans/v2.0.2-harbor-plan.md`.
- **Earlier in the train:** **RustyNES v2.0.1 "Harbor" ("Mooring")** (2026-07-08) — the first release of the **v2.0.x mobile-finalization train** on the v2.0.0 "Timebase" core: the Android core re-port + `foss`/`play` flavor-split scaffolding (ADR 0025), the **AccuracyCoin oracle re-sync** (catalog 144→146 rows / 139→141 assigned; measured honestly at **139/141, 98.58%** — the two new upstream PPU tests "ALE + Read" / "Hybrid Addresses" documented as gaps, then solved flag-on in v2.0.2 per ADR 0030), the **CI cost optimization** (heavy suite gated to `release/*` + a weekly cron), the **dependency sweep** (uniffi 0.32 / mlua 0.12 / wgpu-naga 29.0.4 / cc 1.2.66; wgpu 30 deferred on the egui 0.35 pin), and the **`mc-r1-dmc-abort-probe` housekeeping removal**. Every core change is behaviour-neutral, so the deterministic core is byte-identical to v2.0.0: the **139 passing** AccuracyCoin tests and nestest 0-diff are unchanged — only the *denominator* grew (139→141) as the oracle re-sync added the two new upstream PPU tests. See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[2.0.1]` + `to-dos/plans/v2.0.1-harbor-plan.md`.
-- **Preceding additive/platform release:** **RustyNES v1.10.0 "Arcade"** (2026-07-01) — the native **Libretro core** (`crates/rustynes-libretro` builds `rustynes_libretro` for RetroArch: allocation-free video, batched-audio dynamic-rate sync, WRAM/SRAM RetroAchievements maps, deterministic rollback-ready save-states) plus the egui 0.34.3 → 0.35.0 dependency-tier refresh. This is the latest in an unbroken additive/off-by-default chain running all the way back to v1.0.0: the **v1.1.0 → v1.7.1 "Forge"** desktop-feature line, the **v1.8.0 → v1.8.9 "Atlas"** Android platform train, and the **v1.9.0 → v1.9.9 "Workshop"** iOS TestFlight train (see the sub-bullets below for each). AccuracyCoin has held **100.00% (139/139)** and nestest **0-diff** through every one of these releases; mapper coverage is **172 families** (Core / Curated / BestEffort, CI honesty-gated). RustyNES ships as: a native desktop app (Linux/macOS/Windows), a WebAssembly build (browser demo), a native Android app (GitHub-sideload; Google Play deferred to v2.1.0), a native iOS/iPadOS app (TestFlight; App Store deferred to v2.1.0), and a native Libretro/RetroArch core. See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[1.10.0]`…`[1.0.0]`.
+- **Historical anchor — the last v1.x release:** **RustyNES v1.10.0 "Arcade"** (2026-07-01) — the native **Libretro core** (`crates/rustynes-libretro` builds `rustynes_libretro` for RetroArch: allocation-free video, batched-audio dynamic-rate sync, WRAM/SRAM RetroAchievements maps, deterministic rollback-ready save-states) plus the egui 0.34.3 → 0.35.0 dependency-tier refresh. It closed an unbroken additive/off-by-default chain running all the way back to v1.0.0: the **v1.1.0 → v1.7.1 "Forge"** desktop-feature line, the **v1.8.0 → v1.8.9 "Atlas"** Android platform train, and the **v1.9.0 → v1.9.9 "Workshop"** iOS TestFlight train (see the sub-bullets below for each). AccuracyCoin has held **100.00% (139/139)** and nestest **0-diff** through every one of these releases; mapper coverage is **172 families** (Core / Curated / BestEffort, CI honesty-gated). RustyNES ships as: a native desktop app (Linux/macOS/Windows), a WebAssembly build (browser demo), a native Android app (GitHub-sideload; Google Play deferred to v2.1.0), a native iOS/iPadOS app (TestFlight; App Store deferred to v2.1.0), and a native Libretro/RetroArch core. See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[1.10.0]`…`[1.0.0]`.
- **v2.0.0 "Timebase" — released 2026-07-03.** The forward architectural milestone this Status block used to describe as a distant, high-risk future refactor (see "The path to v2.0.0" below, now updated) has landed and shipped: the one-clock/every-cycle-bus-access scheduler promote (beta.1→beta.4, PRs #217-220), full Vs. `DualSystem` dual-console support with a real commercial-title boot (beta.5, PR #221), and the save-state/movie format break + the two capstone ADRs (rc.1, PR #222 — ADR 0028 save-state v3 + ADR 0029 the timebase architecture) are all merged to `main`. AccuracyCoin held 100% (139/139) at every gate across all five betas + rc.1. The MMC3 R1/R2 IRQ-timing residual was investigated exhaustively (21+ documented attempts total, including two dedicated 2026-07-02 campaigns) and is by-design-deferred beyond v2.0.0 with a mechanism-level explanation (ADR 0002's decision-update section) rather than closed — this is the one known gap in an otherwise complete cut. The tag + release-ceremony + binary publish are done, and the **v2.0.1 "Harbor" ("Mooring")** train now builds on it.
- **RustyNES feature/platform-release history (on the v1.0.0 core; all additive / off-by-default; AccuracyCoin held 100% (139/139) throughout):**
- **v1.1.0 "Scriptable"** (2026-06-15) — full NES_NTSC composite + CRT/scanline shaders + `.pal` palette filters; NES Power Pad + turbo/autofire + an input-display overlay + a per-game nametable-mirroring override DB; debugger breakpoints + a cycle trace logger + an event viewer (behind `debug-hooks`); an NSF/NSFe player + a 5-band graphic EQ; and the flagship **Lua scripting engine** (`rustynes-script`, ADR 0010). See `CHANGELOG.md` `[1.1.0]`.
@@ -84,9 +85,9 @@ v2.8.0 → v0.9.7; the synthesis itself = **v1.0.0**.
- **Engine-lineage phase:** Phase 8 — **engine v1.2.0 (2026-05-24).** DMC DMA scheduler refactor landed under default-off cargo feature `dmc-get-put-scheduler` introducing Mesen2's canonical get/put cycle alternation model alongside the v1.1.0 phase-agnostic scheduler via the parallel-implementation pattern (ADR 0007). AccuracyCoin DMA cluster under flag-on: **6/10 match baseline** (closing 4 → 0 deferred to v1.2.x patches or v2.0 master-clock absorption). Default build bit-identical to v1.1.0.
- **Engine-lineage — earlier work:** **engine v1.1.0 (2026-05-25)** — VRC7 OPLL FM audio via clean-room pure-Rust port of `emu2413 v1.5.9` (MIT); ADR 0006 supersedes ADR 0004; *Lagrange Point* plays with audio. (engine v1.1.0 was an engine v2.0.0-release-plan milestone slotted between Phase 6 and Phase 8, **not** the ROADMAP's Phase 7 — see the numbering note below.) Phase 6 — **engine v1.0.0 (2026-05-23)**: AccuracyCoin gate CLEARED at 90.65% (126/139); T-60-001 C1 IRQ-timing residuals (3 `cpu_interrupts_v2` sub-ROMs + `mmc3_test_2/4` #3) deferred to the master-clock-precise scheduling refactor (Session-29 empirically falsified Option A global PPU-position shift; 17 documented rollbacks). [That engine-lineage master-clock work subsequently landed in the RustyNES v1.0.0 core, taking AccuracyCoin to 100%.]
- **Phase-numbering note:** the shipped releases v1.1.0 → v1.4.0 were sequenced from the v2.0.0 release plan and back-labelled in the detailed sections as v1.1.0 (VRC7) → Phase 8 (v1.2.0 DMC) → Phase 9 (v1.3.0 wasm) → Phase 10 (v1.4.0 TAS). **Phase 7 — Nesdev Accuracy Hardening (below) was authored but never executed**; it is now being executed as **v1.5.0**. See `docs/audit/phase-7-assessment-2026-05-24.md` for the full intent-vs-accomplished-vs-completable disposition.
-- **Current state:** **RustyNES v1.10.0 "Arcade" — the latest tagged release; v2.0.0 "Timebase" is code-complete on `main` with the tag pending.** Every accuracy, compatibility, platform, netplay, RetroAchievements, FDS, Vs/PC10, and performance milestone in the engine-lineage history above is folded into the v1.0.0 core; the v1.1.0 → v1.7.x feature releases then layered (in order) the Lua scripting engine + visual filters/peripherals/devtools/NSF, the library/compatibility/reach pass, the toolchain modernization + Memory-Compare + Vs.-DualSystem detection, the accuracy-and-finish pass, the insight/scriptability/creator-tooling/polish pass, the studio/TAS-tooling/debugger-depth pass, and the writable/programmable-tooling "Forge" pass; the v1.8.x train ported the whole core to Android, the v1.9.x train ported it to iOS/iPadOS, and v1.10.0 added the native Libretro core. Mapper coverage rose **51 → 172 families** across these releases, all additive / off-by-default, with AccuracyCoin holding **100% (139/139)** the entire time. v2.0.0 then landed the one-clock/every-cycle timebase promote + full Vs. `DualSystem` support + the save-state/movie format break — the first genuinely BREAKING release since v1.0.0, by design (ADR 0028/0029). The engine-lineage version markers (v0.9.x → v2.x) in the bullets above and the phase bodies are upstream history, not RustyNES releases.
+- **Current state:** **RustyNES v2.2.4 "Cartridge" is the latest tagged release** (see the Status section at the top for the current-release detail and the v2.1.0 → v2.2.4 line). **v2.0.0 "Timebase" shipped 2026-07-03** — the paragraph below describing it as "code-complete, tag pending" is retained as a historical snapshot of that release's landing, not a current-state claim. Every accuracy, compatibility, platform, netplay, RetroAchievements, FDS, Vs/PC10, and performance milestone in the engine-lineage history above is folded into the v1.0.0 core; the v1.1.0 → v1.7.x feature releases then layered (in order) the Lua scripting engine + visual filters/peripherals/devtools/NSF, the library/compatibility/reach pass, the toolchain modernization + Memory-Compare + Vs.-DualSystem detection, the accuracy-and-finish pass, the insight/scriptability/creator-tooling/polish pass, the studio/TAS-tooling/debugger-depth pass, and the writable/programmable-tooling "Forge" pass; the v1.8.x train ported the whole core to Android, the v1.9.x train ported it to iOS/iPadOS, and v1.10.0 added the native Libretro core. Mapper coverage rose **51 → 172 families** across these releases, all additive / off-by-default, with AccuracyCoin holding **100% (139/139)** the entire time. v2.0.0 then landed the one-clock/every-cycle timebase promote + full Vs. `DualSystem` support + the save-state/movie format break — the first genuinely BREAKING release since v1.0.0, by design (ADR 0028/0029). The engine-lineage version markers (v0.9.x → v2.x) in the bullets above and the phase bodies are upstream history, not RustyNES releases.
-**v2.0.0 "Timebase" — code-complete, tag pending. What actually shipped (2026-07-01 → 2026-07-03):**
+**v2.0.0 "Timebase" — historical landing snapshot (shipped 2026-07-03; this section was written as it landed). What shipped (2026-07-01 → 2026-07-03):**
The forward architectural milestone this section used to describe as a distant, XL/HIGH-risk future refactor has landed, across beta.1 → beta.5 → rc.1 (PRs #217-222). What was originally scoped as workstreams A-F in `to-dos/plans/v2.0.0-master-clock-plan.md`: