Skip to content

Commit 60d43a7

Browse files
doublegateclaude
andauthored
release: cut v2.0.1 "Harbor" (first Android re-port onto Timebase + AccuracyCoin re-sync + housekeeping) (#238)
RustyNES v2.0.1 "Harbor" — the first release of the v2.0.x mobile-finalization train on the v2.0.0 "Timebase" core. Consolidates PRs #231–#237: the Android core re-port + foss/play flavor-split scaffolding (ADR 0025), the AccuracyCoin oracle re-sync (146 rows / 141 assigned; honest 139/141), the CI cost optimization + ADR 0030, the Option-2 octal-latch scaffold, the dependency sweep, and the mc-r1-dmc-abort-probe housekeeping removal. Behaviour-neutral: the deterministic core is byte-identical to v2.0.0, nestest 0-diff, AccuracyCoin 139/141. Version 2.0.0 -> 2.0.1 across all 18 crates; CHANGELOG [2.0.1] "Harbor"; .github/release-notes/v2.0.1.md; STATUS/ROADMAP/README refreshed. release-auto.yml auto-tags v2.0.1 on green main + publishes the GitHub Release + desktop binaries. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1e4f036 commit 60d43a7

8 files changed

Lines changed: 335 additions & 105 deletions

File tree

‎.github/release-notes/v2.0.1.md‎

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# RustyNES v2.0.1 — "Harbor"
2+
3+
**The first release of the v2.0.x "Harbor" mobile-finalization train.** With the
4+
breaking v2.0.0 "Timebase" scheduler rewrite shipped, Harbor opens the runway to
5+
the joint mobile store launch (v2.1.0): the Android app is **re-ported onto the
6+
one-clock v2.0.0 core**, the **AccuracyCoin oracle is re-synced to upstream**, and
7+
the housekeeping that v2.0.0 (#225) deferred to "v2.0.1+" is done. Every core
8+
change is **behaviour-neutral** — the deterministic chip stack is byte-identical to
9+
v2.0.0, `nestest` stays 0-diff, and AccuracyCoin holds its honest **139/141
10+
(98.58%)**. No store submission yet (that is v2.1.0); Android continues as
11+
GitHub-sideload.
12+
13+
---
14+
15+
## Android re-ported onto Timebase (#235)
16+
17+
The `rustynes-android` host + Jetpack Compose app rebuild against the one-clock
18+
`rustynes-mobile` UniFFI bridge (regenerated against **uniffi 0.32**);
19+
`versionCode 20000 → 20001`, `versionName "2.0.0" → "2.0.1"`. The deterministic
20+
chip stack is untouched, so on-device accuracy tracks the desktop core exactly.
21+
22+
The **`.rnm` movie-epoch warning** now reaches the mobile bridge (ADR 0028):
23+
`NesController::movie_play` peeks `rustynes_core::recorded_before_v2_timebase` and,
24+
for a pre-v2.0.0 recording, queues a drainable host warning (new
25+
`drain_warnings`) — parity with the desktop + wasm frontends. Input replay still
26+
proceeds; the host can now tell the user that exact framebuffer/audio reproduction
27+
is not guaranteed across the engine-timebase boundary, rather than silently
28+
implying byte-exactness.
29+
30+
## The `foss` / `play` Android flavor split — structural start (ADR 0025, #235)
31+
32+
Harbor lays the groundwork for shipping to **F-Droid + GitHub sideload** (clean,
33+
Google-free, ad-free) *and* **Google Play** from one codebase. A new
34+
`distribution` flavor dimension adds a default **`foss`** flavor and a **`play`**
35+
flavor; the five proprietary Google-Play subsystems — Play Billing, the Cast
36+
Application Framework, Play Games v2, Play Integrity, in-app update/review — move
37+
from unconditional `implementation(…)` to **`playImplementation(…)`**, so the
38+
`foss` variant links **none** of them. Their glue lives behind `src/main` façades
39+
with **no-op `src/foss/`** twins and real `src/play/` implementations (including
40+
the CAF `MediaRouteButton`, routed through the `ChromecastSender` façade so
41+
`src/main` links no `androidx.mediarouter` / `com.google.android.gms.cast`). CI
42+
now builds **both** `assembleFoss*` and `assemblePlay*`. Monetization stays
43+
dormant; on-device flavor verification is deferred to v2.0.9.
44+
45+
## AccuracyCoin oracle re-synced to upstream (#232)
46+
47+
The vendored `100thCoin/AccuracyCoin` battery is re-synced: the catalog grows
48+
**144 → 146 rows / 139 → 141 assigned tests**, adding the two new upstream PPU
49+
tests **"ALE + Read"** and **"Hybrid Addresses"** (plus the upstream "$2002 Flag
50+
Timing" + "Sprites On Scanline 0" fixes folded into existing rows). The drift-guard
51+
moves to `catalog().len() == 146`.
52+
53+
**Measured honestly: 139/141 (98.58%).** The two new tests are known gaps — they
54+
exercise the deep sub-instruction PPU octal-latch address-corruption timing when
55+
`$2007` is touched mid-render. Two bounded fixes were attempted this release —
56+
Option 2 (`mc-ppu-bus-addr-hybrid`, Mesen2's persistent `_ppuBusAddress` model,
57+
PR #234) and Option 1 (a 2-cycle-ALE fetch refactor, draft PR #236) — and **both
58+
reach only 139/141 with the flag on**, empirically confirming (**ADR 0030**) that
59+
closing these needs a dedicated Timebase-scale campaign, not a bounded fork. Both
60+
ship **default-off**; the shipped build is byte-identical at 139/141. The full fix
61+
is the headline accuracy work of the v2.0.2–v2.0.4 cycle.
62+
63+
## Housekeeping (#237)
64+
65+
The dead **`mc-r1-dmc-abort-probe`** diagnostic feature flag — a default-off
66+
DMC-DMA-abort scheduling probe left over from the v2.0.0 R-1 work — is removed in
67+
full (3 `[features]` decls, the ~210-line gated `abort_probe` module + its 13
68+
`#[cfg]` sites, the `bus.rs` log hooks). It gated **only** diagnostic counters,
69+
never shipped behaviour, so the default build is byte-identical. The
70+
`mmc3-m2-phase-irq` experiment was evaluated for the same treatment and
71+
deliberately **kept** — it gates real alternate MMC3-IRQ-timing behaviour and
72+
remains tied to the open R1/R2 residual (ADR 0002). A **measure-first perf pass**
73+
(`full_frame` re-measured at 3.77 ms / 4.42× realtime) adopted nothing per the
74+
3%-plus Criterion + byte-identical bar.
75+
76+
## CI cost/time optimization (#233)
77+
78+
The heavy CI legs (`test-roms`, `bench`, the full macOS/Windows matrix) now run
79+
only on `release/*` PRs plus a weekly drift-net cron — not on every intermediate
80+
PR — while the required correctness gates still run everywhere. Intermediate
81+
review cycles are dramatically cheaper; a release branch (like this one) still
82+
gets the full battery.
83+
84+
## Dependencies
85+
86+
**uniffi 0.31 → 0.32**, **mlua 0.11 → 0.12**, **wgpu/naga 29.0.3 → 29.0.4**,
87+
**cc 1.2.65 → 1.2.66**; security-advisory surface re-verified. Consolidates and
88+
closes Dependabot #226–#230. **wgpu/naga 30.0.0 is deferred**: `egui-wgpu 0.35`
89+
pins `wgpu ^29` and egui 0.35 is the newest release, so the majors cannot resolve
90+
until an egui 0.36+ targets wgpu 30.
91+
92+
---
93+
94+
## What's next — the rest of "Harbor"
95+
96+
- **v2.0.2–v2.0.4** — Android feature-parity finalization, foss/play on-device
97+
verification + monetization wiring, and the **full ALE+Read / Hybrid Addresses
98+
accuracy fix** (Option 1 to 141/141).
99+
- **v2.0.5–v2.0.8** — iOS finalization on the v2.0.0 core.
100+
- **v2.0.9** — joint readiness verification for both apps.
101+
- **v2.1.0** — the **joint Google Play + Apple App Store + AltStore PAL + F-Droid
102+
launch**.
103+
104+
Full detail: `CHANGELOG.md` `[2.0.1]` · `docs/STATUS.md` (single source of truth) ·
105+
`to-dos/plans/v2.0.1-harbor-plan.md`.

‎CHANGELOG.md‎

Lines changed: 95 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -15,71 +15,108 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
1616
## [Unreleased]
1717

18-
### Android — v2.0.1 core re-port onto Timebase + the start of the `foss`/`play` split
19-
20-
- **Android re-ported onto the v2.0.0 "Timebase" core.** The `versionCode`/`versionName`
21-
bump to `20001` / `2.0.1` cuts the first release of the Android mobile-finalization
22-
re-port train (see `to-dos/ROADMAP.md`). The deterministic chip stack is byte-identical,
23-
so host CI accuracy is unchanged (**AccuracyCoin 139/141**).
24-
- **Movie-epoch warning on the mobile bridge (ADR 0028).** `rustynes-mobile`'s
25-
`NesController::movie_play` now peeks `rustynes_core::recorded_before_v2_timebase` and, for
26-
a pre-v2.0.0 `.rnm`, queues a drainable host warning (new `NesController::drain_warnings`)
27-
— mirroring the desktop + wasm frontends. Input replay still proceeds, but the host can now
28-
tell the user that exact framebuffer/audio reproduction is not guaranteed across the
29-
engine-timebase boundary. A pre-v2.0.0 `.rns` restore already maps to a clean
30-
`MobileError::SaveState` (no crash). Host-shell wiring only; the core is untouched.
31-
- **Structural start of the `foss` / `play` Android product-flavor split (ADR 0025).** Added a
32-
`distribution` flavor dimension — `foss` (default, `isDefault = true`) + `play` — with
33-
`PLAY_BUILD` set per-flavor. Every proprietary Google-Play SDK (Play Billing, the Cast
34-
Application Framework, Play Games v2, Play Integrity, in-app update/review) moved from
35-
unconditional `implementation(…)` to **`playImplementation(…)`**, so the `foss` variant links
36-
**none** of them. The glue that touches those SDKs (`LicenseManager`, `PlayGamesManager`,
37-
`CloudSaveManager`, `IntegrityManager`, `PlayUpdatesManager`, `ChromecastSender`) moved to
38-
`src/play/` with byte-compatible **no-op façades in `src/foss/`**; shared pure types
39-
(`PgsIds`, `IntegrityVerdict`, `DEMO_SESSION_SECONDS`) moved to `src/main`; `MainActivity`
40-
now reads a flavor-neutral `LicenseManager.priceLabel` instead of the Google `ProductDetails`
41-
type, so `src/main` links no Play SDK. The Cast-framework OPTIONS_PROVIDER + Play-Games APP_ID
42-
manifest meta-data moved to `src/play/AndroidManifest.xml` so they never merge into the
43-
`foss` manifest. An `installDebug` alias forwards to `installFossDebug` so the dev/CI command
44-
is unchanged; `android.yml` now bundles both flavors. The monetization / ad glue stays dormant
45-
(the AppLovin/RevenueCat wiring is the v2.1.0 step). The AOSP Presentation-API `CastManager`
46-
is not a proprietary SDK and stays shared in `src/main`.
47-
- **Docs:** added the previously-dangling `to-dos/v1.8.x-on-device-verification.md` (the manual
48-
on-device checklist the mobile plan cites).
18+
*Nothing yet — the next planned milestone is **v2.0.2**, continuing the v2.0.x
19+
"Harbor" mobile-finalization train (see `to-dos/ROADMAP.md`).*
20+
21+
## [2.0.1] - 2026-07-08 - "Harbor" (first Android re-port onto Timebase + AccuracyCoin re-sync + housekeeping)
22+
23+
The first release of the **v2.0.x "Harbor" mobile-finalization train** — the
24+
Android app re-ported onto the v2.0.0 "Timebase" core, the AccuracyCoin oracle
25+
re-synced to upstream, and the housekeeping #225 deferred to "v2.0.1+". All core
26+
changes are behaviour-neutral: the deterministic chip stack is byte-identical to
27+
v2.0.0 and AccuracyCoin holds its honest **139/141 (98.58%)** (the two new upstream
28+
PPU tests are known, documented gaps — see below). No store submission (that is
29+
the future v2.1.0 joint launch); Android continues as GitHub-sideload.
30+
31+
### Added
32+
33+
- **Android core re-port onto the v2.0.0 Timebase core (ADR 0028, PR #235).** The
34+
`rustynes-android` host + Jetpack Compose app rebuild against the one-clock
35+
`rustynes-mobile` UniFFI bridge (regenerated against uniffi 0.32);
36+
`versionCode 20000 → 20001`, `versionName "2.0.0" → "2.0.1"`. The `.rnm` movie
37+
epoch warning is wired into the mobile `movie_play` path
38+
(`recorded_before_v2_timebase` → a drainable host warning via
39+
`NesController::drain_warnings` citing ADR 0028), reaching desktop/wasm parity: a
40+
pre-v2.0.0 movie replays its input faithfully but surfaces the cross-timebase
41+
reproduction caveat, and a pre-v2.0.0 `.rns` restore maps to a clean
42+
`MobileError::SaveState`. Host-shell wiring only; the core is untouched.
43+
- **`foss` / `play` Android flavor split — structural scaffolding (ADR 0025, PR
44+
#235).** A new `distribution` flavor dimension with a default **`foss`** flavor
45+
(no Google SDKs, no ads, no tracking — the F-Droid/sideload artifact) and a
46+
**`play`** flavor. The five proprietary subsystems (Billing, Cast/Chromecast,
47+
Play Games v2, Integrity, in-app update/review) move to `playImplementation`
48+
behind `src/main` façades with **no-op `src/foss/`** twins and real `src/play/`
49+
implementations — including the CAF `MediaRouteButton` (routed through the
50+
`ChromecastSender` façade so `src/main` links no `androidx.mediarouter` /
51+
`com.google.android.gms.cast`). CI builds both `assembleFoss*`/`assemblePlay*`
52+
(the foss+play Gradle bundle is green). Monetization stays dormant; on-device
53+
flavor verification is deferred to v2.0.9. Also added the previously-dangling
54+
`to-dos/v1.8.x-on-device-verification.md`.
55+
56+
### Changed
57+
58+
- **AccuracyCoin oracle re-synced to upstream `100thCoin/AccuracyCoin` (PR #232).**
59+
The catalog grows **144 → 146 rows / 139 → 141 assigned tests** — the two new
60+
upstream PPU tests, **"ALE + Read"** and **"Hybrid Addresses"**, plus the
61+
upstream "$2002 Flag Timing" + "Sprites On Scanline 0" fixes folded into the
62+
existing rows. The drift-guards in
63+
`crates/rustynes-test-harness/src/accuracy_coin_catalog.rs` move to `== 146` /
64+
last-index 145. **Measured honestly: 139/141 (98.58%)** — the two new tests are
65+
known gaps (deep sub-instruction PPU octal-latch address-corruption timing)
66+
tracked for a dedicated future accuracy campaign (ADR 0030), not blocked on here.
67+
- **CI cost/time optimization — full suite gated to release branches (PR #233).**
68+
The heavy legs (`test-roms`, `bench`, and the full macOS/Windows matrix) now run
69+
only on `release/*` PRs + a weekly `schedule` drift-net, not on every
70+
intermediate PR; the required correctness gates still run on all PRs.
71+
- **`docs/performance.md`** gains a v2.0.1 `full_frame` measure-first entry
72+
(`nes_run_frame_nestest` 3.77 ms / 4.42× realtime; `flowing_palette` 2.26 ms /
73+
7.37×), confirmed far under the 16.639 ms NTSC budget. Per the
74+
3%-plus Criterion-stable + byte-identical bar (v1.7.0 H7 precedent), **no
75+
optimization was adopted**.
76+
- **`docs/adr/0030`** (new) records the octal-latch roadmap: both bounded v2.0.1
77+
attempts — Option 2 (`mc-ppu-bus-addr-hybrid`, PR #234) and Option 1 (the
78+
2-cycle-ALE draft, PR #236) — reach only 139/141 flag-on, empirically confirming
79+
the ALE+Read / Hybrid Addresses fix needs a dedicated Timebase-scale campaign.
80+
**139/141 is the honest v2.0.1 baseline.**
81+
- **`docs/STATUS.md`** feature-flags section: the DMC-abort-probe removal recorded
82+
as done; the new default-off v2.0.1 experiments (`mc-ppu-bus-addr-hybrid`,
83+
ADR 0030; `mmc3-m2-phase-irq`, ADR 0002) listed among the remaining flags.
84+
4985
### Removed
5086

51-
- **Dead `mc-r1-dmc-abort-probe` diagnostic feature (v2.0.1 housekeeping).** The
52-
default-off DMC-DMA-abort scheduling probe left over from the v2.0.0 Timebase
53-
R-1 work is removed in full: the `[features]` declarations in `rustynes-apu`,
54-
`rustynes-core`, and `rustynes-test-harness`; the gated `abort_probe` pub-static
55-
atomic-counter module and its `lib.rs` re-export; every `#[cfg(feature =
56-
"mc-r1-dmc-abort-probe")]` increment/dump site in `rustynes-apu/src/apu.rs` and
57-
`rustynes-core/src/bus.rs`; and the probe-only reporting block in the
58-
`scan_dma_abort` diagnostic bin (the bin itself is retained — its no-features
59-
RAM-scan fast-path has standalone value). The flag gated **only** diagnostic
60-
counters and CSV dumps, never shipped behaviour, so the default build is
61-
byte-identical — **AccuracyCoin holds 139/141, nestest 0-diff**. The
62-
`mmc3-m2-phase-irq` experiment was evaluated for the same treatment and
87+
- **Dead `mc-r1-dmc-abort-probe` diagnostic feature (v2.0.1 housekeeping, PR
88+
#237).** The default-off DMC-DMA-abort scheduling probe left over from the
89+
v2.0.0 Timebase R-1 work is removed in full: the `[features]` declarations in
90+
`rustynes-apu`, `rustynes-core`, and `rustynes-test-harness`; the gated
91+
`abort_probe` pub-static atomic-counter module and its `lib.rs` re-export; every
92+
`#[cfg(feature = "mc-r1-dmc-abort-probe")]` increment/dump site in
93+
`rustynes-apu/src/apu.rs` and `rustynes-core/src/bus.rs`; and the probe-only
94+
reporting block in the `scan_dma_abort` diagnostic bin (the bin itself is
95+
retained — its no-features RAM-scan fast-path has standalone value). The flag
96+
gated **only** diagnostic counters and CSV dumps, never shipped behaviour, so the
97+
default build is byte-identical — **AccuracyCoin holds 139/141, nestest 0-diff**.
98+
The `mmc3-m2-phase-irq` experiment was evaluated for the same treatment and
6399
deliberately **kept**: it gates real alternate MMC3-IRQ-timing behaviour (with
64100
`#[cfg(not)]` shipped-path counterparts) and remains tied to the still-open
65101
R1/R2 residual investigation (ADR 0002, by-design-deferred, not closed).
66102

67-
### Changed
103+
### Dependencies
68104

69-
- **`docs/performance.md`** gains a v2.0.1 `full_frame` measure-first entry:
70-
the end-to-end headless frame cost is re-measured and confirmed far under the
71-
16.639 ms NTSC budget and against the ≤2 ms/frame target; per the standing
72-
>3%-Criterion-stable + byte-identical bar (v1.7.0 H7 precedent), **no
73-
optimization was adopted**.
74-
- **`docs/adr/0030`** records that both bounded v2.0.1 AccuracyCoin ALE/hybrid
75-
attempts — Option 2 (`mc-ppu-bus-addr-hybrid`, PR #234) and Option 1 (the
76-
2-cycle-ALE draft, PR #236) — reached only 139/141 flag-on, empirically
77-
confirming the fix needs a dedicated Timebase-scale campaign, not a bounded
78-
fork. **139/141 is the honest v2.0.1 baseline.**
79-
- **`docs/STATUS.md`** feature-flags section updated: the DMC-abort-probe removal
80-
is now recorded as done, and the new default-off v2.0.1 experiments
81-
(`mc-ppu-bus-addr-hybrid`, ADR 0030; `mmc3-m2-phase-irq`, ADR 0002) are listed
82-
among the genuinely-remaining flags.
105+
- **uniffi 0.31 → 0.32** (`rustynes-mobile` + `rustynes-monetization`, lockstep),
106+
**mlua 0.11 → 0.12** (`rustynes-script`), **wgpu/naga 29.0.3 → 29.0.4** (the
107+
in-tier patch), **cc 1.2.65 → 1.2.66**; security-advisory surface re-verified
108+
(cargo-deny + cargo-audit). Consolidates and closes Dependabot #226–#230.
109+
**wgpu/naga 30.0.0 deferred**: `egui-wgpu 0.35` pins `wgpu ^29` and egui 0.35 is
110+
the newest release, so the 30 majors cannot resolve until an egui 0.36+ targets
111+
wgpu 30 (tracked follow-up).
112+
113+
### Notes
114+
115+
- The `mc-ppu-bus-addr-hybrid` (Option 2, PR #234) octal-latch scaffold ships
116+
**default-off** and does **not** yet pass the two new AccuracyCoin tests; with
117+
the flag off the build is byte-identical at 139/141. The full fix (Option 1,
118+
2-cycle-ALE) is carried forward to the v2.0.2–v2.0.4 "Harbor" cycle (draft PR
119+
#236, ADR 0030).
83120

84121
## [2.0.0] - 2026-07-03 - "Timebase" (one-clock master-clock rewrite + Vs. DualSystem)
85122

0 commit comments

Comments
 (0)